masterrecord 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Test: Multi-Context Cache Sharing
3
+ *
4
+ * Verifies that cache is shared across context instances
5
+ * so invalidation in one context affects all contexts
6
+ */
7
+
8
+ const context = require('../context');
9
+ const QueryCache = require('../Cache/QueryCache');
10
+
11
+ console.log("╔════════════════════════════════════════════════════════════════╗");
12
+ console.log("║ Multi-Context Cache Sharing Test ║");
13
+ console.log("╚════════════════════════════════════════════════════════════════╝\n");
14
+
15
+ let passed = 0;
16
+ let failed = 0;
17
+
18
+ // Test 1: Multiple context instances share the same cache
19
+ console.log("📝 Test 1: Multiple contexts share same cache instance");
20
+ console.log("──────────────────────────────────────────────────");
21
+
22
+ try {
23
+ class TestContext extends context {
24
+ constructor() {
25
+ super();
26
+ }
27
+ }
28
+
29
+ const db1 = new TestContext();
30
+ const db2 = new TestContext();
31
+
32
+ const areSameInstance = db1._queryCache === db2._queryCache;
33
+ const isSharedCache = db1._queryCache === context._sharedQueryCache;
34
+
35
+ if(areSameInstance && isSharedCache) {
36
+ console.log(" ✓ Both context instances share the same cache");
37
+ console.log(" ✓ Cache is stored in static property");
38
+ passed++;
39
+ } else {
40
+ console.log(` ✗ Contexts have separate caches (BUG!)`);
41
+ console.log(` ✗ db1._queryCache === db2._queryCache: ${areSameInstance}`);
42
+ console.log(` ✗ Shared cache exists: ${isSharedCache}`);
43
+ failed++;
44
+ }
45
+ } catch(err) {
46
+ console.log(` ✗ Error: ${err.message}`);
47
+ failed++;
48
+ }
49
+
50
+ // Test 2: Cache operations in one context affect another context
51
+ console.log("\n📝 Test 2: Cache operations are shared");
52
+ console.log("──────────────────────────────────────────────────");
53
+
54
+ try {
55
+ class TestContext extends context {
56
+ constructor() {
57
+ super();
58
+ }
59
+ }
60
+
61
+ const db1 = new TestContext();
62
+ const db2 = new TestContext();
63
+
64
+ // Context 1: Add to cache
65
+ const key = db1._queryCache.generateKey('SELECT * FROM users', [], 'users');
66
+ db1._queryCache.set(key, [{ id: 1, name: 'John' }], 'users');
67
+
68
+ // Context 2: Should see the same cached data
69
+ const cached = db2._queryCache.get(key);
70
+
71
+ if(cached && cached[0].name === 'John') {
72
+ console.log(" ✓ Cache data visible across contexts");
73
+ passed++;
74
+ } else {
75
+ console.log(` ✗ Cache data not shared across contexts`);
76
+ failed++;
77
+ }
78
+ } catch(err) {
79
+ console.log(` ✗ Error: ${err.message}`);
80
+ failed++;
81
+ }
82
+
83
+ // Test 3: Cache invalidation in one context affects another
84
+ console.log("\n📝 Test 3: Cache invalidation is shared");
85
+ console.log("──────────────────────────────────────────────────");
86
+
87
+ try {
88
+ class TestContext extends context {
89
+ constructor() {
90
+ super();
91
+ }
92
+ }
93
+
94
+ const db1 = new TestContext();
95
+ const db2 = new TestContext();
96
+
97
+ // Context 1: Add multiple entries to cache
98
+ const key1 = db1._queryCache.generateKey('SELECT * FROM users WHERE id=1', [], 'users');
99
+ const key2 = db1._queryCache.generateKey('SELECT * FROM users WHERE id=2', [], 'users');
100
+ db1._queryCache.set(key1, { id: 1, name: 'John' }, 'users');
101
+ db1._queryCache.set(key2, { id: 2, name: 'Jane' }, 'users');
102
+
103
+ // Verify both are cached
104
+ const beforeCached1 = db1._queryCache.get(key1);
105
+ const beforeCached2 = db1._queryCache.get(key2);
106
+
107
+ // Context 2: Invalidate User table
108
+ db2._queryCache.invalidateTable('users');
109
+
110
+ // Context 1: Should see invalidation
111
+ const afterCached1 = db1._queryCache.get(key1);
112
+ const afterCached2 = db1._queryCache.get(key2);
113
+
114
+ if(beforeCached1 !== null && beforeCached2 !== null && afterCached1 === null && afterCached2 === null) {
115
+ console.log(" ✓ Data was cached in context 1");
116
+ console.log(" ✓ Invalidation in context 2 affected context 1");
117
+ console.log(" ✓ Cache properly shared across contexts");
118
+ passed++;
119
+ } else {
120
+ console.log(` ✗ Invalidation not shared properly`);
121
+ console.log(` ✗ Before: cached1=${beforeCached1 !== null}, cached2=${beforeCached2 !== null}`);
122
+ console.log(` ✗ After: cached1=${afterCached1 !== null}, cached2=${afterCached2 !== null}`);
123
+ failed++;
124
+ }
125
+ } catch(err) {
126
+ console.log(` ✗ Error: ${err.message}`);
127
+ failed++;
128
+ }
129
+
130
+ // Test 4: Cache statistics are shared
131
+ console.log("\n📝 Test 4: Cache statistics are shared");
132
+ console.log("──────────────────────────────────────────────────");
133
+
134
+ try {
135
+ // Clear existing cache for clean test
136
+ context._sharedQueryCache.clear();
137
+
138
+ class TestContext extends context {
139
+ constructor() {
140
+ super();
141
+ }
142
+ }
143
+
144
+ const db1 = new TestContext();
145
+ const db2 = new TestContext();
146
+
147
+ // Context 1: Generate hits/misses
148
+ const key = db1._queryCache.generateKey('query', [], 'users');
149
+ db1._queryCache.set(key, 'data', 'users');
150
+ db1._queryCache.get(key); // Hit
151
+ db1._queryCache.get('nonexistent'); // Miss
152
+
153
+ // Context 2: Should see same stats
154
+ const stats1 = db1.getCacheStats();
155
+ const stats2 = db2.getCacheStats();
156
+
157
+ if(stats1.hits === 1 && stats2.hits === 1 && stats1.misses === 1 && stats2.misses === 1) {
158
+ console.log(" ✓ Cache statistics shared across contexts");
159
+ console.log(` ✓ Both contexts see: ${stats1.hits} hit, ${stats1.misses} miss`);
160
+ passed++;
161
+ } else {
162
+ console.log(` ✗ Statistics not shared`);
163
+ console.log(` ✗ db1 stats: ${JSON.stringify(stats1)}`);
164
+ console.log(` ✗ db2 stats: ${JSON.stringify(stats2)}`);
165
+ failed++;
166
+ }
167
+ } catch(err) {
168
+ console.log(` ✗ Error: ${err.message}`);
169
+ failed++;
170
+ }
171
+
172
+ // Test 5: Clear cache from one context affects all
173
+ console.log("\n📝 Test 5: Clear cache affects all contexts");
174
+ console.log("──────────────────────────────────────────────────");
175
+
176
+ try {
177
+ class TestContext extends context {
178
+ constructor() {
179
+ super();
180
+ }
181
+ }
182
+
183
+ const db1 = new TestContext();
184
+ const db2 = new TestContext();
185
+
186
+ // Context 1: Add data
187
+ const key = db1._queryCache.generateKey('query', [], 'users');
188
+ db1._queryCache.set(key, 'data', 'users');
189
+
190
+ // Verify cached in both
191
+ const before1 = db1._queryCache.get(key);
192
+ const before2 = db2._queryCache.get(key);
193
+
194
+ // Context 2: Clear cache
195
+ db2.clearQueryCache();
196
+
197
+ // Both contexts should see empty cache
198
+ const after1 = db1._queryCache.get(key);
199
+ const after2 = db2._queryCache.get(key);
200
+
201
+ if(before1 !== null && before2 !== null && after1 === null && after2 === null) {
202
+ console.log(" ✓ Data cached in both contexts initially");
203
+ console.log(" ✓ Clear from context 2 affected context 1");
204
+ passed++;
205
+ } else {
206
+ console.log(` ✗ Clear not shared across contexts`);
207
+ failed++;
208
+ }
209
+ } catch(err) {
210
+ console.log(` ✗ Error: ${err.message}`);
211
+ failed++;
212
+ }
213
+
214
+ // Summary
215
+ console.log("\n╔════════════════════════════════════════════════════════════════╗");
216
+ console.log("║ Test Summary ║");
217
+ console.log("╚════════════════════════════════════════════════════════════════╝");
218
+ console.log(`\n ✓ Passed: ${passed}`);
219
+ console.log(` ✗ Failed: ${failed}`);
220
+ console.log(` 📊 Total: ${passed + failed}\n`);
221
+
222
+ if(failed === 0) {
223
+ console.log(" 🎉 All tests passed!\n");
224
+ console.log(" ✅ Cache is properly shared across context instances");
225
+ console.log(" ✅ Bug fix verified: Multi-context cache invalidation works\n");
226
+ process.exit(0);
227
+ } else {
228
+ console.log(" ❌ Some tests failed\n");
229
+ process.exit(1);
230
+ }
@@ -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,148 @@
1
+ const assert = require('assert');
2
+ const QueryCache = require('../Cache/QueryCache');
3
+
4
+ describe('QueryCache', () => {
5
+ let cache;
6
+
7
+ beforeEach(() => {
8
+ cache = new QueryCache({ ttl: 1000, maxSize: 3 });
9
+ });
10
+
11
+ test('cache miss returns null', () => {
12
+ const key = cache.generateKey('SELECT * FROM users', [], 'users');
13
+ assert.strictEqual(cache.get(key), null);
14
+ });
15
+
16
+ test('cache hit returns stored data', () => {
17
+ const key = cache.generateKey('SELECT * FROM users', [], 'users');
18
+ const data = [{ id: 1, name: 'John' }];
19
+
20
+ cache.set(key, data, 'users');
21
+ const cached = cache.get(key);
22
+
23
+ assert.deepStrictEqual(cached, data);
24
+ });
25
+
26
+ test('TTL expiration removes entries', (done) => {
27
+ const key = cache.generateKey('SELECT * FROM users', [], 'users');
28
+ cache.set(key, { id: 1 }, 'users');
29
+
30
+ setTimeout(() => {
31
+ assert.strictEqual(cache.get(key), null);
32
+ done();
33
+ }, 1100); // Wait for TTL expiration
34
+ });
35
+
36
+ test('LRU eviction removes oldest entry', () => {
37
+ cache.set('key1', 'data1', 'table1');
38
+ cache.set('key2', 'data2', 'table2');
39
+ cache.set('key3', 'data3', 'table3');
40
+
41
+ // Access key1 to make it recently used
42
+ cache.get('key1');
43
+
44
+ // Add key4 - should evict key2 (least recently used)
45
+ cache.set('key4', 'data4', 'table4');
46
+
47
+ assert.strictEqual(cache.get('key1'), 'data1'); // Still in cache
48
+ assert.strictEqual(cache.get('key2'), null); // Evicted
49
+ assert.strictEqual(cache.get('key3'), 'data3'); // Still in cache
50
+ assert.strictEqual(cache.get('key4'), 'data4'); // Newly added
51
+ });
52
+
53
+ test('invalidateTable removes all entries for table', () => {
54
+ cache.set('key1', 'data1', 'users');
55
+ cache.set('key2', 'data2', 'users');
56
+ cache.set('key3', 'data3', 'posts');
57
+
58
+ cache.invalidateTable('users');
59
+
60
+ assert.strictEqual(cache.get('key1'), null);
61
+ assert.strictEqual(cache.get('key2'), null);
62
+ assert.strictEqual(cache.get('key3'), 'data3'); // Different table
63
+ });
64
+
65
+ test('getStats returns accurate metrics', () => {
66
+ const key1 = cache.generateKey('query1', [], 'users');
67
+ const key2 = cache.generateKey('query2', [], 'posts');
68
+
69
+ cache.set(key1, 'data1', 'users');
70
+ cache.get(key1); // Hit
71
+ cache.get(key2); // Miss
72
+
73
+ const stats = cache.getStats();
74
+ assert.strictEqual(stats.size, 1);
75
+ assert.strictEqual(stats.hits, 1);
76
+ assert.strictEqual(stats.misses, 1);
77
+ assert.strictEqual(stats.hitRate, '50.00%');
78
+ });
79
+
80
+ test('clear removes all cache entries', () => {
81
+ const key1 = cache.generateKey('query1', [], 'users');
82
+ const key2 = cache.generateKey('query2', [], 'posts');
83
+
84
+ cache.set(key1, 'data1', 'users');
85
+ cache.set(key2, 'data2', 'posts');
86
+
87
+ assert.strictEqual(cache.cache.size, 2);
88
+
89
+ cache.clear();
90
+
91
+ assert.strictEqual(cache.cache.size, 0);
92
+ assert.strictEqual(cache.hitCount, 0);
93
+ assert.strictEqual(cache.missCount, 0);
94
+ });
95
+
96
+ test('disabled cache does not store or retrieve data', () => {
97
+ cache.enabled = false;
98
+
99
+ const key = cache.generateKey('SELECT * FROM users', [], 'users');
100
+ cache.set(key, 'data', 'users');
101
+
102
+ assert.strictEqual(cache.get(key), null);
103
+ assert.strictEqual(cache.cache.size, 0);
104
+ });
105
+
106
+ test('generateKey creates consistent keys for same query', () => {
107
+ const key1 = cache.generateKey('SELECT * FROM users WHERE id = ?', [1], 'users');
108
+ const key2 = cache.generateKey('SELECT * FROM users WHERE id = ?', [1], 'users');
109
+ const key3 = cache.generateKey('SELECT * FROM users WHERE id = ?', [2], 'users');
110
+
111
+ assert.strictEqual(key1, key2); // Same query + params = same key
112
+ assert.notStrictEqual(key1, key3); // Different params = different key
113
+ });
114
+
115
+ test('generateKey normalizes whitespace', () => {
116
+ const key1 = cache.generateKey('SELECT * FROM users', [], 'users');
117
+ const key2 = cache.generateKey('SELECT * FROM users', [], 'users');
118
+
119
+ assert.strictEqual(key1, key2); // Whitespace normalized
120
+ });
121
+
122
+ test('cache updates lastAccess on hit', () => {
123
+ const key = cache.generateKey('query', [], 'users');
124
+ cache.set(key, 'data', 'users');
125
+
126
+ const entry1 = cache.cache.get(key);
127
+ const originalAccess = entry1.lastAccess;
128
+
129
+ // Wait a bit
130
+ setTimeout(() => {
131
+ cache.get(key);
132
+ const entry2 = cache.cache.get(key);
133
+ assert(entry2.lastAccess > originalAccess);
134
+ }, 10);
135
+ });
136
+
137
+ test('cache tracks hit count per entry', () => {
138
+ const key = cache.generateKey('query', [], 'users');
139
+ cache.set(key, 'data', 'users');
140
+
141
+ cache.get(key);
142
+ cache.get(key);
143
+ cache.get(key);
144
+
145
+ const entry = cache.cache.get(key);
146
+ assert.strictEqual(entry.hits, 3);
147
+ });
148
+ });