noormme 1.0.0 → 1.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/LICENSE +89 -21
- package/README.md +195 -578
- package/dist/cjs/cli/commands/analyze.js +4 -1
- package/dist/cjs/cli/commands/generate.js +48 -8
- package/dist/cjs/cli/commands/init.js +54 -14
- package/dist/cjs/cli/commands/inspect.js +10 -3
- package/dist/cjs/cli/commands/migrate.js +38 -2
- package/dist/cjs/cli/commands/optimize.js +4 -1
- package/dist/cjs/cli/commands/status.js +41 -5
- package/dist/cjs/cli/commands/watch.js +4 -1
- package/dist/cjs/cli/index.js +4 -1
- package/dist/cjs/dialect/database-introspector.js +16 -21
- package/dist/cjs/dialect/sqlite/sqlite-introspector.js +13 -24
- package/dist/cjs/dynamic/dynamic.d.ts +20 -0
- package/dist/cjs/dynamic/dynamic.js +25 -0
- package/dist/cjs/edge-runtime/edge-config.d.ts +125 -0
- package/dist/cjs/edge-runtime/edge-config.js +323 -0
- package/dist/cjs/errors/NoormError.d.ts +27 -4
- package/dist/cjs/errors/NoormError.js +142 -21
- package/dist/cjs/logging/logger.d.ts +7 -2
- package/dist/cjs/logging/logger.js +21 -7
- package/dist/cjs/noormme.d.ts +12 -8
- package/dist/cjs/noormme.js +133 -61
- package/dist/cjs/operation-node/column-node.js +4 -0
- package/dist/cjs/operation-node/identifier-node.js +4 -0
- package/dist/cjs/operation-node/table-node.js +7 -0
- package/dist/cjs/parser/reference-parser.js +5 -0
- package/dist/cjs/parser/table-parser.js +2 -0
- package/dist/cjs/performance/index.d.ts +44 -0
- package/dist/cjs/performance/index.js +64 -0
- package/dist/cjs/performance/query-optimizer.d.ts +134 -0
- package/dist/cjs/performance/query-optimizer.js +391 -0
- package/dist/cjs/performance/services/cache-service.d.ts +177 -0
- package/dist/cjs/performance/services/cache-service.js +415 -0
- package/dist/cjs/performance/services/connection-factory.d.ts +198 -0
- package/dist/cjs/performance/services/connection-factory.js +498 -0
- package/dist/cjs/performance/services/metrics-collector.d.ts +162 -0
- package/dist/cjs/performance/services/metrics-collector.js +406 -0
- package/dist/cjs/performance/utils/query-parser.d.ts +123 -0
- package/dist/cjs/performance/utils/query-parser.js +295 -0
- package/dist/cjs/raw-builder/sql.d.ts +73 -26
- package/dist/cjs/raw-builder/sql.js +9 -0
- package/dist/cjs/repository/repository-factory.d.ts +10 -42
- package/dist/cjs/repository/repository-factory.js +276 -394
- package/dist/cjs/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
- package/dist/cjs/schema/core/factories/discovery-factory.js +5 -5
- package/dist/cjs/schema/core/utils/name-generator.js +34 -2
- package/dist/cjs/schema/core/utils/type-mapper.d.ts +19 -14
- package/dist/cjs/schema/core/utils/type-mapper.js +4 -7
- package/dist/cjs/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
- package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
- package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
- package/dist/cjs/schema/test/dialect-capabilities.test.js +6 -0
- package/dist/cjs/schema/test/error-handling.test.js +52 -33
- package/dist/cjs/schema/test/integration.test.js +51 -5
- package/dist/cjs/schema/test/sqlite-discovery-coordinator.test.js +8 -4
- package/dist/cjs/sqlite-migration/index.js +35 -2
- package/dist/cjs/testing/test-utils.d.ts +5 -0
- package/dist/cjs/testing/test-utils.js +66 -6
- package/dist/cjs/types/index.d.ts +78 -13
- package/dist/cjs/types/index.js +23 -0
- package/dist/cjs/types/type-generator.d.ts +8 -0
- package/dist/cjs/types/type-generator.js +86 -17
- package/dist/cjs/util/safe-sql-helpers.d.ts +124 -0
- package/dist/cjs/util/safe-sql-helpers.js +221 -0
- package/dist/cjs/util/security-validator.d.ts +44 -0
- package/dist/cjs/util/security-validator.js +256 -0
- package/dist/cjs/util/security.d.ts +60 -0
- package/dist/cjs/util/security.js +137 -0
- package/dist/cjs/watch/schema-watcher.js +26 -7
- package/dist/esm/cli/commands/generate.js +10 -6
- package/dist/esm/cli/commands/init.js +15 -11
- package/dist/esm/cli/commands/inspect.js +6 -2
- package/dist/esm/cli/commands/status.js +3 -3
- package/dist/esm/dialect/database-introspector.js +16 -21
- package/dist/esm/dialect/sqlite/sqlite-introspector.js +13 -24
- package/dist/esm/dynamic/dynamic.d.ts +20 -0
- package/dist/esm/dynamic/dynamic.js +25 -0
- package/dist/esm/edge-runtime/edge-config.d.ts +125 -0
- package/dist/esm/edge-runtime/edge-config.js +281 -0
- package/dist/esm/errors/NoormError.d.ts +27 -4
- package/dist/esm/errors/NoormError.js +134 -18
- package/dist/esm/logging/logger.d.ts +7 -2
- package/dist/esm/logging/logger.js +21 -7
- package/dist/esm/noormme.d.ts +12 -8
- package/dist/esm/noormme.js +130 -61
- package/dist/esm/operation-node/column-node.js +4 -0
- package/dist/esm/operation-node/identifier-node.js +4 -0
- package/dist/esm/operation-node/table-node.js +7 -0
- package/dist/esm/parser/reference-parser.js +5 -0
- package/dist/esm/parser/table-parser.js +2 -0
- package/dist/esm/performance/index.d.ts +44 -0
- package/dist/esm/performance/index.js +48 -0
- package/dist/esm/performance/query-optimizer.d.ts +134 -0
- package/dist/esm/performance/query-optimizer.js +387 -0
- package/dist/esm/performance/services/cache-service.d.ts +177 -0
- package/dist/esm/performance/services/cache-service.js +410 -0
- package/dist/esm/performance/services/connection-factory.d.ts +198 -0
- package/dist/esm/performance/services/connection-factory.js +493 -0
- package/dist/esm/performance/services/metrics-collector.d.ts +162 -0
- package/dist/esm/performance/services/metrics-collector.js +402 -0
- package/dist/esm/performance/utils/query-parser.d.ts +123 -0
- package/dist/esm/performance/utils/query-parser.js +292 -0
- package/dist/esm/raw-builder/sql.d.ts +73 -26
- package/dist/esm/raw-builder/sql.js +9 -0
- package/dist/esm/repository/repository-factory.d.ts +10 -42
- package/dist/esm/repository/repository-factory.js +277 -395
- package/dist/esm/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
- package/dist/esm/schema/core/factories/discovery-factory.js +5 -5
- package/dist/esm/schema/core/utils/name-generator.js +34 -2
- package/dist/esm/schema/core/utils/type-mapper.d.ts +19 -14
- package/dist/esm/schema/core/utils/type-mapper.js +4 -7
- package/dist/esm/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
- package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
- package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
- package/dist/esm/schema/test/dialect-capabilities.test.js +6 -0
- package/dist/esm/schema/test/error-handling.test.js +52 -33
- package/dist/esm/schema/test/integration.test.js +18 -5
- package/dist/esm/schema/test/sqlite-discovery-coordinator.test.js +8 -4
- package/dist/esm/testing/test-utils.d.ts +5 -0
- package/dist/esm/testing/test-utils.js +66 -6
- package/dist/esm/types/index.d.ts +78 -13
- package/dist/esm/types/index.js +22 -1
- package/dist/esm/types/type-generator.d.ts +8 -0
- package/dist/esm/types/type-generator.js +86 -17
- package/dist/esm/util/safe-sql-helpers.d.ts +124 -0
- package/dist/esm/util/safe-sql-helpers.js +209 -0
- package/dist/esm/util/security-validator.d.ts +44 -0
- package/dist/esm/util/security-validator.js +246 -0
- package/dist/esm/util/security.d.ts +60 -0
- package/dist/esm/util/security.js +114 -0
- package/dist/esm/watch/schema-watcher.js +26 -7
- package/package.json +1 -1
- package/dist/cjs/performance/query-analyzer.d.ts +0 -89
- package/dist/cjs/performance/query-analyzer.js +0 -263
- package/dist/esm/performance/query-analyzer.d.ts +0 -89
- package/dist/esm/performance/query-analyzer.js +0 -260
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/// <reference types="./cache-service.d.ts" />
|
|
2
|
+
import { Logger } from '../../logging/logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generic cache service with TTL, size limits, and metrics
|
|
5
|
+
*/
|
|
6
|
+
export class CacheService {
|
|
7
|
+
cache = new Map();
|
|
8
|
+
config;
|
|
9
|
+
stats;
|
|
10
|
+
metrics;
|
|
11
|
+
cleanupTimer;
|
|
12
|
+
logger;
|
|
13
|
+
constructor(config = {}, logger) {
|
|
14
|
+
this.logger = logger || new Logger('CacheService');
|
|
15
|
+
this.config = {
|
|
16
|
+
maxSize: 1000,
|
|
17
|
+
defaultTtl: 300000, // 5 minutes
|
|
18
|
+
cleanupInterval: 60000, // 1 minute
|
|
19
|
+
enableCompression: false,
|
|
20
|
+
enableMetrics: true,
|
|
21
|
+
...config
|
|
22
|
+
};
|
|
23
|
+
this.stats = {
|
|
24
|
+
size: 0,
|
|
25
|
+
maxSize: this.config.maxSize,
|
|
26
|
+
hitCount: 0,
|
|
27
|
+
missCount: 0,
|
|
28
|
+
hitRate: 0,
|
|
29
|
+
totalSize: 0,
|
|
30
|
+
evictions: 0,
|
|
31
|
+
lastCleanup: new Date()
|
|
32
|
+
};
|
|
33
|
+
this.metrics = {
|
|
34
|
+
operations: {
|
|
35
|
+
get: 0,
|
|
36
|
+
set: 0,
|
|
37
|
+
delete: 0,
|
|
38
|
+
clear: 0
|
|
39
|
+
},
|
|
40
|
+
performance: {
|
|
41
|
+
averageGetTime: 0,
|
|
42
|
+
averageSetTime: 0,
|
|
43
|
+
slowestOperation: 0
|
|
44
|
+
},
|
|
45
|
+
memory: {
|
|
46
|
+
totalSize: 0,
|
|
47
|
+
averageEntrySize: 0,
|
|
48
|
+
largestEntry: 0
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
this.startCleanupTimer();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get a value from cache
|
|
55
|
+
*/
|
|
56
|
+
get(key) {
|
|
57
|
+
const startTime = performance.now();
|
|
58
|
+
try {
|
|
59
|
+
this.metrics.operations.get++;
|
|
60
|
+
const entry = this.cache.get(key);
|
|
61
|
+
if (!entry) {
|
|
62
|
+
this.stats.missCount++;
|
|
63
|
+
this.updateHitRate();
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// Check if entry has expired
|
|
67
|
+
if (this.isExpired(entry)) {
|
|
68
|
+
this.cache.delete(key);
|
|
69
|
+
this.stats.missCount++;
|
|
70
|
+
this.updateHitRate();
|
|
71
|
+
this.updateStats();
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// Update hit count and last access
|
|
75
|
+
entry.hitCount++;
|
|
76
|
+
this.stats.hitCount++;
|
|
77
|
+
this.updateHitRate();
|
|
78
|
+
const duration = performance.now() - startTime;
|
|
79
|
+
this.updatePerformanceMetrics('get', duration);
|
|
80
|
+
return entry.value;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.logger.error(`Cache get error for key ${key}:`, error);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Set a value in cache
|
|
89
|
+
*/
|
|
90
|
+
set(key, value, ttl) {
|
|
91
|
+
const startTime = performance.now();
|
|
92
|
+
try {
|
|
93
|
+
this.metrics.operations.set++;
|
|
94
|
+
const entrySize = this.calculateSize(value);
|
|
95
|
+
const entryTtl = ttl || this.config.defaultTtl;
|
|
96
|
+
// Check if we need to evict entries
|
|
97
|
+
if (this.cache.size >= this.config.maxSize) {
|
|
98
|
+
this.evictEntries();
|
|
99
|
+
}
|
|
100
|
+
const entry = {
|
|
101
|
+
key,
|
|
102
|
+
value,
|
|
103
|
+
timestamp: Date.now(),
|
|
104
|
+
ttl: entryTtl,
|
|
105
|
+
hitCount: 0,
|
|
106
|
+
size: entrySize
|
|
107
|
+
};
|
|
108
|
+
this.cache.set(key, entry);
|
|
109
|
+
this.updateStats();
|
|
110
|
+
const duration = performance.now() - startTime;
|
|
111
|
+
this.updatePerformanceMetrics('set', duration);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
this.logger.error(`Cache set error for key ${key}:`, error);
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Delete a value from cache
|
|
121
|
+
*/
|
|
122
|
+
delete(key) {
|
|
123
|
+
try {
|
|
124
|
+
this.metrics.operations.delete++;
|
|
125
|
+
const deleted = this.cache.delete(key);
|
|
126
|
+
if (deleted) {
|
|
127
|
+
this.updateStats();
|
|
128
|
+
}
|
|
129
|
+
return deleted;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
this.logger.error(`Cache delete error for key ${key}:`, error);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if key exists in cache
|
|
138
|
+
*/
|
|
139
|
+
has(key) {
|
|
140
|
+
const entry = this.cache.get(key);
|
|
141
|
+
if (!entry)
|
|
142
|
+
return false;
|
|
143
|
+
if (this.isExpired(entry)) {
|
|
144
|
+
this.cache.delete(key);
|
|
145
|
+
this.updateStats();
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Clear all entries from cache
|
|
152
|
+
*/
|
|
153
|
+
clear() {
|
|
154
|
+
try {
|
|
155
|
+
this.metrics.operations.clear++;
|
|
156
|
+
this.cache.clear();
|
|
157
|
+
this.updateStats();
|
|
158
|
+
this.logger.info('Cache cleared');
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
this.logger.error('Cache clear error:', error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get cache statistics
|
|
166
|
+
*/
|
|
167
|
+
getStats() {
|
|
168
|
+
return { ...this.stats };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get detailed metrics
|
|
172
|
+
*/
|
|
173
|
+
getMetrics() {
|
|
174
|
+
return { ...this.metrics };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get cache size
|
|
178
|
+
*/
|
|
179
|
+
size() {
|
|
180
|
+
return this.cache.size;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get all cache keys
|
|
184
|
+
*/
|
|
185
|
+
keys() {
|
|
186
|
+
return Array.from(this.cache.keys());
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get cache entries (for debugging)
|
|
190
|
+
*/
|
|
191
|
+
entries() {
|
|
192
|
+
return Array.from(this.cache.entries()).map(([key, entry]) => ({ key, entry }));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Warm cache with multiple entries
|
|
196
|
+
*/
|
|
197
|
+
warm(entries) {
|
|
198
|
+
let warmed = 0;
|
|
199
|
+
for (const { key, value, ttl } of entries) {
|
|
200
|
+
if (this.set(key, value, ttl)) {
|
|
201
|
+
warmed++;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
this.logger.info(`Warmed cache with ${warmed}/${entries.length} entries`);
|
|
205
|
+
return warmed;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get cache health status
|
|
209
|
+
*/
|
|
210
|
+
getHealth() {
|
|
211
|
+
const issues = [];
|
|
212
|
+
const recommendations = [];
|
|
213
|
+
// Check hit rate
|
|
214
|
+
if (this.stats.hitRate < 0.5) {
|
|
215
|
+
issues.push(`Low hit rate: ${(this.stats.hitRate * 100).toFixed(1)}%`);
|
|
216
|
+
recommendations.push('Consider increasing TTL or cache size');
|
|
217
|
+
}
|
|
218
|
+
// Check memory usage
|
|
219
|
+
if (this.stats.totalSize > 100 * 1024 * 1024) { // 100MB
|
|
220
|
+
issues.push(`High memory usage: ${(this.stats.totalSize / 1024 / 1024).toFixed(1)}MB`);
|
|
221
|
+
recommendations.push('Consider reducing cache size or enabling compression');
|
|
222
|
+
}
|
|
223
|
+
// Check eviction rate
|
|
224
|
+
const evictionRate = this.stats.evictions / Math.max(this.stats.hitCount + this.stats.missCount, 1);
|
|
225
|
+
if (evictionRate > 0.1) {
|
|
226
|
+
issues.push(`High eviction rate: ${(evictionRate * 100).toFixed(1)}%`);
|
|
227
|
+
recommendations.push('Consider increasing cache size');
|
|
228
|
+
}
|
|
229
|
+
let status = 'healthy';
|
|
230
|
+
if (issues.length > 0) {
|
|
231
|
+
status = issues.some(issue => issue.includes('critical')) ? 'critical' : 'warning';
|
|
232
|
+
}
|
|
233
|
+
return { status, issues, recommendations };
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Shutdown cache service
|
|
237
|
+
*/
|
|
238
|
+
shutdown() {
|
|
239
|
+
if (this.cleanupTimer) {
|
|
240
|
+
clearInterval(this.cleanupTimer);
|
|
241
|
+
}
|
|
242
|
+
this.clear();
|
|
243
|
+
this.logger.info('Cache service shutdown');
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Check if cache entry is expired
|
|
247
|
+
*/
|
|
248
|
+
isExpired(entry) {
|
|
249
|
+
return Date.now() - entry.timestamp > entry.ttl;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Calculate size of a value (rough estimation)
|
|
253
|
+
*/
|
|
254
|
+
calculateSize(value) {
|
|
255
|
+
try {
|
|
256
|
+
return JSON.stringify(value).length * 2; // Rough estimate in bytes
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return 1024; // Default size if serialization fails
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Evict entries when cache is full
|
|
264
|
+
*/
|
|
265
|
+
evictEntries() {
|
|
266
|
+
const entries = Array.from(this.cache.entries());
|
|
267
|
+
// Sort by hit count and timestamp (LRU with hit count consideration)
|
|
268
|
+
entries.sort((a, b) => {
|
|
269
|
+
const [, entryA] = a;
|
|
270
|
+
const [, entryB] = b;
|
|
271
|
+
// Prefer entries with fewer hits
|
|
272
|
+
if (entryA.hitCount !== entryB.hitCount) {
|
|
273
|
+
return entryA.hitCount - entryB.hitCount;
|
|
274
|
+
}
|
|
275
|
+
// If hit counts are equal, prefer older entries
|
|
276
|
+
return entryA.timestamp - entryB.timestamp;
|
|
277
|
+
});
|
|
278
|
+
// Remove oldest/lowest hit count entries
|
|
279
|
+
const toRemove = Math.ceil(entries.length * 0.1); // Remove 10% of entries
|
|
280
|
+
for (let i = 0; i < toRemove && i < entries.length; i++) {
|
|
281
|
+
const [key] = entries[i];
|
|
282
|
+
this.cache.delete(key);
|
|
283
|
+
this.stats.evictions++;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Update cache statistics
|
|
288
|
+
*/
|
|
289
|
+
updateStats() {
|
|
290
|
+
this.stats.size = this.cache.size;
|
|
291
|
+
let totalSize = 0;
|
|
292
|
+
let largestEntry = 0;
|
|
293
|
+
for (const entry of this.cache.values()) {
|
|
294
|
+
totalSize += entry.size;
|
|
295
|
+
largestEntry = Math.max(largestEntry, entry.size);
|
|
296
|
+
}
|
|
297
|
+
this.stats.totalSize = totalSize;
|
|
298
|
+
this.metrics.memory.totalSize = totalSize;
|
|
299
|
+
this.metrics.memory.largestEntry = largestEntry;
|
|
300
|
+
this.metrics.memory.averageEntrySize = this.cache.size > 0 ? totalSize / this.cache.size : 0;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Update hit rate
|
|
304
|
+
*/
|
|
305
|
+
updateHitRate() {
|
|
306
|
+
const total = this.stats.hitCount + this.stats.missCount;
|
|
307
|
+
this.stats.hitRate = total > 0 ? this.stats.hitCount / total : 0;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Update performance metrics
|
|
311
|
+
*/
|
|
312
|
+
updatePerformanceMetrics(operation, duration) {
|
|
313
|
+
if (!this.config.enableMetrics)
|
|
314
|
+
return;
|
|
315
|
+
this.metrics.performance.slowestOperation = Math.max(this.metrics.performance.slowestOperation, duration);
|
|
316
|
+
if (operation === 'get') {
|
|
317
|
+
const totalGets = this.metrics.operations.get;
|
|
318
|
+
this.metrics.performance.averageGetTime =
|
|
319
|
+
(this.metrics.performance.averageGetTime * (totalGets - 1) + duration) / totalGets;
|
|
320
|
+
}
|
|
321
|
+
else if (operation === 'set') {
|
|
322
|
+
const totalSets = this.metrics.operations.set;
|
|
323
|
+
this.metrics.performance.averageSetTime =
|
|
324
|
+
(this.metrics.performance.averageSetTime * (totalSets - 1) + duration) / totalSets;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Start cleanup timer
|
|
329
|
+
*/
|
|
330
|
+
startCleanupTimer() {
|
|
331
|
+
this.cleanupTimer = setInterval(() => {
|
|
332
|
+
this.cleanup();
|
|
333
|
+
}, this.config.cleanupInterval);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Cleanup expired entries
|
|
337
|
+
*/
|
|
338
|
+
cleanup() {
|
|
339
|
+
const now = Date.now();
|
|
340
|
+
let cleaned = 0;
|
|
341
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
342
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
343
|
+
this.cache.delete(key);
|
|
344
|
+
cleaned++;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (cleaned > 0) {
|
|
348
|
+
this.stats.lastCleanup = new Date();
|
|
349
|
+
this.updateStats();
|
|
350
|
+
this.logger.debug(`Cleaned up ${cleaned} expired cache entries`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Factory function to create cache service
|
|
356
|
+
*/
|
|
357
|
+
export function createCacheService(config, logger) {
|
|
358
|
+
return new CacheService(config, logger);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Specialized query cache service
|
|
362
|
+
*/
|
|
363
|
+
export class QueryCacheService extends CacheService {
|
|
364
|
+
constructor(config, logger) {
|
|
365
|
+
super({
|
|
366
|
+
maxSize: 1000,
|
|
367
|
+
defaultTtl: 300000, // 5 minutes
|
|
368
|
+
cleanupInterval: 60000, // 1 minute
|
|
369
|
+
enableMetrics: true,
|
|
370
|
+
...config
|
|
371
|
+
}, logger);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Cache query result with automatic key generation
|
|
375
|
+
*/
|
|
376
|
+
cacheQuery(query, params, result, ttl) {
|
|
377
|
+
const key = this.generateQueryKey(query, params);
|
|
378
|
+
return this.set(key, result, ttl);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Get cached query result
|
|
382
|
+
*/
|
|
383
|
+
getCachedQuery(query, params) {
|
|
384
|
+
const key = this.generateQueryKey(query, params);
|
|
385
|
+
return this.get(key);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Generate cache key for query
|
|
389
|
+
*/
|
|
390
|
+
generateQueryKey(query, params) {
|
|
391
|
+
const normalizedQuery = query.replace(/\s+/g, ' ').trim();
|
|
392
|
+
const paramsKey = params.map(p => String(p)).join('|');
|
|
393
|
+
return `query:${normalizedQuery}:${paramsKey}`;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Invalidate cache entries for a specific table
|
|
397
|
+
*/
|
|
398
|
+
invalidateTable(table) {
|
|
399
|
+
let invalidated = 0;
|
|
400
|
+
for (const key of this.keys()) {
|
|
401
|
+
if (key.includes(`FROM ${table}`) || key.includes(`JOIN ${table}`)) {
|
|
402
|
+
if (this.delete(key)) {
|
|
403
|
+
invalidated++;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
this.logger?.info(`Invalidated ${invalidated} cache entries for table ${table}`);
|
|
408
|
+
return invalidated;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { NOORMME } from '../../noormme';
|
|
2
|
+
import { NOORMConfig } from '../../types';
|
|
3
|
+
import { Logger } from '../../logging/logger.js';
|
|
4
|
+
export interface ConnectionConfig extends NOORMConfig {
|
|
5
|
+
id?: string;
|
|
6
|
+
maxRetries?: number;
|
|
7
|
+
retryDelay?: number;
|
|
8
|
+
validationTimeout?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface PooledConnection {
|
|
11
|
+
id: string;
|
|
12
|
+
db: NOORMME;
|
|
13
|
+
createdAt: Date;
|
|
14
|
+
lastUsed: Date;
|
|
15
|
+
isActive: boolean;
|
|
16
|
+
inUse: boolean;
|
|
17
|
+
config: ConnectionConfig;
|
|
18
|
+
}
|
|
19
|
+
export interface ConnectionValidationResult {
|
|
20
|
+
isValid: boolean;
|
|
21
|
+
error?: string;
|
|
22
|
+
responseTime?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface ConnectionStats {
|
|
25
|
+
totalCreated: number;
|
|
26
|
+
totalDestroyed: number;
|
|
27
|
+
activeConnections: number;
|
|
28
|
+
failedCreations: number;
|
|
29
|
+
averageCreationTime: number;
|
|
30
|
+
averageValidationTime: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Factory for creating and managing database connections
|
|
34
|
+
*/
|
|
35
|
+
export declare class ConnectionFactory {
|
|
36
|
+
private connections;
|
|
37
|
+
private stats;
|
|
38
|
+
private logger;
|
|
39
|
+
constructor(logger?: Logger);
|
|
40
|
+
/**
|
|
41
|
+
* Create a new database connection
|
|
42
|
+
*/
|
|
43
|
+
createConnection(config: ConnectionConfig): Promise<PooledConnection>;
|
|
44
|
+
/**
|
|
45
|
+
* Validate a connection
|
|
46
|
+
*/
|
|
47
|
+
validateConnection(connection: PooledConnection): Promise<ConnectionValidationResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Get connection by ID
|
|
50
|
+
*/
|
|
51
|
+
getConnection(id: string): PooledConnection | null;
|
|
52
|
+
/**
|
|
53
|
+
* Get all connections
|
|
54
|
+
*/
|
|
55
|
+
getAllConnections(): PooledConnection[];
|
|
56
|
+
/**
|
|
57
|
+
* Get active connections
|
|
58
|
+
*/
|
|
59
|
+
getActiveConnections(): PooledConnection[];
|
|
60
|
+
/**
|
|
61
|
+
* Get idle connections
|
|
62
|
+
*/
|
|
63
|
+
getIdleConnections(): PooledConnection[];
|
|
64
|
+
/**
|
|
65
|
+
* Mark connection as in use
|
|
66
|
+
*/
|
|
67
|
+
markInUse(connection: PooledConnection): void;
|
|
68
|
+
/**
|
|
69
|
+
* Mark connection as idle
|
|
70
|
+
*/
|
|
71
|
+
markIdle(connection: PooledConnection): void;
|
|
72
|
+
/**
|
|
73
|
+
* Destroy a connection
|
|
74
|
+
*/
|
|
75
|
+
destroyConnection(id: string): Promise<boolean>;
|
|
76
|
+
/**
|
|
77
|
+
* Destroy all connections
|
|
78
|
+
*/
|
|
79
|
+
destroyAllConnections(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Cleanup inactive connections
|
|
82
|
+
*/
|
|
83
|
+
cleanupInactiveConnections(maxAge?: number): Promise<number>;
|
|
84
|
+
/**
|
|
85
|
+
* Validate all connections
|
|
86
|
+
*/
|
|
87
|
+
validateAllConnections(): Promise<Array<{
|
|
88
|
+
id: string;
|
|
89
|
+
isValid: boolean;
|
|
90
|
+
error?: string;
|
|
91
|
+
responseTime?: number;
|
|
92
|
+
}>>;
|
|
93
|
+
/**
|
|
94
|
+
* Get connection statistics
|
|
95
|
+
*/
|
|
96
|
+
getStats(): ConnectionStats;
|
|
97
|
+
/**
|
|
98
|
+
* Get connection health status
|
|
99
|
+
*/
|
|
100
|
+
getHealthStatus(): Promise<{
|
|
101
|
+
status: 'healthy' | 'warning' | 'critical';
|
|
102
|
+
issues: string[];
|
|
103
|
+
connections: {
|
|
104
|
+
total: number;
|
|
105
|
+
active: number;
|
|
106
|
+
idle: number;
|
|
107
|
+
inUse: number;
|
|
108
|
+
};
|
|
109
|
+
}>;
|
|
110
|
+
/**
|
|
111
|
+
* Generate unique connection ID
|
|
112
|
+
*/
|
|
113
|
+
private generateConnectionId;
|
|
114
|
+
/**
|
|
115
|
+
* Update statistics
|
|
116
|
+
*/
|
|
117
|
+
private updateStats;
|
|
118
|
+
/**
|
|
119
|
+
* Update average time for operations
|
|
120
|
+
*/
|
|
121
|
+
private updateAverageTime;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Connection pool manager using the factory
|
|
125
|
+
*/
|
|
126
|
+
export declare class ConnectionPoolManager {
|
|
127
|
+
private factory;
|
|
128
|
+
private config;
|
|
129
|
+
private poolConfig;
|
|
130
|
+
private waitingQueue;
|
|
131
|
+
private validationTimer?;
|
|
132
|
+
private isShuttingDown;
|
|
133
|
+
private logger;
|
|
134
|
+
constructor(config: ConnectionConfig, poolConfig?: Partial<ConnectionPoolManager['poolConfig']>, logger?: Logger);
|
|
135
|
+
/**
|
|
136
|
+
* Initialize the connection pool
|
|
137
|
+
*/
|
|
138
|
+
initialize(): Promise<void>;
|
|
139
|
+
/**
|
|
140
|
+
* Acquire a connection from the pool
|
|
141
|
+
*/
|
|
142
|
+
acquire(): Promise<PooledConnection>;
|
|
143
|
+
/**
|
|
144
|
+
* Release a connection back to the pool
|
|
145
|
+
*/
|
|
146
|
+
release(connection: PooledConnection): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Execute a function with a pooled connection
|
|
149
|
+
*/
|
|
150
|
+
withConnection<T>(fn: (db: NOORMME) => Promise<T>): Promise<T>;
|
|
151
|
+
/**
|
|
152
|
+
* Get pool statistics
|
|
153
|
+
*/
|
|
154
|
+
getStats(): {
|
|
155
|
+
poolSize: number;
|
|
156
|
+
idleConnections: number;
|
|
157
|
+
inUseConnections: number;
|
|
158
|
+
waitingQueue: number;
|
|
159
|
+
totalCreated: number;
|
|
160
|
+
totalDestroyed: number;
|
|
161
|
+
activeConnections: number;
|
|
162
|
+
failedCreations: number;
|
|
163
|
+
averageCreationTime: number;
|
|
164
|
+
averageValidationTime: number;
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Shutdown the connection pool
|
|
168
|
+
*/
|
|
169
|
+
shutdown(): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* Get idle connection from pool
|
|
172
|
+
*/
|
|
173
|
+
private getIdleConnection;
|
|
174
|
+
/**
|
|
175
|
+
* Wait for a connection to become available
|
|
176
|
+
*/
|
|
177
|
+
private waitForConnection;
|
|
178
|
+
/**
|
|
179
|
+
* Process waiting queue when connections become available
|
|
180
|
+
*/
|
|
181
|
+
private processWaitingQueue;
|
|
182
|
+
/**
|
|
183
|
+
* Start validation timer
|
|
184
|
+
*/
|
|
185
|
+
private startValidationTimer;
|
|
186
|
+
/**
|
|
187
|
+
* Validate all connections in the pool
|
|
188
|
+
*/
|
|
189
|
+
private validateConnections;
|
|
190
|
+
/**
|
|
191
|
+
* Cleanup idle connections that exceed the idle timeout
|
|
192
|
+
*/
|
|
193
|
+
private cleanupIdleConnections;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Factory function to create connection pool manager
|
|
197
|
+
*/
|
|
198
|
+
export declare function createConnectionPool(config: ConnectionConfig, poolConfig?: Partial<ConnectionPoolManager['poolConfig']>, logger?: Logger): ConnectionPoolManager;
|