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
package/readme.md
CHANGED
|
@@ -785,25 +785,51 @@ MasterRecord includes a **production-grade two-level caching system** similar to
|
|
|
785
785
|
└─────────────────────────────────────────────────────┘
|
|
786
786
|
```
|
|
787
787
|
|
|
788
|
-
#### Basic Usage (
|
|
788
|
+
#### Basic Usage (Opt-In, Request-Scoped)
|
|
789
789
|
|
|
790
|
-
Caching is **
|
|
790
|
+
Caching is **opt-in** and **request-scoped** like Active Record. Use `.cache()` to enable caching, and call `endRequest()` to clear:
|
|
791
791
|
|
|
792
792
|
```javascript
|
|
793
793
|
const db = new AppContext();
|
|
794
794
|
|
|
795
|
-
//
|
|
796
|
-
const user = db.User.
|
|
795
|
+
// DEFAULT: No caching (always hits database)
|
|
796
|
+
const user = db.User.findById(1); // DB query
|
|
797
|
+
const user2 = db.User.findById(1); // DB query again (no cache)
|
|
797
798
|
|
|
798
|
-
//
|
|
799
|
-
const
|
|
799
|
+
// OPT-IN: Enable caching with .cache()
|
|
800
|
+
const categories = db.Categories.cache().toList(); // DB query, cached
|
|
801
|
+
const categories2 = db.Categories.cache().toList(); // Cache hit! (instant)
|
|
800
802
|
|
|
801
803
|
// Update invalidates cache automatically
|
|
802
|
-
|
|
803
|
-
|
|
804
|
+
const cat = db.Categories.findById(1);
|
|
805
|
+
cat.name = "Updated";
|
|
806
|
+
db.saveChanges(); // Cache for Categories table cleared
|
|
804
807
|
|
|
805
|
-
//
|
|
806
|
-
|
|
808
|
+
// End request (clears cache - like Active Record)
|
|
809
|
+
db.endRequest(); // Cache cleared for next request
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
**Web Application Pattern (Recommended):**
|
|
813
|
+
```javascript
|
|
814
|
+
// Express middleware - automatic request-scoped caching
|
|
815
|
+
app.use((req, res, next) => {
|
|
816
|
+
req.db = new AppContext();
|
|
817
|
+
|
|
818
|
+
// Clear cache when response finishes (like Active Record)
|
|
819
|
+
res.on('finish', () => {
|
|
820
|
+
req.db.endRequest(); // Clears query cache
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
next();
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
// In your routes
|
|
827
|
+
app.get('/categories', (req, res) => {
|
|
828
|
+
// Cache is fresh for this request
|
|
829
|
+
const categories = req.db.Categories.cache().toList();
|
|
830
|
+
res.json(categories);
|
|
831
|
+
// Cache auto-cleared after response
|
|
832
|
+
});
|
|
807
833
|
```
|
|
808
834
|
|
|
809
835
|
#### Configuration
|
|
@@ -812,29 +838,43 @@ Configure caching via environment variables:
|
|
|
812
838
|
|
|
813
839
|
```bash
|
|
814
840
|
# Development (.env)
|
|
815
|
-
|
|
816
|
-
QUERY_CACHE_TTL=300000 # TTL in milliseconds (default: 5 minutes)
|
|
841
|
+
QUERY_CACHE_TTL=5000 # TTL in milliseconds (default: 5 seconds - request-scoped)
|
|
817
842
|
QUERY_CACHE_SIZE=1000 # Max cache entries (default: 1000)
|
|
843
|
+
QUERY_CACHE_ENABLED=true # Enable/disable globally (default: true)
|
|
818
844
|
|
|
819
845
|
# Production (.env)
|
|
820
|
-
|
|
821
|
-
QUERY_CACHE_TTL=300 # Redis uses seconds
|
|
846
|
+
QUERY_CACHE_TTL=5 # Redis uses seconds (5 seconds default)
|
|
822
847
|
REDIS_URL=redis://localhost:6379 # Use Redis for distributed caching
|
|
823
848
|
```
|
|
824
849
|
|
|
825
|
-
|
|
850
|
+
**Note:**
|
|
851
|
+
- Cache is **opt-in per query** using `.cache()`
|
|
852
|
+
- Default TTL is **5 seconds** (request-scoped like Active Record)
|
|
853
|
+
- Call `db.endRequest()` to clear cache manually (recommended in middleware)
|
|
854
|
+
- Environment variables control the cache system globally
|
|
855
|
+
|
|
856
|
+
#### Enable Caching for Specific Queries
|
|
826
857
|
|
|
827
|
-
Use `.
|
|
858
|
+
Use `.cache()` for frequently accessed, rarely changed data:
|
|
828
859
|
|
|
829
860
|
```javascript
|
|
830
|
-
// Always
|
|
861
|
+
// DEFAULT: Always hits database (safe)
|
|
831
862
|
const liveData = db.Analytics
|
|
832
863
|
.where(a => a.date == $$, today)
|
|
833
|
-
.
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
const
|
|
864
|
+
.toList(); // No caching (default)
|
|
865
|
+
|
|
866
|
+
// OPT-IN: Cache reference data
|
|
867
|
+
const categories = db.Categories.cache().toList(); // Cached for 5 minutes
|
|
868
|
+
const settings = db.Settings.cache().toList(); // Cached
|
|
869
|
+
const countries = db.Countries.cache().toList(); // Cached
|
|
870
|
+
|
|
871
|
+
// When to use .cache():
|
|
872
|
+
// ✅ Reference data (categories, settings, countries)
|
|
873
|
+
// ✅ Rarely changing data (roles, permissions)
|
|
874
|
+
// ✅ Expensive aggregations with stable results
|
|
875
|
+
// ❌ User-specific data
|
|
876
|
+
// ❌ Real-time data
|
|
877
|
+
// ❌ Financial/critical data
|
|
838
878
|
```
|
|
839
879
|
|
|
840
880
|
#### Manual Cache Control
|
|
@@ -901,19 +941,22 @@ class AppContext extends context {
|
|
|
901
941
|
MasterRecord automatically invalidates cache entries when data changes:
|
|
902
942
|
|
|
903
943
|
```javascript
|
|
904
|
-
// Query
|
|
905
|
-
const
|
|
944
|
+
// Query with caching enabled
|
|
945
|
+
const categories = db.Categories.cache().toList(); // DB query, cached
|
|
946
|
+
|
|
947
|
+
// Any modification to Categories table invalidates ALL cached Category queries
|
|
948
|
+
const cat = db.Categories.findById(1);
|
|
949
|
+
cat.name = "Updated";
|
|
950
|
+
db.saveChanges(); // Invalidates all cached Categories queries
|
|
906
951
|
|
|
907
|
-
//
|
|
908
|
-
const
|
|
909
|
-
user.name = "Updated";
|
|
910
|
-
db.saveChanges(); // Invalidates all cached User queries
|
|
952
|
+
// Next cached query hits database (fresh data)
|
|
953
|
+
const categoriesAgain = db.Categories.cache().toList(); // DB query (cache cleared)
|
|
911
954
|
|
|
912
|
-
//
|
|
913
|
-
const
|
|
955
|
+
// Non-cached queries are unaffected (always fresh)
|
|
956
|
+
const users = db.User.toList(); // No .cache() = always DB query
|
|
914
957
|
|
|
915
|
-
// Queries for OTHER tables are unaffected
|
|
916
|
-
const
|
|
958
|
+
// Queries for OTHER tables' caches are unaffected
|
|
959
|
+
const settings = db.Settings.cache().toList(); // Still cached (different table)
|
|
917
960
|
```
|
|
918
961
|
|
|
919
962
|
**Invalidation rules:**
|
|
@@ -937,40 +980,39 @@ Expected performance improvements:
|
|
|
937
980
|
|
|
938
981
|
#### Best Practices
|
|
939
982
|
|
|
940
|
-
**DO cache:**
|
|
983
|
+
**DO use .cache():**
|
|
941
984
|
```javascript
|
|
942
985
|
// Reference data (rarely changes)
|
|
943
|
-
const categories = db.Categories.toList();
|
|
944
|
-
const settings = db.Settings.toList();
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
.where(o => o.status == $$, 'completed')
|
|
986
|
+
const categories = db.Categories.cache().toList();
|
|
987
|
+
const settings = db.Settings.cache().toList();
|
|
988
|
+
const countries = db.Countries.cache().toList();
|
|
989
|
+
|
|
990
|
+
// Expensive aggregations (stable results)
|
|
991
|
+
const totalRevenue = db.Orders
|
|
992
|
+
.where(o => o.year == $$, 2024)
|
|
993
|
+
.cache()
|
|
952
994
|
.count();
|
|
953
995
|
```
|
|
954
996
|
|
|
955
|
-
**DON'T cache:**
|
|
997
|
+
**DON'T use .cache():**
|
|
956
998
|
```javascript
|
|
957
|
-
//
|
|
999
|
+
// User-specific data (default is safe - no caching)
|
|
1000
|
+
const user = db.User.findById(userId); // Always fresh
|
|
1001
|
+
|
|
1002
|
+
// Real-time data (default is safe)
|
|
958
1003
|
const liveOrders = db.Orders
|
|
959
1004
|
.where(o => o.status == $$, 'pending')
|
|
960
|
-
.
|
|
961
|
-
.toList();
|
|
1005
|
+
.toList(); // Always fresh
|
|
962
1006
|
|
|
963
|
-
// Financial transactions (
|
|
1007
|
+
// Financial transactions (default is safe)
|
|
964
1008
|
const balance = db.Transactions
|
|
965
1009
|
.where(t => t.user_id == $$, userId)
|
|
966
|
-
.
|
|
967
|
-
.toList();
|
|
1010
|
+
.toList(); // Always fresh
|
|
968
1011
|
|
|
969
|
-
// User-specific sensitive data (
|
|
1012
|
+
// User-specific sensitive data (default is safe)
|
|
970
1013
|
const permissions = db.UserPermissions
|
|
971
1014
|
.where(p => p.user_id == $$, userId)
|
|
972
|
-
.
|
|
973
|
-
.toList();
|
|
1015
|
+
.toList(); // Always fresh
|
|
974
1016
|
```
|
|
975
1017
|
|
|
976
1018
|
#### Monitoring Cache Performance
|
|
@@ -988,6 +1030,74 @@ if (parseFloat(stats.hitRate) < 50) {
|
|
|
988
1030
|
}
|
|
989
1031
|
```
|
|
990
1032
|
|
|
1033
|
+
#### Request-Scoped Caching (Like Active Record)
|
|
1034
|
+
|
|
1035
|
+
MasterRecord's caching is designed to work like Active Record - **cache within a request, clear after**:
|
|
1036
|
+
|
|
1037
|
+
```javascript
|
|
1038
|
+
// Express middleware pattern (recommended)
|
|
1039
|
+
app.use((req, res, next) => {
|
|
1040
|
+
req.db = new AppContext();
|
|
1041
|
+
|
|
1042
|
+
// Automatically clear cache when request ends
|
|
1043
|
+
res.on('finish', () => {
|
|
1044
|
+
req.db.endRequest(); // Like Active Record's cache clearing
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
next();
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
// In routes - cache is fresh per request
|
|
1051
|
+
app.get('/api/categories', (req, res) => {
|
|
1052
|
+
// First call in this request - DB query
|
|
1053
|
+
const categories = req.db.Categories.cache().toList();
|
|
1054
|
+
|
|
1055
|
+
// Second call in same request - cache hit
|
|
1056
|
+
const categoriesAgain = req.db.Categories.cache().toList();
|
|
1057
|
+
|
|
1058
|
+
res.json(categories);
|
|
1059
|
+
// After response, cache is automatically cleared
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
// Next request starts with empty cache (fresh)
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
**Why request-scoped?**
|
|
1066
|
+
- ✅ Like Active Record - familiar pattern
|
|
1067
|
+
- ✅ No stale data across requests
|
|
1068
|
+
- ✅ Cache only lives during request processing
|
|
1069
|
+
- ✅ Automatic cleanup
|
|
1070
|
+
|
|
1071
|
+
#### Important: Shared Cache Behavior
|
|
1072
|
+
|
|
1073
|
+
**The cache is shared across all context instances of the same class.** This ensures consistency within a request:
|
|
1074
|
+
|
|
1075
|
+
```javascript
|
|
1076
|
+
const db1 = new AppContext();
|
|
1077
|
+
const db2 = new AppContext();
|
|
1078
|
+
|
|
1079
|
+
// Context 1: Cache data with .cache()
|
|
1080
|
+
const categories1 = db1.Categories.cache().toList(); // DB query, cached
|
|
1081
|
+
|
|
1082
|
+
// Context 2: Sees cached data
|
|
1083
|
+
const categories2 = db2.Categories.cache().toList(); // Cache hit!
|
|
1084
|
+
|
|
1085
|
+
// Context 2: Updates invalidate cache for BOTH contexts
|
|
1086
|
+
const cat = db2.Categories.findById(1);
|
|
1087
|
+
cat.name = "Updated";
|
|
1088
|
+
db2.saveChanges(); // Invalidates shared cache
|
|
1089
|
+
|
|
1090
|
+
// Context 1: Sees fresh data
|
|
1091
|
+
const categories3 = db1.Categories.cache().toList(); // Cache miss, fresh data
|
|
1092
|
+
console.log(categories3[0].name); // "Updated"
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
**Why shared cache?**
|
|
1096
|
+
- ✅ Prevents stale data across multiple context instances
|
|
1097
|
+
- ✅ Ensures all parts of your application see consistent data
|
|
1098
|
+
- ✅ Reduces memory usage (one cache instead of many)
|
|
1099
|
+
- ✅ Correct behavior for single-database applications (most use cases)
|
|
1100
|
+
|
|
991
1101
|
### Multi-Context Applications
|
|
992
1102
|
|
|
993
1103
|
Manage multiple databases in one application:
|
|
@@ -1065,6 +1175,7 @@ context.remove(entity)
|
|
|
1065
1175
|
// Cache management
|
|
1066
1176
|
context.getCacheStats() // Get cache statistics
|
|
1067
1177
|
context.clearQueryCache() // Clear all cached queries
|
|
1178
|
+
context.endRequest() // End request and clear cache (like Active Record)
|
|
1068
1179
|
context.setQueryCacheEnabled(bool) // Enable/disable caching
|
|
1069
1180
|
```
|
|
1070
1181
|
|
|
@@ -1079,7 +1190,7 @@ context.setQueryCacheEnabled(bool) // Enable/disable caching
|
|
|
1079
1190
|
.skip(number) // Skip N records
|
|
1080
1191
|
.take(number) // Limit to N records
|
|
1081
1192
|
.include(relationship) // Eager load
|
|
1082
|
-
.
|
|
1193
|
+
.cache() // Enable caching for this query (opt-in)
|
|
1083
1194
|
|
|
1084
1195
|
// Terminal methods (execute query)
|
|
1085
1196
|
.toList() // Return array
|
|
@@ -1249,18 +1360,22 @@ console.log(`${author.name} has ${posts.length} posts`);
|
|
|
1249
1360
|
|
|
1250
1361
|
## Performance Tips
|
|
1251
1362
|
|
|
1252
|
-
### 1.
|
|
1363
|
+
### 1. Use Query Caching Selectively
|
|
1253
1364
|
|
|
1254
1365
|
```javascript
|
|
1255
|
-
// ✅ GOOD: Cache reference data
|
|
1256
|
-
const categories = db.Categories.toList(); //
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1366
|
+
// ✅ GOOD: Cache reference data that rarely changes
|
|
1367
|
+
const categories = db.Categories.cache().toList(); // Opt-in caching
|
|
1368
|
+
const settings = db.Settings.cache().toList();
|
|
1369
|
+
|
|
1370
|
+
// ✅ GOOD: Queries without .cache() are always fresh (safe default)
|
|
1371
|
+
const user1 = db.User.findById(123); // Always DB query (no cache)
|
|
1372
|
+
const user2 = db.User.findById(123); // Always DB query (no cache)
|
|
1373
|
+
|
|
1374
|
+
// ✅ GOOD: Cache expensive queries with stable results
|
|
1375
|
+
const revenue2024 = db.Orders
|
|
1376
|
+
.where(o => o.year == $$, 2024)
|
|
1377
|
+
.cache() // Historical data doesn't change
|
|
1378
|
+
.count();
|
|
1264
1379
|
|
|
1265
1380
|
// Monitor cache performance
|
|
1266
1381
|
const stats = db.getCacheStats();
|
|
@@ -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
|
+
}
|