masterrecord 0.3.6 → 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.
package/context.js CHANGED
@@ -33,18 +33,26 @@ class context {
33
33
  isMySQL = false;
34
34
  isPostgres = false;
35
35
 
36
+ // Static shared cache - all context instances share the same cache
37
+ static _sharedQueryCache = null;
38
+
36
39
  constructor(){
37
40
  this. __environment = process.env.master;
38
41
  this.__name = this.constructor.name;
39
42
  this._SQLEngine = "";
40
43
  this.__trackedEntitiesMap = new Map(); // Initialize Map for O(1) lookups
41
44
 
42
- // Initialize query cache
43
- this._queryCache = new QueryCache({
44
- ttl: process.env.QUERY_CACHE_TTL || 5 * 60 * 1000, // 5 min default
45
- maxSize: process.env.QUERY_CACHE_SIZE || 1000,
46
- enabled: process.env.QUERY_CACHE_ENABLED !== 'false'
47
- });
45
+ // Initialize shared query cache (only once across all instances)
46
+ if (!context._sharedQueryCache) {
47
+ context._sharedQueryCache = new QueryCache({
48
+ ttl: process.env.QUERY_CACHE_TTL || 5 * 60 * 1000, // 5 min default
49
+ maxSize: process.env.QUERY_CACHE_SIZE || 1000,
50
+ enabled: process.env.QUERY_CACHE_ENABLED !== 'false'
51
+ });
52
+ }
53
+
54
+ // Reference the shared cache
55
+ this._queryCache = context._sharedQueryCache;
48
56
  }
49
57
 
50
58
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "masterrecord",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "An Object-relational mapping for the Master framework. Master Record connects classes to relational database tables to establish a database with almost zero-configuration ",
5
5
  "main": "MasterRecord.js",
6
6
  "bin": {
package/readme.md CHANGED
@@ -787,7 +787,7 @@ MasterRecord includes a **production-grade two-level caching system** similar to
787
787
 
788
788
  #### Basic Usage (Default Behavior)
789
789
 
790
- Caching is **enabled by default** and requires zero configuration:
790
+ Caching is **enabled by default** and requires zero configuration. The cache is **shared across all context instances** to ensure consistency:
791
791
 
792
792
  ```javascript
793
793
  const db = new AppContext();
@@ -804,6 +804,10 @@ db.saveChanges(); // Cache for User table cleared
804
804
 
805
805
  // Next query hits database again (cache miss)
806
806
  const user3 = db.User.where(u => u.id == $$, 1).single();
807
+
808
+ // Cache is shared across all context instances
809
+ const db2 = new AppContext();
810
+ const user4 = db2.User.findById(1); // Also uses shared cache
807
811
  ```
808
812
 
809
813
  #### Configuration
@@ -988,6 +992,35 @@ if (parseFloat(stats.hitRate) < 50) {
988
992
  }
989
993
  ```
990
994
 
995
+ #### Important: Shared Cache Behavior
996
+
997
+ **The cache is shared across all context instances of the same class.** This ensures consistency:
998
+
999
+ ```javascript
1000
+ const db1 = new AppContext();
1001
+ const db2 = new AppContext();
1002
+
1003
+ // Context 1: Cache data
1004
+ const user1 = db1.User.findById(1); // DB query, cached
1005
+
1006
+ // Context 2: Sees cached data
1007
+ const user2 = db2.User.findById(1); // Cache hit!
1008
+
1009
+ // Context 2: Updates invalidate cache for BOTH contexts
1010
+ user2.name = "Updated";
1011
+ db2.saveChanges(); // Invalidates shared cache
1012
+
1013
+ // Context 1: Sees fresh data
1014
+ const user3 = db1.User.findById(1); // Cache miss, fresh data
1015
+ console.log(user3.name); // "Updated"
1016
+ ```
1017
+
1018
+ **Why shared cache?**
1019
+ - ✅ Prevents stale data across multiple context instances
1020
+ - ✅ Ensures all parts of your application see consistent data
1021
+ - ✅ Reduces memory usage (one cache instead of many)
1022
+ - ✅ Correct behavior for single-database applications (most use cases)
1023
+
991
1024
  ### Multi-Context Applications
992
1025
 
993
1026
  Manage multiple databases in one application:
@@ -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
+ }