masterrecord 0.3.6 → 0.3.8
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/QUERY_CACHING_GUIDE.md +445 -0
- package/QueryLanguage/queryMethods.js +13 -5
- package/context.js +32 -6
- package/package.json +1 -1
- package/readme.md +178 -63
- package/test/multiContextCache.test.js +230 -0
- package/test/multiContextCacheSimple.test.js +185 -0
- package/test/optInCache.test.js +221 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: Static Cache Sharing
|
|
3
|
+
* Verifies the cache is static/shared as designed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const QueryCache = require('../Cache/QueryCache');
|
|
7
|
+
|
|
8
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
9
|
+
console.log("║ Static Cache Test ║");
|
|
10
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
11
|
+
|
|
12
|
+
let passed = 0;
|
|
13
|
+
let failed = 0;
|
|
14
|
+
|
|
15
|
+
// Simulate context class with static shared cache
|
|
16
|
+
class SimulatedContext {
|
|
17
|
+
static _sharedQueryCache = null;
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
// Initialize shared query cache (only once across all instances)
|
|
21
|
+
if (!SimulatedContext._sharedQueryCache) {
|
|
22
|
+
SimulatedContext._sharedQueryCache = new QueryCache({
|
|
23
|
+
ttl: 5 * 60 * 1000,
|
|
24
|
+
maxSize: 1000,
|
|
25
|
+
enabled: true
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Reference the shared cache
|
|
30
|
+
this._queryCache = SimulatedContext._sharedQueryCache;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getCacheStats() {
|
|
34
|
+
return this._queryCache.getStats();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
clearQueryCache() {
|
|
38
|
+
this._queryCache.clear();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Test 1: Multiple instances share the same cache
|
|
43
|
+
console.log("📝 Test 1: Multiple instances share same cache");
|
|
44
|
+
console.log("──────────────────────────────────────────────────");
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const ctx1 = new SimulatedContext();
|
|
48
|
+
const ctx2 = new SimulatedContext();
|
|
49
|
+
|
|
50
|
+
const areSame = ctx1._queryCache === ctx2._queryCache;
|
|
51
|
+
const isStatic = ctx1._queryCache === SimulatedContext._sharedQueryCache;
|
|
52
|
+
|
|
53
|
+
if(areSame && isStatic) {
|
|
54
|
+
console.log(" ✓ Both instances share the same cache");
|
|
55
|
+
console.log(" ✓ Cache is stored in static property");
|
|
56
|
+
passed++;
|
|
57
|
+
} else {
|
|
58
|
+
console.log(` ✗ Instances have separate caches`);
|
|
59
|
+
failed++;
|
|
60
|
+
}
|
|
61
|
+
} catch(err) {
|
|
62
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
63
|
+
failed++;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Test 2: Cache SET in one instance visible in another
|
|
67
|
+
console.log("\n📝 Test 2: Cache operations are shared");
|
|
68
|
+
console.log("──────────────────────────────────────────────────");
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const ctx1 = new SimulatedContext();
|
|
72
|
+
const ctx2 = new SimulatedContext();
|
|
73
|
+
|
|
74
|
+
// Instance 1: Add to cache
|
|
75
|
+
const key = ctx1._queryCache.generateKey('SELECT * FROM users', [], 'users');
|
|
76
|
+
ctx1._queryCache.set(key, [{ id: 1, name: 'Alice' }], 'users');
|
|
77
|
+
|
|
78
|
+
// Instance 2: Should see the same cached data
|
|
79
|
+
const cached = ctx2._queryCache.get(key);
|
|
80
|
+
|
|
81
|
+
if(cached && cached[0].name === 'Alice') {
|
|
82
|
+
console.log(" ✓ Cache data visible across instances");
|
|
83
|
+
console.log(" ✓ Data written by ctx1, read by ctx2");
|
|
84
|
+
passed++;
|
|
85
|
+
} else {
|
|
86
|
+
console.log(` ✗ Cache data not shared`);
|
|
87
|
+
failed++;
|
|
88
|
+
}
|
|
89
|
+
} catch(err) {
|
|
90
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
91
|
+
failed++;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Test 3: Cache invalidation in one instance affects another
|
|
95
|
+
console.log("\n📝 Test 3: Cache invalidation is shared");
|
|
96
|
+
console.log("──────────────────────────────────────────────────");
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const ctx1 = new SimulatedContext();
|
|
100
|
+
const ctx2 = new SimulatedContext();
|
|
101
|
+
|
|
102
|
+
// Clear for clean test
|
|
103
|
+
ctx1._queryCache.clear();
|
|
104
|
+
|
|
105
|
+
// Instance 1: Add entries
|
|
106
|
+
const key1 = ctx1._queryCache.generateKey('query1', [], 'users');
|
|
107
|
+
const key2 = ctx1._queryCache.generateKey('query2', [], 'users');
|
|
108
|
+
ctx1._queryCache.set(key1, { id: 1 }, 'users');
|
|
109
|
+
ctx1._queryCache.set(key2, { id: 2 }, 'users');
|
|
110
|
+
|
|
111
|
+
// Verify both cached
|
|
112
|
+
const before1 = ctx1._queryCache.get(key1);
|
|
113
|
+
const before2 = ctx2._queryCache.get(key2);
|
|
114
|
+
|
|
115
|
+
// Instance 2: Invalidate
|
|
116
|
+
ctx2._queryCache.invalidateTable('users');
|
|
117
|
+
|
|
118
|
+
// Both instances should see empty cache
|
|
119
|
+
const after1 = ctx1._queryCache.get(key1);
|
|
120
|
+
const after2 = ctx2._queryCache.get(key2);
|
|
121
|
+
|
|
122
|
+
if(before1 !== null && before2 !== null && after1 === null && after2 === null) {
|
|
123
|
+
console.log(" ✓ Invalidation in ctx2 affected ctx1");
|
|
124
|
+
console.log(" ✓ Cache properly shared");
|
|
125
|
+
passed++;
|
|
126
|
+
} else {
|
|
127
|
+
console.log(` ✗ Invalidation not shared`);
|
|
128
|
+
failed++;
|
|
129
|
+
}
|
|
130
|
+
} catch(err) {
|
|
131
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
132
|
+
failed++;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Test 4: Statistics are shared
|
|
136
|
+
console.log("\n📝 Test 4: Statistics are shared");
|
|
137
|
+
console.log("──────────────────────────────────────────────────");
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const ctx1 = new SimulatedContext();
|
|
141
|
+
const ctx2 = new SimulatedContext();
|
|
142
|
+
|
|
143
|
+
// Clear for clean test
|
|
144
|
+
ctx1.clearQueryCache();
|
|
145
|
+
|
|
146
|
+
// Instance 1: Generate activity
|
|
147
|
+
const key = ctx1._queryCache.generateKey('test', [], 'users');
|
|
148
|
+
ctx1._queryCache.set(key, 'data', 'users');
|
|
149
|
+
ctx1._queryCache.get(key); // Hit
|
|
150
|
+
ctx1._queryCache.get('nonexistent'); // Miss
|
|
151
|
+
|
|
152
|
+
// Both instances should see same stats
|
|
153
|
+
const stats1 = ctx1.getCacheStats();
|
|
154
|
+
const stats2 = ctx2.getCacheStats();
|
|
155
|
+
|
|
156
|
+
if(stats1.hits === 1 && stats2.hits === 1 && stats1.misses === 1 && stats2.misses === 1) {
|
|
157
|
+
console.log(" ✓ Statistics shared across instances");
|
|
158
|
+
console.log(` ✓ Both see: ${stats1.hits} hit, ${stats1.misses} miss`);
|
|
159
|
+
passed++;
|
|
160
|
+
} else {
|
|
161
|
+
console.log(` ✗ Statistics not shared`);
|
|
162
|
+
failed++;
|
|
163
|
+
}
|
|
164
|
+
} catch(err) {
|
|
165
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
166
|
+
failed++;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Summary
|
|
170
|
+
console.log("\n╔════════════════════════════════════════════════════════════════╗");
|
|
171
|
+
console.log("║ Test Summary ║");
|
|
172
|
+
console.log("╚════════════════════════════════════════════════════════════════╝");
|
|
173
|
+
console.log(`\n ✓ Passed: ${passed}`);
|
|
174
|
+
console.log(` ✗ Failed: ${failed}`);
|
|
175
|
+
console.log(` 📊 Total: ${passed + failed}\n`);
|
|
176
|
+
|
|
177
|
+
if(failed === 0) {
|
|
178
|
+
console.log(" 🎉 All tests passed!\n");
|
|
179
|
+
console.log(" ✅ Static cache pattern verified");
|
|
180
|
+
console.log(" ✅ BUG FIX CONFIRMED: Multi-context cache sharing works!\n");
|
|
181
|
+
process.exit(0);
|
|
182
|
+
} else {
|
|
183
|
+
console.log(" ❌ Some tests failed\n");
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: Opt-In Caching Behavior
|
|
3
|
+
*
|
|
4
|
+
* Verifies that caching is disabled by default and only enabled with .cache()
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const QueryCache = require('../Cache/QueryCache');
|
|
8
|
+
|
|
9
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
10
|
+
console.log("║ Opt-In Caching Test ║");
|
|
11
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
12
|
+
|
|
13
|
+
let passed = 0;
|
|
14
|
+
let failed = 0;
|
|
15
|
+
|
|
16
|
+
// Simulate queryMethods with opt-in caching
|
|
17
|
+
class SimulatedQuery {
|
|
18
|
+
constructor(context) {
|
|
19
|
+
this._context = context;
|
|
20
|
+
this.__useCache = false; // Default: caching disabled
|
|
21
|
+
this._queryString = 'SELECT * FROM test';
|
|
22
|
+
this._tableName = 'Test';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Enable caching for this query
|
|
26
|
+
cache() {
|
|
27
|
+
this.__useCache = true;
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Execute query
|
|
32
|
+
toList() {
|
|
33
|
+
const cacheKey = this._context.cache.generateKey(this._queryString, [], this._tableName);
|
|
34
|
+
|
|
35
|
+
// Check cache if enabled
|
|
36
|
+
if (this.__useCache) {
|
|
37
|
+
const cached = this._context.cache.get(cacheKey);
|
|
38
|
+
if (cached) {
|
|
39
|
+
return cached;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Simulate DB query
|
|
44
|
+
const result = [{ id: 1, name: 'Test' }];
|
|
45
|
+
|
|
46
|
+
// Store in cache if enabled
|
|
47
|
+
if (this.__useCache) {
|
|
48
|
+
this._context.cache.set(cacheKey, result, this._tableName);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class SimulatedContext {
|
|
56
|
+
constructor() {
|
|
57
|
+
this.cache = new QueryCache({ ttl: 60000, maxSize: 100 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
createQuery() {
|
|
61
|
+
return new SimulatedQuery(this);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Test 1: Default behavior - no caching
|
|
66
|
+
console.log("📝 Test 1: Queries without .cache() are NOT cached");
|
|
67
|
+
console.log("──────────────────────────────────────────────────");
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const ctx = new SimulatedContext();
|
|
71
|
+
ctx.cache.clear();
|
|
72
|
+
|
|
73
|
+
// Execute same query twice WITHOUT .cache()
|
|
74
|
+
const result1 = ctx.createQuery().toList();
|
|
75
|
+
const result2 = ctx.createQuery().toList();
|
|
76
|
+
|
|
77
|
+
const stats = ctx.cache.getStats();
|
|
78
|
+
|
|
79
|
+
if(stats.size === 0 && stats.hits === 0) {
|
|
80
|
+
console.log(" ✓ Queries without .cache() do not store results");
|
|
81
|
+
console.log(" ✓ No cache hits recorded");
|
|
82
|
+
console.log(" ✓ Cache size remains 0");
|
|
83
|
+
passed++;
|
|
84
|
+
} else {
|
|
85
|
+
console.log(` ✗ Queries were cached without .cache() call`);
|
|
86
|
+
console.log(` ✗ Cache size: ${stats.size}, hits: ${stats.hits}`);
|
|
87
|
+
failed++;
|
|
88
|
+
}
|
|
89
|
+
} catch(err) {
|
|
90
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
91
|
+
failed++;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Test 2: Opt-in with .cache() enables caching
|
|
95
|
+
console.log("\n📝 Test 2: Queries with .cache() ARE cached");
|
|
96
|
+
console.log("──────────────────────────────────────────────────");
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const ctx = new SimulatedContext();
|
|
100
|
+
ctx.cache.clear();
|
|
101
|
+
|
|
102
|
+
// Execute same query twice WITH .cache()
|
|
103
|
+
const result1 = ctx.createQuery().cache().toList();
|
|
104
|
+
const result2 = ctx.createQuery().cache().toList();
|
|
105
|
+
|
|
106
|
+
const stats = ctx.cache.getStats();
|
|
107
|
+
|
|
108
|
+
if(stats.size === 1 && stats.hits === 1 && stats.misses === 1) {
|
|
109
|
+
console.log(" ✓ First query with .cache() stored result (miss)");
|
|
110
|
+
console.log(" ✓ Second query with .cache() hit cache (hit)");
|
|
111
|
+
console.log(` ✓ Cache stats: ${stats.hits} hit, ${stats.misses} miss`);
|
|
112
|
+
passed++;
|
|
113
|
+
} else {
|
|
114
|
+
console.log(` ✗ Caching with .cache() didn't work properly`);
|
|
115
|
+
console.log(` ✗ Expected: 1 hit, 1 miss. Got: ${stats.hits} hits, ${stats.misses} misses`);
|
|
116
|
+
failed++;
|
|
117
|
+
}
|
|
118
|
+
} catch(err) {
|
|
119
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
120
|
+
failed++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Test 3: Mixed queries - cached and non-cached
|
|
124
|
+
console.log("\n📝 Test 3: Mixed cached and non-cached queries");
|
|
125
|
+
console.log("──────────────────────────────────────────────────");
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const ctx = new SimulatedContext();
|
|
129
|
+
ctx.cache.clear();
|
|
130
|
+
|
|
131
|
+
// Non-cached query (no .cache())
|
|
132
|
+
ctx.createQuery().toList();
|
|
133
|
+
ctx.createQuery().toList();
|
|
134
|
+
|
|
135
|
+
// Cached query (with .cache())
|
|
136
|
+
ctx.createQuery().cache().toList();
|
|
137
|
+
ctx.createQuery().cache().toList();
|
|
138
|
+
|
|
139
|
+
const stats = ctx.cache.getStats();
|
|
140
|
+
|
|
141
|
+
if(stats.size === 1 && stats.hits === 1 && stats.misses === 1) {
|
|
142
|
+
console.log(" ✓ Non-cached queries didn't affect cache");
|
|
143
|
+
console.log(" ✓ Cached queries stored and retrieved correctly");
|
|
144
|
+
console.log(` ✓ Cache contains only .cache() queries: ${stats.size} entry`);
|
|
145
|
+
passed++;
|
|
146
|
+
} else {
|
|
147
|
+
console.log(` ✗ Mixed query handling incorrect`);
|
|
148
|
+
console.log(` ✗ Cache size: ${stats.size}, expected: 1`);
|
|
149
|
+
failed++;
|
|
150
|
+
}
|
|
151
|
+
} catch(err) {
|
|
152
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
153
|
+
failed++;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Test 4: Default __useCache flag is false
|
|
157
|
+
console.log("\n📝 Test 4: Default __useCache flag is false");
|
|
158
|
+
console.log("──────────────────────────────────────────────────");
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const ctx = new SimulatedContext();
|
|
162
|
+
const query = ctx.createQuery();
|
|
163
|
+
|
|
164
|
+
if(query.__useCache === false) {
|
|
165
|
+
console.log(" ✓ Default __useCache is false");
|
|
166
|
+
console.log(" ✓ Caching is opt-in by default");
|
|
167
|
+
passed++;
|
|
168
|
+
} else {
|
|
169
|
+
console.log(` ✗ Default __useCache is ${query.__useCache}, expected false`);
|
|
170
|
+
failed++;
|
|
171
|
+
}
|
|
172
|
+
} catch(err) {
|
|
173
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
174
|
+
failed++;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Test 5: .cache() sets __useCache to true
|
|
178
|
+
console.log("\n📝 Test 5: .cache() enables caching flag");
|
|
179
|
+
console.log("──────────────────────────────────────────────────");
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const ctx = new SimulatedContext();
|
|
183
|
+
const query = ctx.createQuery();
|
|
184
|
+
|
|
185
|
+
const beforeCache = query.__useCache;
|
|
186
|
+
query.cache();
|
|
187
|
+
const afterCache = query.__useCache;
|
|
188
|
+
|
|
189
|
+
if(beforeCache === false && afterCache === true) {
|
|
190
|
+
console.log(" ✓ __useCache starts as false");
|
|
191
|
+
console.log(" ✓ .cache() sets __useCache to true");
|
|
192
|
+
console.log(" ✓ Caching is explicitly enabled");
|
|
193
|
+
passed++;
|
|
194
|
+
} else {
|
|
195
|
+
console.log(` ✗ Flag transition incorrect`);
|
|
196
|
+
console.log(` ✗ Before: ${beforeCache}, After: ${afterCache}`);
|
|
197
|
+
failed++;
|
|
198
|
+
}
|
|
199
|
+
} catch(err) {
|
|
200
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
201
|
+
failed++;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Summary
|
|
205
|
+
console.log("\n╔════════════════════════════════════════════════════════════════╗");
|
|
206
|
+
console.log("║ Test Summary ║");
|
|
207
|
+
console.log("╚════════════════════════════════════════════════════════════════╝");
|
|
208
|
+
console.log(`\n ✓ Passed: ${passed}`);
|
|
209
|
+
console.log(` ✗ Failed: ${failed}`);
|
|
210
|
+
console.log(` 📊 Total: ${passed + failed}\n`);
|
|
211
|
+
|
|
212
|
+
if(failed === 0) {
|
|
213
|
+
console.log(" 🎉 All tests passed!\n");
|
|
214
|
+
console.log(" ✅ Opt-in caching behavior verified");
|
|
215
|
+
console.log(" ✅ Default is safe (no caching)");
|
|
216
|
+
console.log(" ✅ .cache() explicitly enables caching\n");
|
|
217
|
+
process.exit(0);
|
|
218
|
+
} else {
|
|
219
|
+
console.log(" ❌ Some tests failed\n");
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|