masterrecord 0.3.49 → 0.3.51

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,303 @@
1
+ /**
2
+ * Test: Global Pool Registry — One Pool Per Database
3
+ *
4
+ * Verifies that multiple context instances share the same connection pool
5
+ * instead of creating a new pool per instance (the bug this fixes).
6
+ */
7
+
8
+ const context = require('../context');
9
+
10
+ console.log("╔════════════════════════════════════════════════════════════════╗");
11
+ console.log("║ Pool Registry Test — One Pool Per Database ║");
12
+ console.log("╚════════════════════════════════════════════════════════════════╝\n");
13
+
14
+ let passed = 0;
15
+ let failed = 0;
16
+
17
+ function assert(condition, passMsg, failMsg) {
18
+ if (condition) {
19
+ console.log(` ✓ ${passMsg}`);
20
+ passed++;
21
+ } else {
22
+ console.log(` ✗ ${failMsg}`);
23
+ failed++;
24
+ }
25
+ }
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Test 1: _pools global registry exists
29
+ // ---------------------------------------------------------------------------
30
+ console.log("📝 Test 1: Global pool registry exists");
31
+ console.log("──────────────────────────────────────────────────");
32
+
33
+ try {
34
+ const pools = global.__MR_POOLS__;
35
+ assert(
36
+ pools instanceof Map,
37
+ "global.__MR_POOLS__ is a Map",
38
+ `global.__MR_POOLS__ is not a Map (got: ${typeof pools})`
39
+ );
40
+ } catch (err) {
41
+ console.log(` ✗ Error: ${err.message}`);
42
+ failed++;
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Test 2: closeAll() is a static method
47
+ // ---------------------------------------------------------------------------
48
+ console.log("\n📝 Test 2: context.closeAll() is a static method");
49
+ console.log("──────────────────────────────────────────────────");
50
+
51
+ try {
52
+ assert(
53
+ typeof context.closeAll === 'function',
54
+ "context.closeAll exists and is a function",
55
+ `context.closeAll is not a function (got: ${typeof context.closeAll})`
56
+ );
57
+ } catch (err) {
58
+ console.log(` ✗ Error: ${err.message}`);
59
+ failed++;
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Test 3: getPoolStats() is a static method
64
+ // ---------------------------------------------------------------------------
65
+ console.log("\n📝 Test 3: context.getPoolStats() is a static method");
66
+ console.log("──────────────────────────────────────────────────");
67
+
68
+ try {
69
+ assert(
70
+ typeof context.getPoolStats === 'function',
71
+ "context.getPoolStats exists and is a function",
72
+ `context.getPoolStats is not a function (got: ${typeof context.getPoolStats})`
73
+ );
74
+ } catch (err) {
75
+ console.log(` ✗ Error: ${err.message}`);
76
+ failed++;
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Test 4: getPoolStats() returns an array
81
+ // ---------------------------------------------------------------------------
82
+ console.log("\n📝 Test 4: getPoolStats() returns an array");
83
+ console.log("──────────────────────────────────────────────────");
84
+
85
+ try {
86
+ const stats = context.getPoolStats();
87
+ assert(
88
+ Array.isArray(stats),
89
+ `getPoolStats() returned array with ${stats.length} entries`,
90
+ `Expected array, got: ${typeof stats}`
91
+ );
92
+ } catch (err) {
93
+ console.log(` ✗ Error: ${err.message}`);
94
+ failed++;
95
+ }
96
+
97
+ // Async tests wrapped in IIFE
98
+ (async function runAsyncTests() {
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Test 5: closeAll() clears the registry
102
+ // ---------------------------------------------------------------------------
103
+ console.log("\n📝 Test 5: closeAll() clears the registry");
104
+ console.log("──────────────────────────────────────────────────");
105
+
106
+ try {
107
+ const pools = global.__MR_POOLS__;
108
+ pools.set('test:fake@localhost:5432/testdb', {
109
+ engine: { close: function() {} },
110
+ refCount: 1,
111
+ dbType: 'test'
112
+ });
113
+
114
+ const beforeSize = pools.size;
115
+ await context.closeAll();
116
+ const afterSize = pools.size;
117
+
118
+ assert(
119
+ beforeSize > 0 && afterSize === 0,
120
+ `Registry had ${beforeSize} entry, now has ${afterSize} after closeAll()`,
121
+ `Expected registry to be cleared (before: ${beforeSize}, after: ${afterSize})`
122
+ );
123
+ } catch (err) {
124
+ console.log(` ✗ Error: ${err.message}`);
125
+ failed++;
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Test 6: close() on instance decrements refCount
130
+ // ---------------------------------------------------------------------------
131
+ console.log("\n📝 Test 6: close() decrements refCount");
132
+ console.log("──────────────────────────────────────────────────");
133
+
134
+ try {
135
+ const pools = global.__MR_POOLS__;
136
+ const fakeEngine = { close: function() { return Promise.resolve(); } };
137
+
138
+ pools.set('test:ref-count-test', {
139
+ engine: fakeEngine,
140
+ refCount: 2,
141
+ dbType: 'test'
142
+ });
143
+
144
+ class TestCtx extends context {
145
+ constructor() { super(); }
146
+ }
147
+
148
+ const db = new TestCtx();
149
+ db._SQLEngine = fakeEngine;
150
+
151
+ await db.close();
152
+
153
+ const entry = pools.get('test:ref-count-test');
154
+
155
+ assert(
156
+ entry && entry.refCount === 1,
157
+ "refCount decremented from 2 to 1 — pool NOT closed (still has references)",
158
+ entry ? `Expected refCount 1, got ${entry.refCount}` : "Entry was deleted (should still exist with refCount 1)"
159
+ );
160
+
161
+ pools.delete('test:ref-count-test');
162
+ } catch (err) {
163
+ console.log(` ✗ Error: ${err.message}`);
164
+ failed++;
165
+ }
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // Test 7: close() removes pool when refCount reaches 0
169
+ // ---------------------------------------------------------------------------
170
+ console.log("\n📝 Test 7: close() removes pool when refCount reaches 0");
171
+ console.log("──────────────────────────────────────────────────");
172
+
173
+ try {
174
+ const pools = global.__MR_POOLS__;
175
+ let closeCalled = false;
176
+ const fakeEngine = { close: function() { closeCalled = true; return Promise.resolve(); } };
177
+
178
+ pools.set('test:last-ref-test', {
179
+ engine: fakeEngine,
180
+ refCount: 1,
181
+ dbType: 'test'
182
+ });
183
+
184
+ class TestCtx2 extends context {
185
+ constructor() { super(); }
186
+ }
187
+
188
+ const db = new TestCtx2();
189
+ db._SQLEngine = fakeEngine;
190
+
191
+ await db.close();
192
+
193
+ const entry = pools.get('test:last-ref-test');
194
+
195
+ assert(
196
+ !entry && closeCalled,
197
+ "Pool removed from registry when refCount reached 0, engine.close() called",
198
+ entry ? `Entry still exists with refCount ${entry.refCount}` : "Entry removed but engine.close() was not called"
199
+ );
200
+ } catch (err) {
201
+ console.log(` ✗ Error: ${err.message}`);
202
+ failed++;
203
+ }
204
+
205
+ // ---------------------------------------------------------------------------
206
+ // Test 8: getPoolStats() returns correct shape
207
+ // ---------------------------------------------------------------------------
208
+ console.log("\n📝 Test 8: Pool registry entries have correct shape");
209
+ console.log("──────────────────────────────────────────────────");
210
+
211
+ try {
212
+ const pools = global.__MR_POOLS__;
213
+ const fakeEngine = { close: function() {} };
214
+
215
+ pools.set('mysql:testuser@localhost:3306/testdb', {
216
+ client: {},
217
+ engine: fakeEngine,
218
+ refCount: 3,
219
+ dbType: 'mysql'
220
+ });
221
+
222
+ const stats = context.getPoolStats();
223
+ const mysqlStat = stats.find(function(s) { return s.key === 'mysql:testuser@localhost:3306/testdb'; });
224
+
225
+ assert(
226
+ mysqlStat && mysqlStat.dbType === 'mysql' && mysqlStat.refCount === 3,
227
+ `getPoolStats() correct: ${mysqlStat.key} (type: ${mysqlStat.dbType}, refs: ${mysqlStat.refCount})`,
228
+ `Expected mysql entry with refCount 3, got: ${JSON.stringify(mysqlStat)}`
229
+ );
230
+
231
+ pools.delete('mysql:testuser@localhost:3306/testdb');
232
+ } catch (err) {
233
+ console.log(` ✗ Error: ${err.message}`);
234
+ failed++;
235
+ }
236
+
237
+ // ---------------------------------------------------------------------------
238
+ // Test 9: Two context instances with same fake engine share one pool entry
239
+ // ---------------------------------------------------------------------------
240
+ console.log("\n📝 Test 9: Two instances sharing a pool both decrement correctly");
241
+ console.log("──────────────────────────────────────────────────");
242
+
243
+ try {
244
+ const pools = global.__MR_POOLS__;
245
+ let closeCalled = false;
246
+ const sharedEngine = { close: function() { closeCalled = true; return Promise.resolve(); } };
247
+
248
+ pools.set('test:shared-engine', {
249
+ engine: sharedEngine,
250
+ refCount: 2,
251
+ dbType: 'test'
252
+ });
253
+
254
+ class TestCtx3 extends context {
255
+ constructor() { super(); }
256
+ }
257
+
258
+ const db1 = new TestCtx3();
259
+ db1._SQLEngine = sharedEngine;
260
+
261
+ const db2 = new TestCtx3();
262
+ db2._SQLEngine = sharedEngine;
263
+
264
+ // First close: refCount 2 -> 1
265
+ await db1.close();
266
+ let entry = pools.get('test:shared-engine');
267
+ const afterFirst = entry ? entry.refCount : -1;
268
+
269
+ // Second close: refCount 1 -> 0, pool removed
270
+ await db2.close();
271
+ entry = pools.get('test:shared-engine');
272
+
273
+ assert(
274
+ afterFirst === 1 && !entry && closeCalled,
275
+ "First close: refs 2->1. Second close: refs 1->0, pool destroyed, engine.close() called",
276
+ `afterFirst=${afterFirst}, entryGone=${!entry}, closeCalled=${closeCalled}`
277
+ );
278
+ } catch (err) {
279
+ console.log(` ✗ Error: ${err.message}`);
280
+ failed++;
281
+ }
282
+
283
+ // ---------------------------------------------------------------------------
284
+ // Summary
285
+ // ---------------------------------------------------------------------------
286
+ console.log("\n╔════════════════════════════════════════════════════════════════╗");
287
+ console.log("║ Test Summary ║");
288
+ console.log("╚════════════════════════════════════════════════════════════════╝");
289
+ console.log(`\n ✓ Passed: ${passed}`);
290
+ console.log(` ✗ Failed: ${failed}`);
291
+ console.log(` 📊 Total: ${passed + failed}\n`);
292
+
293
+ if (failed === 0) {
294
+ console.log(" 🎉 All tests passed!\n");
295
+ console.log(" ✅ Global pool registry is working correctly");
296
+ console.log(" ✅ One pool per database — no more connection exhaustion\n");
297
+ process.exit(0);
298
+ } else {
299
+ console.log(" ❌ Some tests failed\n");
300
+ process.exit(1);
301
+ }
302
+
303
+ })();
@@ -1,185 +0,0 @@
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
- }
@@ -1,61 +0,0 @@
1
- /**
2
- * Simple test to check if ID is set after save
3
- */
4
-
5
- const masterrecord = require('../MasterRecord.js');
6
- const path = require('path');
7
- const fs = require('fs');
8
-
9
- class User {
10
- id(db) {
11
- db.integer().primary().auto();
12
- }
13
- name(db) {
14
- db.string();
15
- }
16
- }
17
-
18
- class TestContext extends masterrecord.context {
19
- constructor() {
20
- super();
21
- this.database = path.join(__dirname, '..', 'database', 'simpleIdTest.db');
22
- }
23
- onConfig(db) {
24
- this.dbset(User);
25
- }
26
- }
27
-
28
- // Clean
29
- if (fs.existsSync(path.join(__dirname, '..', 'database', 'simpleIdTest.db'))) {
30
- fs.unlinkSync(path.join(__dirname, '..', 'database', 'simpleIdTest.db'));
31
- }
32
-
33
- async function test() {
34
- const db = new TestContext();
35
- db.onConfig();
36
-
37
- const user = db.User.new();
38
- user.name = 'Test';
39
-
40
- console.log('Before save - user.id:', user.id);
41
- console.log('Before save - user.__proto__:', Object.keys(user.__proto__));
42
-
43
- await user.save();
44
-
45
- console.log('After save - user.id:', user.id);
46
- console.log('After save - user.__proto__:', Object.keys(user.__proto__));
47
- console.log('After save - user.__proto__._id:', user.__proto__._id);
48
-
49
- // Try direct access
50
- console.log('Direct access test:');
51
- for (const key in user) {
52
- if (key === 'id') {
53
- console.log('Found id property via for-in');
54
- }
55
- }
56
-
57
- console.log('Has own property id?', user.hasOwnProperty('id'));
58
- console.log('Keys:', Object.keys(user));
59
- }
60
-
61
- test().catch(console.error);
@@ -1,70 +0,0 @@
1
- /**
2
- * Single user ID test
3
- */
4
-
5
- const masterrecord = require('../MasterRecord.js');
6
- const path = require('path');
7
- const fs = require('fs');
8
-
9
- class User {
10
- id(db) {
11
- db.integer().primary().auto();
12
- }
13
- name(db) {
14
- db.string();
15
- }
16
- }
17
-
18
- class TestContext extends masterrecord.context {
19
- constructor() {
20
- super();
21
- }
22
- onConfig(db) {
23
- this.dbset(User);
24
- }
25
- }
26
-
27
- // Clean
28
- const dbPath = path.join(__dirname, '..', 'database', 'singleUserTest.db');
29
- if (fs.existsSync(dbPath)) {
30
- fs.unlinkSync(dbPath);
31
- }
32
-
33
- async function test() {
34
- const db = new TestContext();
35
-
36
- // Manually initialize SQLite for testing
37
- const SQLLiteEngine = require('../SQLLiteEngine');
38
- const sqlite3 = require('better-sqlite3');
39
-
40
- db.isSQLite = true;
41
- db.isMySQL = false;
42
- db.isPostgres = false;
43
- db._SQLEngine = new SQLLiteEngine();
44
- db.db = new sqlite3(dbPath);
45
- db._SQLEngine.setDB(db.db, 'better-sqlite3');
46
-
47
- db.onConfig();
48
-
49
- // Create table
50
- db.db.exec('CREATE TABLE IF NOT EXISTS User (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)');
51
-
52
- const user = db.User.new();
53
- user.name = 'Test';
54
-
55
- console.log('Before save - user.id:', user.id);
56
- console.log('Before save - tracked:', db.__trackedEntities.length);
57
-
58
- await user.save();
59
-
60
- console.log('After save - user.id:', user.id);
61
- console.log('After save - user.__proto__._id:', user.__proto__._id);
62
-
63
- if (user.id) {
64
- console.log('✓ SUCCESS - ID was set to:', user.id);
65
- } else {
66
- console.log('✗ FAIL - ID is still undefined');
67
- }
68
- }
69
-
70
- test().catch(console.error);