bunsane 0.1.4 → 0.2.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/.claude/settings.local.json +47 -0
- package/.claude/skills/update-memory.md +74 -0
- package/.prettierrc +4 -0
- package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
- package/.serena/memories/architecture.md +154 -0
- package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
- package/.serena/memories/code_style_and_conventions.md +76 -0
- package/.serena/memories/project_overview.md +43 -0
- package/.serena/memories/schema-dsl-plan.md +107 -0
- package/.serena/memories/suggested_commands.md +80 -0
- package/.serena/memories/typescript-compilation-status.md +54 -0
- package/.serena/project.yml +114 -0
- package/TODO.md +1 -7
- package/bun.lock +150 -4
- package/bunfig.toml +10 -0
- package/config/cache.config.ts +77 -0
- package/config/upload.config.ts +4 -5
- package/core/App.ts +870 -123
- package/core/ArcheType.ts +2268 -377
- package/core/BatchLoader.ts +181 -71
- package/core/Config.ts +153 -0
- package/core/Decorators.ts +4 -1
- package/core/Entity.ts +621 -92
- package/core/EntityHookManager.ts +1 -1
- package/core/EntityInterface.ts +3 -1
- package/core/EntityManager.ts +1 -13
- package/core/ErrorHandler.ts +8 -2
- package/core/Logger.ts +9 -0
- package/core/Middleware.ts +34 -0
- package/core/RequestContext.ts +5 -1
- package/core/RequestLoaders.ts +227 -93
- package/core/SchedulerManager.ts +193 -52
- package/core/cache/CacheAnalytics.ts +399 -0
- package/core/cache/CacheFactory.ts +145 -0
- package/core/cache/CacheManager.ts +520 -0
- package/core/cache/CacheProvider.ts +34 -0
- package/core/cache/CacheWarmer.ts +157 -0
- package/core/cache/CompressionUtils.ts +110 -0
- package/core/cache/MemoryCache.ts +251 -0
- package/core/cache/MultiLevelCache.ts +180 -0
- package/core/cache/NoOpCache.ts +53 -0
- package/core/cache/RedisCache.ts +464 -0
- package/core/cache/TTLStrategy.ts +254 -0
- package/core/cache/index.ts +6 -0
- package/core/components/BaseComponent.ts +120 -0
- package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
- package/core/components/Decorators.ts +88 -0
- package/core/components/Interfaces.ts +7 -0
- package/core/components/index.ts +5 -0
- package/core/decorators/EntityHooks.ts +0 -3
- package/core/decorators/IndexedField.ts +26 -0
- package/core/decorators/ScheduledTask.ts +0 -47
- package/core/events/EntityLifecycleEvents.ts +1 -1
- package/core/health.ts +112 -0
- package/core/metadata/definitions/ArcheType.ts +14 -0
- package/core/metadata/definitions/Component.ts +9 -0
- package/core/metadata/definitions/gqlObject.ts +1 -1
- package/core/metadata/index.ts +42 -1
- package/core/metadata/metadata-storage.ts +28 -2
- package/core/middleware/AccessLog.ts +59 -0
- package/core/middleware/RequestId.ts +38 -0
- package/core/middleware/SecurityHeaders.ts +62 -0
- package/core/middleware/index.ts +3 -0
- package/core/scheduler/DistributedLock.ts +266 -0
- package/core/scheduler/index.ts +15 -0
- package/core/validateEnv.ts +92 -0
- package/database/DatabaseHelper.ts +416 -40
- package/database/IndexingStrategy.ts +342 -0
- package/database/PreparedStatementCache.ts +226 -0
- package/database/index.ts +32 -7
- package/database/sqlHelpers.ts +14 -2
- package/endpoints/archetypes.ts +362 -0
- package/endpoints/components.ts +58 -0
- package/endpoints/entity.ts +80 -0
- package/endpoints/index.ts +27 -0
- package/endpoints/query.ts +93 -0
- package/endpoints/stats.ts +76 -0
- package/endpoints/tables.ts +212 -0
- package/endpoints/types.ts +155 -0
- package/gql/ArchetypeOperations.ts +32 -86
- package/gql/Generator.ts +27 -315
- package/gql/GeneratorV2.ts +37 -0
- package/gql/builders/InputTypeBuilder.ts +99 -0
- package/gql/builders/ResolverBuilder.ts +234 -0
- package/gql/builders/TypeDefBuilder.ts +105 -0
- package/gql/builders/index.ts +3 -0
- package/gql/decorators/Upload.ts +1 -1
- package/gql/depthLimit.ts +85 -0
- package/gql/graph/GraphNode.ts +224 -0
- package/gql/graph/SchemaGraph.ts +278 -0
- package/gql/helpers.ts +8 -2
- package/gql/index.ts +56 -4
- package/gql/middleware.ts +79 -0
- package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
- package/gql/orchestration/index.ts +1 -0
- package/gql/scanner/ServiceScanner.ts +347 -0
- package/gql/schema/index.ts +458 -0
- package/gql/strategies/TypeGenerationStrategy.ts +329 -0
- package/gql/types.ts +1 -0
- package/gql/utils/TypeSignature.ts +220 -0
- package/gql/utils/index.ts +1 -0
- package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
- package/gql/visitors/DeduplicationVisitor.ts +82 -0
- package/gql/visitors/GraphVisitor.ts +78 -0
- package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
- package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
- package/gql/visitors/TypeCollectorVisitor.ts +79 -0
- package/gql/visitors/VisitorComposer.ts +96 -0
- package/gql/visitors/index.ts +7 -0
- package/package.json +59 -37
- package/plugins/index.ts +2 -2
- package/query/CTENode.ts +97 -0
- package/query/ComponentInclusionNode.ts +689 -0
- package/query/FilterBuilder.ts +127 -0
- package/query/FilterBuilderRegistry.ts +202 -0
- package/query/OrNode.ts +517 -0
- package/query/OrQuery.ts +42 -0
- package/query/Query.ts +1022 -0
- package/query/QueryContext.ts +170 -0
- package/query/QueryDAG.ts +122 -0
- package/query/QueryNode.ts +65 -0
- package/query/SourceNode.ts +53 -0
- package/query/builders/FullTextSearchBuilder.ts +236 -0
- package/query/index.ts +21 -0
- package/scheduler/index.ts +40 -8
- package/service/Service.ts +2 -1
- package/service/ServiceRegistry.ts +6 -5
- package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
- package/storage/S3StorageProvider.ts +316 -0
- package/{core/storage → storage}/StorageProvider.ts +7 -3
- package/studio/bun.lock +482 -0
- package/studio/index.html +13 -0
- package/studio/package.json +39 -0
- package/studio/postcss.config.js +6 -0
- package/studio/src/components/DataTable.tsx +211 -0
- package/studio/src/components/Layout.tsx +13 -0
- package/studio/src/components/PageContainer.tsx +9 -0
- package/studio/src/components/PageHeader.tsx +13 -0
- package/studio/src/components/SearchBar.tsx +57 -0
- package/studio/src/components/Sidebar.tsx +294 -0
- package/studio/src/components/ui/button.tsx +56 -0
- package/studio/src/components/ui/checkbox.tsx +26 -0
- package/studio/src/components/ui/input.tsx +25 -0
- package/studio/src/hooks/useDataTable.ts +131 -0
- package/studio/src/index.css +36 -0
- package/studio/src/lib/api.ts +186 -0
- package/studio/src/lib/utils.ts +13 -0
- package/studio/src/main.tsx +17 -0
- package/studio/src/pages/ArcheType.tsx +239 -0
- package/studio/src/pages/Components.tsx +124 -0
- package/studio/src/pages/EntityInspector.tsx +302 -0
- package/studio/src/pages/QueryRunner.tsx +246 -0
- package/studio/src/pages/Table.tsx +94 -0
- package/studio/src/pages/Welcome.tsx +241 -0
- package/studio/src/routes.tsx +45 -0
- package/studio/src/store/archeTypeSettings.ts +30 -0
- package/studio/src/store/studio.ts +65 -0
- package/studio/src/utils/columnHelpers.tsx +114 -0
- package/studio/studio-instructions.md +81 -0
- package/studio/tailwind.config.js +77 -0
- package/studio/tsconfig.json +24 -0
- package/studio/utils.ts +54 -0
- package/studio/vite.config.js +19 -0
- package/swagger/generator.ts +1 -1
- package/tests/e2e/http.test.ts +126 -0
- package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
- package/tests/fixtures/components/TestOrder.ts +23 -0
- package/tests/fixtures/components/TestProduct.ts +23 -0
- package/tests/fixtures/components/TestUser.ts +20 -0
- package/tests/fixtures/components/index.ts +6 -0
- package/tests/graphql/SchemaGeneration.test.ts +90 -0
- package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
- package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
- package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
- package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
- package/tests/integration/entity/Entity.persistence.test.ts +333 -0
- package/tests/integration/query/Query.exec.test.ts +523 -0
- package/tests/pglite-setup.ts +61 -0
- package/tests/setup.ts +164 -0
- package/tests/stress/BenchmarkRunner.ts +203 -0
- package/tests/stress/DataSeeder.ts +190 -0
- package/tests/stress/StressTestReporter.ts +229 -0
- package/tests/stress/cursor-perf-test.ts +171 -0
- package/tests/stress/fixtures/StressTestComponents.ts +58 -0
- package/tests/stress/index.ts +7 -0
- package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
- package/tests/unit/BatchLoader.test.ts +82 -0
- package/tests/unit/archetype/ArcheType.test.ts +107 -0
- package/tests/unit/cache/CacheManager.test.ts +347 -0
- package/tests/unit/cache/MemoryCache.test.ts +260 -0
- package/tests/unit/cache/RedisCache.test.ts +411 -0
- package/tests/unit/entity/Entity.components.test.ts +244 -0
- package/tests/unit/entity/Entity.test.ts +345 -0
- package/tests/unit/gql/depthLimit.test.ts +203 -0
- package/tests/unit/gql/operationMiddleware.test.ts +293 -0
- package/tests/unit/health/Health.test.ts +129 -0
- package/tests/unit/middleware/AccessLog.test.ts +37 -0
- package/tests/unit/middleware/Middleware.test.ts +98 -0
- package/tests/unit/middleware/RequestId.test.ts +54 -0
- package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
- package/tests/unit/query/FilterBuilder.test.ts +111 -0
- package/tests/unit/query/Query.test.ts +308 -0
- package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
- package/tests/unit/schema/schema-integration.test.ts +426 -0
- package/tests/unit/schema/schema.test.ts +580 -0
- package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
- package/tests/unit/upload/RestUpload.test.ts +267 -0
- package/tests/unit/validateEnv.test.ts +82 -0
- package/tests/utils/entity-tracker.ts +57 -0
- package/tests/utils/index.ts +13 -0
- package/tests/utils/test-context.ts +149 -0
- package/tsconfig.json +5 -1
- package/types/archetype.types.ts +6 -0
- package/types/hooks.types.ts +1 -1
- package/types/query.types.ts +110 -0
- package/types/scheduler.types.ts +68 -7
- package/types/upload.types.ts +1 -0
- package/{core → upload}/FileValidator.ts +10 -1
- package/upload/RestUpload.ts +130 -0
- package/{core/components → upload}/UploadComponent.ts +11 -11
- package/{core → upload}/UploadManager.ts +3 -3
- package/upload/index.ts +23 -7
- package/utils/UploadHelper.ts +27 -6
- package/utils/cronParser.ts +16 -6
- package/.github/workflows/deploy-docs.yml +0 -57
- package/core/Components.ts +0 -202
- package/core/EntityCache.ts +0 -15
- package/core/Query.ts +0 -880
- package/docs/README.md +0 -149
- package/docs/_coverpage.md +0 -36
- package/docs/_sidebar.md +0 -23
- package/docs/api/core.md +0 -568
- package/docs/api/hooks.md +0 -554
- package/docs/api/index.md +0 -222
- package/docs/api/query.md +0 -678
- package/docs/api/service.md +0 -744
- package/docs/core-concepts/archetypes.md +0 -512
- package/docs/core-concepts/components.md +0 -498
- package/docs/core-concepts/entity.md +0 -314
- package/docs/core-concepts/hooks.md +0 -683
- package/docs/core-concepts/query.md +0 -588
- package/docs/core-concepts/services.md +0 -647
- package/docs/examples/code-examples.md +0 -425
- package/docs/getting-started.md +0 -337
- package/docs/index.html +0 -97
- package/tests/bench/insert.bench.ts +0 -60
- package/tests/bench/relations.bench.ts +0 -270
- package/tests/bench/sorting.bench.ts +0 -416
- package/tests/component-hooks-simple.test.ts +0 -117
- package/tests/component-hooks.test.ts +0 -1461
- package/tests/component.test.ts +0 -339
- package/tests/errorHandling.test.ts +0 -155
- package/tests/hooks.test.ts +0 -667
- package/tests/query-sorting.test.ts +0 -101
- package/tests/query.test.ts +0 -81
- package/tests/relations.test.ts +0 -170
- package/tests/scheduler.test.ts +0 -724
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import type { CacheProvider, CacheStats } from './CacheProvider.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CacheReport provides comprehensive analytics about cache performance
|
|
5
|
+
* and recommendations for optimization.
|
|
6
|
+
*/
|
|
7
|
+
export interface CacheReport {
|
|
8
|
+
/** Overall cache hit rate (0.0 to 1.0) */
|
|
9
|
+
hitRate: number;
|
|
10
|
+
|
|
11
|
+
/** Total number of cache requests */
|
|
12
|
+
totalRequests: number;
|
|
13
|
+
|
|
14
|
+
/** Number of cache hits */
|
|
15
|
+
hits: number;
|
|
16
|
+
|
|
17
|
+
/** Number of cache misses */
|
|
18
|
+
misses: number;
|
|
19
|
+
|
|
20
|
+
/** Average response time in milliseconds */
|
|
21
|
+
averageLatency: number;
|
|
22
|
+
|
|
23
|
+
/** Peak memory usage in bytes */
|
|
24
|
+
memoryUsage?: number;
|
|
25
|
+
|
|
26
|
+
/** Cache efficiency score (0.0 to 1.0) */
|
|
27
|
+
efficiency: number;
|
|
28
|
+
|
|
29
|
+
/** List of optimization recommendations */
|
|
30
|
+
recommendations: string[];
|
|
31
|
+
|
|
32
|
+
/** Detailed breakdown by operation type */
|
|
33
|
+
breakdown: {
|
|
34
|
+
get: { hits: number; misses: number; hitRate: number };
|
|
35
|
+
set: { operations: number; averageLatency: number };
|
|
36
|
+
delete: { operations: number; averageLatency: number };
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Time period covered by this report */
|
|
40
|
+
timeRange: {
|
|
41
|
+
start: Date;
|
|
42
|
+
end: Date;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* CacheAnalytics tracks cache performance metrics and provides
|
|
48
|
+
* optimization recommendations based on usage patterns.
|
|
49
|
+
*
|
|
50
|
+
* Features:
|
|
51
|
+
* - Hit/miss rate tracking
|
|
52
|
+
* - Latency monitoring
|
|
53
|
+
* - Memory usage analysis
|
|
54
|
+
* - Automated recommendations
|
|
55
|
+
* - Performance trend analysis
|
|
56
|
+
*/
|
|
57
|
+
export class CacheAnalytics {
|
|
58
|
+
private metrics: {
|
|
59
|
+
hits: number;
|
|
60
|
+
misses: number;
|
|
61
|
+
totalRequests: number;
|
|
62
|
+
latencies: number[];
|
|
63
|
+
operationLatencies: Map<string, number[]>;
|
|
64
|
+
memoryUsage: number[];
|
|
65
|
+
startTime: Date;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
private readonly maxLatencySamples = 1000;
|
|
69
|
+
private readonly maxMemorySamples = 100;
|
|
70
|
+
|
|
71
|
+
constructor() {
|
|
72
|
+
this.metrics = {
|
|
73
|
+
hits: 0,
|
|
74
|
+
misses: 0,
|
|
75
|
+
totalRequests: 0,
|
|
76
|
+
latencies: [],
|
|
77
|
+
operationLatencies: new Map(),
|
|
78
|
+
memoryUsage: [],
|
|
79
|
+
startTime: new Date()
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Records a cache hit
|
|
85
|
+
*/
|
|
86
|
+
recordHit(operation: string = 'get', latency?: number): void {
|
|
87
|
+
this.metrics.hits++;
|
|
88
|
+
this.metrics.totalRequests++;
|
|
89
|
+
this.recordLatency(operation, latency);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Records a cache miss
|
|
94
|
+
*/
|
|
95
|
+
recordMiss(operation: string = 'get', latency?: number): void {
|
|
96
|
+
this.metrics.misses++;
|
|
97
|
+
this.metrics.totalRequests++;
|
|
98
|
+
this.recordLatency(operation, latency);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Records operation latency
|
|
103
|
+
*/
|
|
104
|
+
recordLatency(operation: string, latency?: number): void {
|
|
105
|
+
if (latency !== undefined) {
|
|
106
|
+
this.metrics.latencies.push(latency);
|
|
107
|
+
|
|
108
|
+
// Keep only recent samples
|
|
109
|
+
if (this.metrics.latencies.length > this.maxLatencySamples) {
|
|
110
|
+
this.metrics.latencies.shift();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Record per-operation latency
|
|
114
|
+
if (!this.metrics.operationLatencies.has(operation)) {
|
|
115
|
+
this.metrics.operationLatencies.set(operation, []);
|
|
116
|
+
}
|
|
117
|
+
const opLatencies = this.metrics.operationLatencies.get(operation)!;
|
|
118
|
+
opLatencies.push(latency);
|
|
119
|
+
|
|
120
|
+
// Keep only recent samples per operation
|
|
121
|
+
if (opLatencies.length > this.maxLatencySamples / 10) {
|
|
122
|
+
opLatencies.shift();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Records memory usage
|
|
129
|
+
*/
|
|
130
|
+
recordMemoryUsage(bytes: number): void {
|
|
131
|
+
this.metrics.memoryUsage.push(bytes);
|
|
132
|
+
|
|
133
|
+
// Keep only recent samples
|
|
134
|
+
if (this.metrics.memoryUsage.length > this.maxMemorySamples) {
|
|
135
|
+
this.metrics.memoryUsage.shift();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generates a comprehensive cache performance report
|
|
141
|
+
*/
|
|
142
|
+
getReport(): CacheReport {
|
|
143
|
+
const hitRate = this.metrics.totalRequests > 0 ? this.metrics.hits / this.metrics.totalRequests : 0;
|
|
144
|
+
const averageLatency = this.metrics.latencies.length > 0
|
|
145
|
+
? this.metrics.latencies.reduce((a, b) => a + b, 0) / this.metrics.latencies.length
|
|
146
|
+
: 0;
|
|
147
|
+
|
|
148
|
+
const memoryUsage = this.metrics.memoryUsage.length > 0
|
|
149
|
+
? Math.max(...this.metrics.memoryUsage)
|
|
150
|
+
: undefined;
|
|
151
|
+
|
|
152
|
+
const efficiency = this.calculateEfficiency(hitRate, averageLatency);
|
|
153
|
+
|
|
154
|
+
const recommendations = this.generateRecommendations(hitRate, averageLatency, memoryUsage);
|
|
155
|
+
|
|
156
|
+
const breakdown = this.generateBreakdown();
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
hitRate,
|
|
160
|
+
totalRequests: this.metrics.totalRequests,
|
|
161
|
+
hits: this.metrics.hits,
|
|
162
|
+
misses: this.metrics.misses,
|
|
163
|
+
averageLatency,
|
|
164
|
+
memoryUsage,
|
|
165
|
+
efficiency,
|
|
166
|
+
recommendations,
|
|
167
|
+
breakdown,
|
|
168
|
+
timeRange: {
|
|
169
|
+
start: this.metrics.startTime,
|
|
170
|
+
end: new Date()
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Calculates cache efficiency score
|
|
177
|
+
*/
|
|
178
|
+
private calculateEfficiency(hitRate: number, averageLatency: number): number {
|
|
179
|
+
// Efficiency is based on hit rate and latency
|
|
180
|
+
// Higher hit rate and lower latency = higher efficiency
|
|
181
|
+
const latencyScore = Math.max(0, 1 - (averageLatency / 100)); // Assume 100ms is poor
|
|
182
|
+
const hitRateScore = hitRate;
|
|
183
|
+
|
|
184
|
+
return (latencyScore + hitRateScore) / 2;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Generates optimization recommendations
|
|
189
|
+
*/
|
|
190
|
+
private generateRecommendations(hitRate: number, averageLatency: number, memoryUsage?: number): string[] {
|
|
191
|
+
const recommendations: string[] = [];
|
|
192
|
+
|
|
193
|
+
if (hitRate < 0.5) {
|
|
194
|
+
recommendations.push('Cache hit rate is below 50%. Consider increasing TTL or preloading frequently accessed data.');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (hitRate > 0.9) {
|
|
198
|
+
recommendations.push('Excellent hit rate! Consider increasing TTL to reduce database load further.');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (averageLatency > 50) {
|
|
202
|
+
recommendations.push('High latency detected. Consider using faster storage or optimizing cache key generation.');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (averageLatency < 1) {
|
|
206
|
+
recommendations.push('Very low latency achieved. Cache performance is optimal.');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (memoryUsage && memoryUsage > 100 * 1024 * 1024) { // 100MB
|
|
210
|
+
recommendations.push('High memory usage detected. Consider implementing compression or reducing TTL.');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (this.metrics.totalRequests < 100) {
|
|
214
|
+
recommendations.push('Low request volume. Monitor performance as traffic increases.');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return recommendations;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Generates detailed breakdown by operation type
|
|
222
|
+
*/
|
|
223
|
+
private generateBreakdown() {
|
|
224
|
+
const getLatencies = this.metrics.operationLatencies.get('get') || [];
|
|
225
|
+
const setLatencies = this.metrics.operationLatencies.get('set') || [];
|
|
226
|
+
const deleteLatencies = this.metrics.operationLatencies.get('delete') || [];
|
|
227
|
+
|
|
228
|
+
const getHits = this.metrics.hits; // Assuming all hits are from get operations
|
|
229
|
+
const getMisses = this.metrics.misses; // Assuming all misses are from get operations
|
|
230
|
+
const getHitRate = (getHits + getMisses) > 0 ? getHits / (getHits + getMisses) : 0;
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
get: {
|
|
234
|
+
hits: getHits,
|
|
235
|
+
misses: getMisses,
|
|
236
|
+
hitRate: getHitRate
|
|
237
|
+
},
|
|
238
|
+
set: {
|
|
239
|
+
operations: setLatencies.length,
|
|
240
|
+
averageLatency: setLatencies.length > 0 ? setLatencies.reduce((a, b) => a + b, 0) / setLatencies.length : 0
|
|
241
|
+
},
|
|
242
|
+
delete: {
|
|
243
|
+
operations: deleteLatencies.length,
|
|
244
|
+
averageLatency: deleteLatencies.length > 0 ? deleteLatencies.reduce((a, b) => a + b, 0) / deleteLatencies.length : 0
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Resets all metrics
|
|
251
|
+
*/
|
|
252
|
+
reset(): void {
|
|
253
|
+
this.metrics = {
|
|
254
|
+
hits: 0,
|
|
255
|
+
misses: 0,
|
|
256
|
+
totalRequests: 0,
|
|
257
|
+
latencies: [],
|
|
258
|
+
operationLatencies: new Map(),
|
|
259
|
+
memoryUsage: [],
|
|
260
|
+
startTime: new Date()
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Gets current metrics summary
|
|
266
|
+
*/
|
|
267
|
+
getSummary(): {
|
|
268
|
+
hitRate: number;
|
|
269
|
+
totalRequests: number;
|
|
270
|
+
averageLatency: number;
|
|
271
|
+
efficiency: number;
|
|
272
|
+
} {
|
|
273
|
+
const report = this.getReport();
|
|
274
|
+
return {
|
|
275
|
+
hitRate: report.hitRate,
|
|
276
|
+
totalRequests: report.totalRequests,
|
|
277
|
+
averageLatency: report.averageLatency,
|
|
278
|
+
efficiency: report.efficiency
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Enhanced cache provider with analytics integration
|
|
285
|
+
*/
|
|
286
|
+
export class AnalyticsCacheProvider implements CacheProvider {
|
|
287
|
+
private cache: CacheProvider;
|
|
288
|
+
private analytics: CacheAnalytics;
|
|
289
|
+
|
|
290
|
+
constructor(cache: CacheProvider, analytics?: CacheAnalytics) {
|
|
291
|
+
this.cache = cache;
|
|
292
|
+
this.analytics = analytics || new CacheAnalytics();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async get(key: string): Promise<any | null> {
|
|
296
|
+
const startTime = Date.now();
|
|
297
|
+
const result = await this.cache.get(key);
|
|
298
|
+
const latency = Date.now() - startTime;
|
|
299
|
+
|
|
300
|
+
if (result !== null) {
|
|
301
|
+
this.analytics.recordHit('get', latency);
|
|
302
|
+
} else {
|
|
303
|
+
this.analytics.recordMiss('get', latency);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async set(key: string, value: any, ttl?: number): Promise<void> {
|
|
310
|
+
const startTime = Date.now();
|
|
311
|
+
await this.cache.set(key, value, ttl);
|
|
312
|
+
const latency = Date.now() - startTime;
|
|
313
|
+
|
|
314
|
+
this.analytics.recordLatency('set', latency);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async delete(key: string | string[]): Promise<void> {
|
|
318
|
+
const startTime = Date.now();
|
|
319
|
+
await this.cache.delete(key);
|
|
320
|
+
const latency = Date.now() - startTime;
|
|
321
|
+
|
|
322
|
+
this.analytics.recordLatency('delete', latency);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async clear(): Promise<void> {
|
|
326
|
+
await this.cache.clear();
|
|
327
|
+
this.analytics.reset();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async getMany<T>(keys: string[]): Promise<(T | null)[]> {
|
|
331
|
+
const startTime = Date.now();
|
|
332
|
+
const results = await this.cache.getMany<T>(keys);
|
|
333
|
+
const latency = Date.now() - startTime;
|
|
334
|
+
|
|
335
|
+
// Count hits and misses
|
|
336
|
+
let hits = 0;
|
|
337
|
+
let misses = 0;
|
|
338
|
+
|
|
339
|
+
for (const result of results) {
|
|
340
|
+
if (result !== null) {
|
|
341
|
+
hits++;
|
|
342
|
+
} else {
|
|
343
|
+
misses++;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Record metrics (approximate - we don't know individual latencies)
|
|
348
|
+
for (let i = 0; i < hits; i++) {
|
|
349
|
+
this.analytics.recordHit('get', latency / keys.length);
|
|
350
|
+
}
|
|
351
|
+
for (let i = 0; i < misses; i++) {
|
|
352
|
+
this.analytics.recordMiss('get', latency / keys.length);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return results;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async setMany<T>(entries: Array<{key: string, value: T, ttl?: number}>): Promise<void> {
|
|
359
|
+
const startTime = Date.now();
|
|
360
|
+
await this.cache.setMany(entries);
|
|
361
|
+
const latency = Date.now() - startTime;
|
|
362
|
+
|
|
363
|
+
this.analytics.recordLatency('set', latency);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async deleteMany(keys: string[]): Promise<void> {
|
|
367
|
+
const startTime = Date.now();
|
|
368
|
+
await this.cache.deleteMany(keys);
|
|
369
|
+
const latency = Date.now() - startTime;
|
|
370
|
+
|
|
371
|
+
this.analytics.recordLatency('delete', latency);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async invalidatePattern(pattern: string): Promise<void> {
|
|
375
|
+
await this.cache.invalidatePattern(pattern);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async ping(): Promise<boolean> {
|
|
379
|
+
return await this.cache.ping();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async getStats(): Promise<CacheStats> {
|
|
383
|
+
return await this.cache.getStats();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Gets the analytics instance
|
|
388
|
+
*/
|
|
389
|
+
getAnalytics(): CacheAnalytics {
|
|
390
|
+
return this.analytics;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Gets current performance report
|
|
395
|
+
*/
|
|
396
|
+
getReport(): CacheReport {
|
|
397
|
+
return this.analytics.getReport();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { type CacheProvider } from './CacheProvider';
|
|
2
|
+
import { MemoryCache, type MemoryCacheConfig } from './MemoryCache';
|
|
3
|
+
import { NoOpCache } from './NoOpCache';
|
|
4
|
+
import { RedisCache, type RedisCacheConfig } from './RedisCache';
|
|
5
|
+
import { MultiLevelCache } from './MultiLevelCache';
|
|
6
|
+
import { type CacheConfig } from '../../config/cache.config';
|
|
7
|
+
import { logger } from '../Logger';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Factory for creating cache provider instances based on configuration
|
|
11
|
+
*/
|
|
12
|
+
export class CacheFactory {
|
|
13
|
+
/**
|
|
14
|
+
* Create a cache provider instance based on the configuration
|
|
15
|
+
*/
|
|
16
|
+
public static create(config: CacheConfig): CacheProvider {
|
|
17
|
+
if (!config.enabled) {
|
|
18
|
+
logger.debug('Cache disabled, using NoOpCache');
|
|
19
|
+
return new NoOpCache();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
switch (config.provider) {
|
|
23
|
+
case 'memory':
|
|
24
|
+
return this.createMemoryCache(config);
|
|
25
|
+
case 'redis':
|
|
26
|
+
return this.createRedisCache(config);
|
|
27
|
+
case 'multilevel':
|
|
28
|
+
return this.createMultiLevelCache(config);
|
|
29
|
+
case 'noop':
|
|
30
|
+
return new NoOpCache();
|
|
31
|
+
default:
|
|
32
|
+
logger.warn(`Unknown cache provider '${config.provider}', falling back to MemoryCache`);
|
|
33
|
+
return this.createMemoryCache(config);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a MemoryCache instance with the given configuration
|
|
39
|
+
*/
|
|
40
|
+
private static createMemoryCache(config: CacheConfig): MemoryCache {
|
|
41
|
+
const memoryConfig: MemoryCacheConfig = {
|
|
42
|
+
maxSize: config.query?.maxSize ?? 10000,
|
|
43
|
+
maxMemory: config.maxMemory,
|
|
44
|
+
defaultTTL: config.defaultTTL,
|
|
45
|
+
cleanupInterval: 60000 // 1 minute cleanup interval
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
logger.debug({ msg: 'Creating MemoryCache', config: memoryConfig });
|
|
49
|
+
return new MemoryCache(memoryConfig);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a RedisCache instance with the given configuration
|
|
54
|
+
*/
|
|
55
|
+
private static createRedisCache(config: CacheConfig): RedisCache {
|
|
56
|
+
if (!config.redis) {
|
|
57
|
+
throw new Error('Redis configuration is required for Redis cache provider');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const redisConfig: RedisCacheConfig = {
|
|
61
|
+
host: config.redis.host,
|
|
62
|
+
port: config.redis.port,
|
|
63
|
+
password: config.redis.password,
|
|
64
|
+
db: config.redis.db,
|
|
65
|
+
keyPrefix: config.redis.keyPrefix,
|
|
66
|
+
retryStrategy: config.redis.retryStrategy,
|
|
67
|
+
maxRetriesPerRequest: 3,
|
|
68
|
+
lazyConnect: false,
|
|
69
|
+
enableReadyCheck: true
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const { password: _pw, ...safeConfig } = redisConfig;
|
|
73
|
+
logger.debug({ msg: 'Creating RedisCache', config: safeConfig });
|
|
74
|
+
return new RedisCache(redisConfig);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create a MultiLevelCache instance with the given configuration
|
|
79
|
+
*/
|
|
80
|
+
private static createMultiLevelCache(config: CacheConfig): MultiLevelCache {
|
|
81
|
+
// Create L1 (Memory) cache
|
|
82
|
+
const l1Cache = this.createMemoryCache(config);
|
|
83
|
+
|
|
84
|
+
// Create L2 (Redis) cache if Redis config is available
|
|
85
|
+
let l2Cache: RedisCache | null = null;
|
|
86
|
+
if (config.redis) {
|
|
87
|
+
l2Cache = this.createRedisCache(config);
|
|
88
|
+
} else {
|
|
89
|
+
logger.warn('MultiLevel cache requested but no Redis config provided, using Memory-only cache');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
logger.debug({ msg: 'Creating MultiLevelCache', hasL2: !!l2Cache });
|
|
93
|
+
return new MultiLevelCache(l1Cache, l2Cache, config);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Validate cache configuration
|
|
98
|
+
*/
|
|
99
|
+
public static validateConfig(config: CacheConfig): { valid: boolean; errors: string[] } {
|
|
100
|
+
const errors: string[] = [];
|
|
101
|
+
|
|
102
|
+
if (config.defaultTTL < 0) {
|
|
103
|
+
errors.push('defaultTTL must be non-negative');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (config.maxMemory && config.maxMemory < 0) {
|
|
107
|
+
errors.push('maxMemory must be non-negative');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (config.entity?.ttl && config.entity.ttl < 0) {
|
|
111
|
+
errors.push('entity.ttl must be non-negative');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (config.component?.ttl && config.component.ttl < 0) {
|
|
115
|
+
errors.push('component.ttl must be non-negative');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (config.query?.ttl && config.query.ttl < 0) {
|
|
119
|
+
errors.push('query.ttl must be non-negative');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (config.query?.maxSize && config.query.maxSize < 0) {
|
|
123
|
+
errors.push('query.maxSize must be non-negative');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (config.redis) {
|
|
127
|
+
if (config.redis.port < 1 || config.redis.port > 65535) {
|
|
128
|
+
errors.push('redis.port must be between 1 and 65535');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (config.redis.db && (config.redis.db < 0 || config.redis.db > 15)) {
|
|
132
|
+
errors.push('redis.db must be between 0 and 15');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (config.provider === 'redis' && !config.redis.host) {
|
|
136
|
+
errors.push('redis.host is required when using redis provider');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
valid: errors.length === 0,
|
|
142
|
+
errors
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|