@aetherframework/database 1.1.0 → 1.1.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/examples/mysql-test-pressure.js +1530 -0
- package/examples/test-direct.js +116 -0
- package/examples/transaction_example.js +127 -0
- package/package.json +3 -1
- package/src/DatabaseManager.js +565 -0
- package/src/core/ConnectionManager.js +351 -0
- package/src/core/DatabaseFactory.js +188 -0
- package/src/core/MongoQueryBuilder.js +576 -0
- package/src/core/PluginManager.js +968 -0
- package/src/core/QueryBuilder.js +4394 -0
- package/src/core/TransactionManager.js +40 -0
- package/src/drivers/clickhouse-driver.js +272 -0
- package/src/drivers/index.js +273 -0
- package/src/drivers/mongodb-driver.js +87 -0
- package/src/drivers/mssql-driver.js +117 -0
- package/src/drivers/mysql-driver.js +169 -0
- package/src/drivers/oracle-driver.js +101 -0
- package/src/drivers/postgres-driver.js +234 -0
- package/src/drivers/redis-driver.js +52 -0
- package/src/drivers/sqlite-driver.js +67 -0
- package/src/middleware/connection-pool.js +455 -0
- package/src/middleware/performance-monitor.js +652 -0
- package/src/middleware/query-cache.js +500 -0
- package/src/middleware/query-logger.js +262 -0
- package/src/plugins/AuditPlugin.js +447 -0
- package/src/plugins/BasePlugin.js +418 -0
- package/src/plugins/BatchOperationPlugin.js +165 -0
- package/src/plugins/CachePlugin.js +407 -0
- package/src/plugins/CtePlugin.js +523 -0
- package/src/plugins/DistributedPlugin.js +543 -0
- package/src/plugins/EncryptionPlugin.js +211 -0
- package/src/plugins/FullTextSearchPlugin.js +164 -0
- package/src/plugins/GeospatialPlugin.js +219 -0
- package/src/plugins/GraphQLPlugin.js +162 -0
- package/src/plugins/HookPlugin.js +211 -0
- package/src/plugins/JsonPlugin.js +366 -0
- package/src/plugins/OptimisticLockPlugin.js +374 -0
- package/src/plugins/PerformancePlugin.js +175 -0
- package/src/plugins/ResiliencePlugin.js +114 -0
- package/src/plugins/ShardingPlugin.js +227 -0
- package/src/plugins/SoftDeletePlugin.js +258 -0
- package/src/plugins/SyncPlugin.js +373 -0
- package/src/plugins/VersioningPlugin.js +314 -0
- package/src/plugins/WindowFunctionPlugin.js +343 -0
- package/src/utils/config-loader.js +632 -0
- package/src/utils/error-handler.js +724 -0
- package/src/utils/migration-runner.js +1066 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/database/plugin/CachePlugin
|
|
6
|
+
*/
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
import { BasePlugin } from './BasePlugin.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Cache Plugin - Provides query caching functionality with multiple cache drivers
|
|
12
|
+
* Supports Redis, Memcached, Memory, and custom cache implementations
|
|
13
|
+
*/
|
|
14
|
+
export class CachePlugin extends BasePlugin {
|
|
15
|
+
constructor(queryBuilder) {
|
|
16
|
+
super(queryBuilder);
|
|
17
|
+
this.cacheDriver = null;
|
|
18
|
+
this.cacheEnabled = false;
|
|
19
|
+
this.cacheConfig = {
|
|
20
|
+
defaultTtl: 300, // 5 minutes
|
|
21
|
+
prefix: 'query:',
|
|
22
|
+
tagsEnabled: false,
|
|
23
|
+
compression: false
|
|
24
|
+
};
|
|
25
|
+
this.cacheStats = {
|
|
26
|
+
hits: 0,
|
|
27
|
+
misses: 0,
|
|
28
|
+
sets: 0,
|
|
29
|
+
deletes: 0
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_registerMethods() {
|
|
34
|
+
// Register cache methods to QueryBuilder
|
|
35
|
+
this.queryBuilder.setCacheDriver = this.setCacheDriver.bind(this);
|
|
36
|
+
this.queryBuilder.cache = this.cache.bind(this);
|
|
37
|
+
this.queryBuilder.cacheWithTags = this.cacheWithTags.bind(this);
|
|
38
|
+
this.queryBuilder.executeWithCache = this.executeWithCache.bind(this);
|
|
39
|
+
this.queryBuilder.clearTableCache = this.clearTableCache.bind(this);
|
|
40
|
+
this.queryBuilder.clearCache = this.clearCache.bind(this);
|
|
41
|
+
this.queryBuilder.getCacheStats = this.getCacheStats.bind(this);
|
|
42
|
+
this.queryBuilder.generateCacheKey = this.generateCacheKey.bind(this);
|
|
43
|
+
this.queryBuilder.cacheTags = this.cacheTags.bind(this);
|
|
44
|
+
this.queryBuilder.remember = this.remember.bind(this);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Set cache driver
|
|
49
|
+
* @param {Object} cacheDriver - Cache driver instance
|
|
50
|
+
* @param {Object} options - Cache configuration
|
|
51
|
+
* @returns {QueryBuilder} Query builder instance
|
|
52
|
+
*/
|
|
53
|
+
setCacheDriver(cacheDriver, options = {}) {
|
|
54
|
+
this.cacheDriver = cacheDriver;
|
|
55
|
+
this.cacheConfig = {
|
|
56
|
+
...this.cacheConfig,
|
|
57
|
+
...options
|
|
58
|
+
};
|
|
59
|
+
this.cacheEnabled = true;
|
|
60
|
+
|
|
61
|
+
// Validate cache driver interface
|
|
62
|
+
this._validateCacheDriver();
|
|
63
|
+
|
|
64
|
+
return this.queryBuilder;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Enable query caching
|
|
69
|
+
* @param {number} ttl - Time to live in seconds
|
|
70
|
+
* @param {string} key - Custom cache key
|
|
71
|
+
* @returns {QueryBuilder} Query builder instance
|
|
72
|
+
*/
|
|
73
|
+
cache(ttl = null, key = null) {
|
|
74
|
+
if (!this.cacheEnabled) {
|
|
75
|
+
throw new Error('Cache is not enabled. Call setCacheDriver() first.');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.queryBuilder.query.cache = true;
|
|
79
|
+
this.queryBuilder.query.cacheTtl = ttl || this.cacheConfig.defaultTtl;
|
|
80
|
+
this.queryBuilder.query.cacheKey = key || this.generateCacheKey();
|
|
81
|
+
this.queryBuilder.query.cacheTags = [];
|
|
82
|
+
|
|
83
|
+
return this.queryBuilder;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Enable caching with tags
|
|
88
|
+
* @param {number} ttl - Time to live in seconds
|
|
89
|
+
* @param {Array} tags - Cache tags
|
|
90
|
+
* @returns {QueryBuilder} Query builder instance
|
|
91
|
+
*/
|
|
92
|
+
cacheWithTags(ttl = 300, tags = []) {
|
|
93
|
+
if (!this.cacheConfig.tagsEnabled) {
|
|
94
|
+
console.warn('Cache tags are not enabled in cache configuration');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.queryBuilder.query.cache = true;
|
|
98
|
+
this.queryBuilder.query.cacheTtl = ttl;
|
|
99
|
+
this.queryBuilder.query.cacheTags = tags;
|
|
100
|
+
this.queryBuilder.query.cacheKey = this.generateCacheKey();
|
|
101
|
+
|
|
102
|
+
return this.queryBuilder;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Add tags to cache
|
|
107
|
+
* @param {...string} tags - Cache tags
|
|
108
|
+
* @returns {QueryBuilder} Query builder instance
|
|
109
|
+
*/
|
|
110
|
+
cacheTags(...tags) {
|
|
111
|
+
if (!this.queryBuilder.query.cacheTags) {
|
|
112
|
+
this.queryBuilder.query.cacheTags = [];
|
|
113
|
+
}
|
|
114
|
+
this.queryBuilder.query.cacheTags.push(...tags);
|
|
115
|
+
return this.queryBuilder;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Generate cache key from query
|
|
120
|
+
* @returns {string} Cache key
|
|
121
|
+
*/
|
|
122
|
+
generateCacheKey() {
|
|
123
|
+
const { sql, bindings } = this.queryBuilder.toSQL();
|
|
124
|
+
const queryHash = crypto
|
|
125
|
+
.createHash('sha256')
|
|
126
|
+
.update(sql + JSON.stringify(bindings) + this.queryBuilder.dialect)
|
|
127
|
+
.digest('hex');
|
|
128
|
+
|
|
129
|
+
const keyParts = [
|
|
130
|
+
this.cacheConfig.prefix,
|
|
131
|
+
this.queryBuilder.tableName,
|
|
132
|
+
this.queryBuilder.query.type,
|
|
133
|
+
queryHash
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
return keyParts.join(':');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Execute query with caching
|
|
141
|
+
* @returns {Promise<Object>} Query result
|
|
142
|
+
*/
|
|
143
|
+
async executeWithCache() {
|
|
144
|
+
if (!this.queryBuilder.query.cache || !this.cacheDriver) {
|
|
145
|
+
return this.queryBuilder.execute();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Determine TTL based on query type
|
|
149
|
+
let ttl = this.queryBuilder.query.cacheTtl || this.cacheConfig.defaultTtl;
|
|
150
|
+
if (this.queryBuilder.query.type === 'select') {
|
|
151
|
+
ttl = Math.max(ttl, 600); // At least 10 minutes for SELECT
|
|
152
|
+
} else if (
|
|
153
|
+
this.queryBuilder.query.type === 'insert' ||
|
|
154
|
+
this.queryBuilder.query.type === 'update' ||
|
|
155
|
+
this.queryBuilder.query.type === 'delete'
|
|
156
|
+
) {
|
|
157
|
+
ttl = Math.min(ttl, 30); // Maximum 30 seconds for DML
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const cacheKey = this.queryBuilder.query.cacheKey;
|
|
161
|
+
|
|
162
|
+
// Try to get from cache
|
|
163
|
+
try {
|
|
164
|
+
const cached = await this.cacheDriver.get(cacheKey);
|
|
165
|
+
if (cached !== null && cached !== undefined) {
|
|
166
|
+
this.cacheStats.hits++;
|
|
167
|
+
this.queryBuilder.emit('cache:hit', {
|
|
168
|
+
key: cacheKey,
|
|
169
|
+
ttl: ttl
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Parse cached data
|
|
173
|
+
const result = typeof cached === 'string' ? JSON.parse(cached) : cached;
|
|
174
|
+
|
|
175
|
+
// Add cache metadata
|
|
176
|
+
if (result && typeof result === 'object') {
|
|
177
|
+
result._cache = {
|
|
178
|
+
hit: true,
|
|
179
|
+
key: cacheKey,
|
|
180
|
+
ttl: ttl,
|
|
181
|
+
cachedAt: new Date().toISOString()
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.warn('Cache get error:', error.message);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Cache miss - execute query
|
|
192
|
+
this.cacheStats.misses++;
|
|
193
|
+
const result = await this.queryBuilder.execute();
|
|
194
|
+
|
|
195
|
+
// Store in cache
|
|
196
|
+
try {
|
|
197
|
+
const cacheValue = JSON.stringify(result);
|
|
198
|
+
|
|
199
|
+
if (this.queryBuilder.query.cacheTags?.length > 0 &&
|
|
200
|
+
this.cacheDriver.setWithTags) {
|
|
201
|
+
await this.cacheDriver.setWithTags(
|
|
202
|
+
cacheKey,
|
|
203
|
+
cacheValue,
|
|
204
|
+
ttl,
|
|
205
|
+
this.queryBuilder.query.cacheTags
|
|
206
|
+
);
|
|
207
|
+
} else {
|
|
208
|
+
await this.cacheDriver.set(cacheKey, cacheValue, ttl);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.cacheStats.sets++;
|
|
212
|
+
this.queryBuilder.emit('cache:miss', {
|
|
213
|
+
key: cacheKey,
|
|
214
|
+
ttl: ttl
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Add cache metadata to result
|
|
218
|
+
if (result && typeof result === 'object') {
|
|
219
|
+
result._cache = {
|
|
220
|
+
hit: false,
|
|
221
|
+
key: cacheKey,
|
|
222
|
+
ttl: ttl,
|
|
223
|
+
cachedAt: new Date().toISOString()
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.warn('Cache set error:', error.message);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Remember pattern - get from cache or execute and cache
|
|
235
|
+
* @param {string} key - Cache key
|
|
236
|
+
* @param {number} ttl - Time to live in seconds
|
|
237
|
+
* @param {Function} callback - Function to execute if not cached
|
|
238
|
+
* @returns {Promise<*>} Cached or fresh result
|
|
239
|
+
*/
|
|
240
|
+
async remember(key, ttl, callback) {
|
|
241
|
+
if (!this.cacheEnabled || !this.cacheDriver) {
|
|
242
|
+
return callback();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const cacheKey = `${this.cacheConfig.prefix}${key}`;
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const cached = await this.cacheDriver.get(cacheKey);
|
|
249
|
+
if (cached !== null && cached !== undefined) {
|
|
250
|
+
this.cacheStats.hits++;
|
|
251
|
+
return typeof cached === 'string' ? JSON.parse(cached) : cached;
|
|
252
|
+
}
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.warn('Cache get error:', error.message);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Execute callback and cache result
|
|
258
|
+
const result = await callback();
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
await this.cacheDriver.set(cacheKey, JSON.stringify(result), ttl);
|
|
262
|
+
this.cacheStats.sets++;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.warn('Cache set error:', error.message);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Clear cache for this table
|
|
272
|
+
* @param {string} pattern - Cache key pattern
|
|
273
|
+
* @returns {Promise<void>}
|
|
274
|
+
*/
|
|
275
|
+
async clearTableCache(pattern = null) {
|
|
276
|
+
if (!this.cacheDriver) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const cachePattern = pattern || `query:${this.queryBuilder.tableName}:*`;
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
if (this.cacheDriver.clearPattern) {
|
|
284
|
+
await this.cacheDriver.clearPattern(cachePattern);
|
|
285
|
+
} else if (this.cacheDriver.del) {
|
|
286
|
+
// For Redis-like drivers
|
|
287
|
+
const keys = await this.cacheDriver.keys(cachePattern);
|
|
288
|
+
if (keys.length > 0) {
|
|
289
|
+
await this.cacheDriver.del(...keys);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.cacheStats.deletes++;
|
|
294
|
+
this.queryBuilder.emit('cache:cleared', { pattern: cachePattern });
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error('Failed to clear cache:', error.message);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Clear specific cache key
|
|
302
|
+
* @param {string} key - Cache key to clear
|
|
303
|
+
* @returns {Promise<void>}
|
|
304
|
+
*/
|
|
305
|
+
async clearCache(key = null) {
|
|
306
|
+
if (!this.cacheDriver) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const cacheKey = key || this.queryBuilder.query.cacheKey;
|
|
311
|
+
if (!cacheKey) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
await this.cacheDriver.del(cacheKey);
|
|
317
|
+
this.cacheStats.deletes++;
|
|
318
|
+
this.queryBuilder.emit('cache:cleared', { key: cacheKey });
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error('Failed to clear cache:', error.message);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Clear cache by tags
|
|
326
|
+
* @param {...string} tags - Cache tags
|
|
327
|
+
* @returns {Promise<void>}
|
|
328
|
+
*/
|
|
329
|
+
async clearCacheByTags(...tags) {
|
|
330
|
+
if (!this.cacheDriver || !this.cacheDriver.clearByTags) {
|
|
331
|
+
console.warn('Cache driver does not support tag-based clearing');
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
await this.cacheDriver.clearByTags(...tags);
|
|
337
|
+
this.cacheStats.deletes++;
|
|
338
|
+
this.queryBuilder.emit('cache:cleared:tags', { tags });
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error('Failed to clear cache by tags:', error.message);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Get cache statistics
|
|
346
|
+
* @returns {Object} Cache statistics
|
|
347
|
+
*/
|
|
348
|
+
getCacheStats() {
|
|
349
|
+
return {
|
|
350
|
+
...this.cacheStats,
|
|
351
|
+
enabled: this.cacheEnabled,
|
|
352
|
+
driver: this.cacheDriver?.constructor?.name || 'none',
|
|
353
|
+
config: this.cacheConfig
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Reset cache statistics
|
|
359
|
+
* @returns {Object} Reset statistics
|
|
360
|
+
*/
|
|
361
|
+
resetCacheStats() {
|
|
362
|
+
const oldStats = { ...this.cacheStats };
|
|
363
|
+
this.cacheStats = { hits: 0, misses: 0, sets: 0, deletes: 0 };
|
|
364
|
+
return oldStats;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Validate cache driver interface
|
|
369
|
+
* @private
|
|
370
|
+
*/
|
|
371
|
+
_validateCacheDriver() {
|
|
372
|
+
const requiredMethods = ['get', 'set', 'del'];
|
|
373
|
+
const missingMethods = [];
|
|
374
|
+
|
|
375
|
+
for (const method of requiredMethods) {
|
|
376
|
+
if (typeof this.cacheDriver[method] !== 'function') {
|
|
377
|
+
missingMethods.push(method);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (missingMethods.length > 0) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`Cache driver missing required methods: ${missingMethods.join(', ')}`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Get plugin metadata
|
|
390
|
+
* @returns {Object} Plugin metadata
|
|
391
|
+
*/
|
|
392
|
+
getMetadata() {
|
|
393
|
+
return {
|
|
394
|
+
name: 'CachePlugin',
|
|
395
|
+
version: '1.0.0',
|
|
396
|
+
description: 'Advanced query caching with multiple driver support',
|
|
397
|
+
features: [
|
|
398
|
+
'Multi-driver support (Redis, Memcached, Memory)',
|
|
399
|
+
'Tag-based caching',
|
|
400
|
+
'Automatic TTL management',
|
|
401
|
+
'Cache statistics',
|
|
402
|
+
'Pattern-based cache clearing'
|
|
403
|
+
],
|
|
404
|
+
drivers: ['redis', 'memcached', 'memory', 'custom']
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|