masterrecord 0.3.49 → 0.3.50

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.
@@ -63,7 +63,8 @@
63
63
  "Bash(npx mocha:*)",
64
64
  "Bash(masterrecord add-migration:*)",
65
65
  "Bash(masterrecord:*)",
66
- "Bash(git -C /Users/alexanderrich/Documents/development/bookbaghq/bookbag-training log --oneline --all -- *MASTERRECORD_ISSUE*)"
66
+ "Bash(git -C /Users/alexanderrich/Documents/development/bookbaghq/bookbag-training log --oneline --all -- *MASTERRECORD_ISSUE*)",
67
+ "Bash(npm ls -g masterrecord 2>&1 | head -5)"
67
68
  ],
68
69
  "deny": [],
69
70
  "ask": []
package/context.js CHANGED
@@ -32,6 +32,19 @@ const MySQLAsyncClient = require('masterrecord/mySQLConnect');
32
32
  const PostgresClient = require('masterrecord/postgresSyncConnect');
33
33
  const QueryCache = require('./Cache/QueryCache');
34
34
 
35
+ // ============================================================================
36
+ // GLOBAL POOL REGISTRY - One pool per database, shared across all contexts
37
+ // ============================================================================
38
+
39
+ const _pools = global.__MR_POOLS__ || (global.__MR_POOLS__ = new Map());
40
+
41
+ function _poolKey(type, cfg) {
42
+ if (type === 'sqlite') return `sqlite:${cfg.completeConnection || cfg.connection}`;
43
+ const host = cfg.host || 'localhost';
44
+ const port = cfg.port || (type === 'mysql' ? 3306 : 5432);
45
+ return `${type}:${cfg.user}@${host}:${port}/${cfg.database}`;
46
+ }
47
+
35
48
  // ============================================================================
36
49
  // CONSTANTS - Extract all magic numbers for maintainability
37
50
  // ============================================================================
@@ -329,6 +342,16 @@ class context {
329
342
  );
330
343
  }
331
344
 
345
+ const key = _poolKey('mysql', env);
346
+ if (_pools.has(key)) {
347
+ const cached = _pools.get(key);
348
+ cached.refCount++;
349
+ this._SQLEngine = cached.engine;
350
+ this.isMySQL = true;
351
+ console.log(`[MySQL] Reusing pool for ${env.database} (refs: ${cached.refCount})`);
352
+ return cached.client;
353
+ }
354
+
332
355
  console.log('[MySQL] Initializing async connection pool...');
333
356
  const client = new MySQLAsyncClient(env);
334
357
  await client.connect();
@@ -339,6 +362,7 @@ class context {
339
362
  this._SQLEngine.__name = sqlName;
340
363
  this.isMySQL = true;
341
364
 
365
+ _pools.set(key, { client, engine: this._SQLEngine, refCount: 1, dbType: 'mysql' });
342
366
  console.log('[MySQL] Connection pool ready');
343
367
  return client;
344
368
  } catch (error) {
@@ -401,12 +425,24 @@ class context {
401
425
  );
402
426
  }
403
427
 
428
+ const key = _poolKey('postgres', env);
429
+ if (_pools.has(key)) {
430
+ const cached = _pools.get(key);
431
+ cached.refCount++;
432
+ this._SQLEngine = cached.engine;
433
+ this._SQLEngine.__name = sqlName;
434
+ console.log(`[PostgreSQL] Reusing pool for ${env.database} (refs: ${cached.refCount})`);
435
+ return cached.pool;
436
+ }
437
+
404
438
  const connection = new PostgresClient();
405
439
  await connection.connect(env);
406
440
  this._SQLEngine = connection.getEngine();
407
441
  this._SQLEngine.__name = sqlName;
408
442
 
409
- return connection.getPool();
443
+ const pool = connection.getPool();
444
+ _pools.set(key, { pool, engine: this._SQLEngine, client: connection, refCount: 1, dbType: 'postgres' });
445
+ return pool;
410
446
  } catch (error) {
411
447
  // Preserve original error if it's already a ContextError
412
448
  if (error instanceof ContextError) {
@@ -700,8 +736,20 @@ class context {
700
736
  }
701
737
 
702
738
  const sqliteOptions = { ...options, completeConnection: dbPath };
739
+
740
+ const sqliteKey = _poolKey('sqlite', sqliteOptions);
741
+ if (_pools.has(sqliteKey)) {
742
+ const cached = _pools.get(sqliteKey);
743
+ cached.refCount++;
744
+ this.db = cached.db;
745
+ this._SQLEngine = cached.engine;
746
+ return this;
747
+ }
748
+
703
749
  this.db = this.__SQLiteInit(sqliteOptions, 'better-sqlite3');
704
750
  this._SQLEngine.setDB(this.db, 'better-sqlite3');
751
+
752
+ _pools.set(sqliteKey, { db: this.db, engine: this._SQLEngine, refCount: 1, dbType: 'sqlite' });
705
753
  return this;
706
754
  }
707
755
 
@@ -843,8 +891,19 @@ class context {
843
891
  fs.mkdirSync(dbDirectory, { recursive: true });
844
892
  }
845
893
 
894
+ const sqliteKey = _poolKey('sqlite', options);
895
+ if (_pools.has(sqliteKey)) {
896
+ const cached = _pools.get(sqliteKey);
897
+ cached.refCount++;
898
+ this.db = cached.db;
899
+ this._SQLEngine = cached.engine;
900
+ return this;
901
+ }
902
+
846
903
  this.db = this.__SQLiteInit(options, 'better-sqlite3');
847
904
  this._SQLEngine.setDB(this.db, 'better-sqlite3');
905
+
906
+ _pools.set(sqliteKey, { db: this.db, engine: this._SQLEngine, refCount: 1, dbType: 'sqlite' });
848
907
  return this;
849
908
  } catch (error) {
850
909
  // Preserve original error if it's already a ContextError
@@ -1927,11 +1986,72 @@ class context {
1927
1986
  * db.close(); // Close connections
1928
1987
  */
1929
1988
  async close() {
1989
+ // Find this instance's pool in the registry and decrement
1990
+ for (const [key, entry] of _pools) {
1991
+ if (entry.engine === this._SQLEngine) {
1992
+ entry.refCount--;
1993
+ if (entry.refCount <= 0) {
1994
+ _pools.delete(key);
1995
+ if (this._SQLEngine && typeof this._SQLEngine.close === 'function') {
1996
+ await this._SQLEngine.close();
1997
+ }
1998
+ }
1999
+ this._SQLEngine = null;
2000
+ this.db = null;
2001
+ return;
2002
+ }
2003
+ }
2004
+
2005
+ // Fallback (not in registry)
1930
2006
  if (this._SQLEngine && typeof this._SQLEngine.close === 'function') {
1931
2007
  return await this._SQLEngine.close();
1932
2008
  }
1933
2009
  }
1934
2010
 
2011
+ /**
2012
+ * Close all shared connection pools, regardless of reference count.
2013
+ * Useful for graceful shutdown or test cleanup.
2014
+ *
2015
+ * @static
2016
+ * @async
2017
+ * @returns {Promise<void>}
2018
+ *
2019
+ * @example
2020
+ * // Graceful shutdown
2021
+ * process.on('SIGTERM', async () => {
2022
+ * await context.closeAll();
2023
+ * process.exit(0);
2024
+ * });
2025
+ */
2026
+ static async closeAll() {
2027
+ for (const [key, entry] of _pools) {
2028
+ try {
2029
+ if (entry.engine && typeof entry.engine.close === 'function') {
2030
+ await entry.engine.close();
2031
+ }
2032
+ } catch (err) {
2033
+ console.error('[MasterRecord] Error closing pool:', err.message);
2034
+ }
2035
+ }
2036
+ _pools.clear();
2037
+ }
2038
+
2039
+ /**
2040
+ * Get statistics about active connection pools.
2041
+ *
2042
+ * @static
2043
+ * @returns {Array<{key: string, dbType: string, refCount: number}>}
2044
+ *
2045
+ * @example
2046
+ * console.log(context.getPoolStats());
2047
+ * // [{ key: 'mysql:root@localhost:3306/mydb', dbType: 'mysql', refCount: 3 }]
2048
+ */
2049
+ static getPoolStats() {
2050
+ return Array.from(_pools.entries()).map(([key, entry]) => ({
2051
+ key, dbType: entry.dbType, refCount: entry.refCount
2052
+ }));
2053
+ }
2054
+
1935
2055
  /**
1936
2056
  * Attach a detached entity and mark it as modified
1937
2057
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "masterrecord",
3
- "version": "0.3.49",
3
+ "version": "0.3.50",
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": {
@@ -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);
@@ -1,169 +0,0 @@
1
- /**
2
- * Verify .findById() Method Implementation
3
- *
4
- * Simple verification that the .findById() method exists
5
- */
6
-
7
- const fs = require('fs');
8
- const path = require('path');
9
-
10
- console.log("╔════════════════════════════════════════════════════════════════╗");
11
- console.log("║ Verify .findById() Method Implementation ║");
12
- console.log("╚════════════════════════════════════════════════════════════════╝\n");
13
-
14
- let passed = 0;
15
- let failed = 0;
16
-
17
- // Test 1: Check file exists
18
- console.log("📝 Test 1: Check queryMethods.js exists");
19
- console.log("──────────────────────────────────────────────────");
20
-
21
- const queryMethodsPath = path.join(__dirname, '../QueryLanguage/queryMethods.js');
22
-
23
- try {
24
- if(fs.existsSync(queryMethodsPath)) {
25
- console.log(" ✓ queryMethods.js found");
26
- passed++;
27
- } else {
28
- console.log(" ✗ queryMethods.js not found");
29
- failed++;
30
- }
31
- } catch(err) {
32
- console.log(` ✗ Error: ${err.message}`);
33
- failed++;
34
- }
35
-
36
- // Test 2: Check .findById() method exists
37
- console.log("\n📝 Test 2: Check .findById() method is implemented");
38
- console.log("──────────────────────────────────────────────────");
39
-
40
- try {
41
- const content = fs.readFileSync(queryMethodsPath, 'utf8');
42
-
43
- const hasFindByIdMethod = /findById\s*\(\s*id\s*\)\s*\{/.test(content);
44
- const hasComment = /Convenience method.*Find record by primary key/i.test(content);
45
- const hasPrimaryKeyDetection = /Find the primary key field/i.test(content);
46
-
47
- if(hasFindByIdMethod) {
48
- console.log(" ✓ findById() method definition found");
49
-
50
- if(hasComment) {
51
- console.log(" ✓ Method documentation present");
52
- }
53
-
54
- if(hasPrimaryKeyDetection) {
55
- console.log(" ✓ Primary key detection code present");
56
- }
57
-
58
- passed++;
59
- } else {
60
- console.log(" ✗ findById() method not found");
61
- failed++;
62
- }
63
- } catch(err) {
64
- console.log(` ✗ Error: ${err.message}`);
65
- failed++;
66
- }
67
-
68
- // Test 3: Verify method implementation
69
- console.log("\n📝 Test 3: Verify method implementation details");
70
- console.log("──────────────────────────────────────────────────");
71
-
72
- try {
73
- const content = fs.readFileSync(queryMethodsPath, 'utf8');
74
-
75
- const checks = [
76
- { name: 'Primary key loop', pattern: /for\s*\(\s*const\s+fieldName\s+in\s+this\.__entity\s*\)/ },
77
- { name: 'Primary key check', pattern: /field\.primary\s*===\s*true/ },
78
- { name: 'Error for missing PK', pattern: /No primary key defined/ },
79
- { name: 'WHERE clause builder', pattern: /whereClause\s*=/ },
80
- { name: 'Calls .where()', pattern: /this\.where\(whereClause/ },
81
- { name: 'Calls .single()', pattern: /\.single\(\)/ }
82
- ];
83
-
84
- let allChecksPass = true;
85
-
86
- checks.forEach(check => {
87
- if(check.pattern.test(content)) {
88
- console.log(` ✓ ${check.name}`);
89
- } else {
90
- console.log(` ✗ Missing: ${check.name}`);
91
- allChecksPass = false;
92
- }
93
- });
94
-
95
- if(allChecksPass) {
96
- passed++;
97
- } else {
98
- failed++;
99
- }
100
- } catch(err) {
101
- console.log(` ✗ Error: ${err.message}`);
102
- failed++;
103
- }
104
-
105
- // Test 4: Check method placement
106
- console.log("\n📝 Test 4: Verify method placement in file");
107
- console.log("──────────────────────────────────────────────────");
108
-
109
- try {
110
- const content = fs.readFileSync(queryMethodsPath, 'utf8');
111
-
112
- const findByIdIndex = content.indexOf('findById(id)');
113
- const singleIndex = content.indexOf('single(){');
114
-
115
- if(findByIdIndex > 0 && singleIndex > 0 && findByIdIndex < singleIndex) {
116
- console.log(" ✓ findById() placed before single()");
117
- console.log(" ✓ Correct location in class structure");
118
- passed++;
119
- } else if(findByIdIndex > 0) {
120
- console.log(" ⚠ findById() exists but not in expected location");
121
- console.log(` ℹ findById at position ${findByIdIndex}`);
122
- passed++;
123
- } else {
124
- console.log(" ✗ Could not verify method placement");
125
- failed++;
126
- }
127
- } catch(err) {
128
- console.log(` ✗ Error: ${err.message}`);
129
- failed++;
130
- }
131
-
132
- // Summary
133
- console.log("\n\n╔════════════════════════════════════════════════════════════════╗");
134
- console.log("║ Verification Summary ║");
135
- console.log("╚════════════════════════════════════════════════════════════════╝");
136
-
137
- const total = passed + failed;
138
- const successRate = total > 0 ? Math.round((passed/total)*100) : 0;
139
-
140
- console.log(`\n Total Checks: ${total}`);
141
- console.log(` ✅ Passed: ${passed}`);
142
- console.log(` ❌ Failed: ${failed}`);
143
- console.log(` Success Rate: ${successRate}%\n`);
144
-
145
- if(failed === 0){
146
- console.log("🎉 All verification checks passed!");
147
- console.log("\n✨ .findById() Method Successfully Implemented!");
148
- console.log("\n📖 Implementation Details:");
149
- console.log(" File: QueryLanguage/queryMethods.js");
150
- console.log(" Method: findById(id)");
151
- console.log(" Purpose: Convenience method for finding by primary key");
152
- console.log("\n📖 Features:");
153
- console.log(" ✓ Automatically detects primary key field");
154
- console.log(" ✓ Works with any primary key name (id, user_id, etc.)");
155
- console.log(" ✓ Validates entity has a primary key");
156
- console.log(" ✓ Returns single record or null");
157
- console.log(" ✓ Generates proper parameterized query");
158
- console.log("\n📖 Usage Examples:");
159
- console.log(" const user = context.User.findById(123);");
160
- console.log(" const lead = qaContext.QaLead.findById(leadId);");
161
- console.log(" const post = context.Post.findById(postId);");
162
- console.log("\n📖 Equivalent To:");
163
- console.log(" const user = context.User.where(u => u.id == $$, 123).single();");
164
- console.log("\n✅ Now compatible with Mongoose/Sequelize-style syntax!\n");
165
- process.exit(0);
166
- } else {
167
- console.log("⚠️ Some verification checks failed.");
168
- process.exit(1);
169
- }
@@ -1,191 +0,0 @@
1
- /**
2
- * Verify .new() Method Implementation
3
- *
4
- * Simple verification that the .new() method exists in queryMethods.js
5
- */
6
-
7
- const fs = require('fs');
8
- const path = require('path');
9
-
10
- console.log("╔════════════════════════════════════════════════════════════════╗");
11
- console.log("║ Verify .new() Method Implementation ║");
12
- console.log("╚════════════════════════════════════════════════════════════════╝\n");
13
-
14
- let passed = 0;
15
- let failed = 0;
16
-
17
- // Test 1: Check file exists
18
- console.log("📝 Test 1: Check queryMethods.js exists");
19
- console.log("──────────────────────────────────────────────────");
20
-
21
- const queryMethodsPath = path.join(__dirname, '../QueryLanguage/queryMethods.js');
22
-
23
- try {
24
- if(fs.existsSync(queryMethodsPath)) {
25
- console.log(" ✓ queryMethods.js found");
26
- passed++;
27
- } else {
28
- console.log(" ✗ queryMethods.js not found");
29
- failed++;
30
- }
31
- } catch(err) {
32
- console.log(` ✗ Error: ${err.message}`);
33
- failed++;
34
- }
35
-
36
- // Test 2: Check .new() method exists in file
37
- console.log("\n📝 Test 2: Check .new() method is implemented");
38
- console.log("──────────────────────────────────────────────────");
39
-
40
- try {
41
- const content = fs.readFileSync(queryMethodsPath, 'utf8');
42
-
43
- // Check for method definition
44
- const hasNewMethod = /new\s*\(\s*\)\s*\{/.test(content);
45
- const hasComment = /Creates a new empty entity instance/i.test(content);
46
- const hasTracking = /this\.__context\.__track\(newEntity\)/.test(content);
47
- const hasPropertySetup = /Object\.defineProperty\(newEntity/.test(content);
48
-
49
- if(hasNewMethod) {
50
- console.log(" ✓ new() method definition found");
51
-
52
- if(hasComment) {
53
- console.log(" ✓ Method documentation present");
54
- }
55
-
56
- if(hasTracking) {
57
- console.log(" ✓ Entity tracking code present");
58
- }
59
-
60
- if(hasPropertySetup) {
61
- console.log(" ✓ Property definition code present");
62
- }
63
-
64
- passed++;
65
- } else {
66
- console.log(" ✗ new() method not found in queryMethods.js");
67
- failed++;
68
- }
69
- } catch(err) {
70
- console.log(` ✗ Error: ${err.message}`);
71
- failed++;
72
- }
73
-
74
- // Test 3: Verify method structure
75
- console.log("\n📝 Test 3: Verify method implementation structure");
76
- console.log("──────────────────────────────────────────────────");
77
-
78
- try {
79
- const content = fs.readFileSync(queryMethodsPath, 'utf8');
80
-
81
- // Extract the new() method
82
- const methodMatch = content.match(/new\s*\(\s*\)\s*\{[\s\S]*?^\s{4}\}/m);
83
-
84
- if(methodMatch) {
85
- const methodCode = methodMatch[0];
86
-
87
- const checks = [
88
- { name: '__state = "insert"', pattern: /__state\s*:\s*"insert"/ },
89
- { name: '__entity reference', pattern: /__entity\s*:\s*this\.__entity/ },
90
- { name: '__context reference', pattern: /__context\s*:\s*this\.__context/ },
91
- { name: '__dirtyFields array', pattern: /__dirtyFields\s*:\s*\[/ },
92
- { name: 'Property loop', pattern: /for\s*\(\s*var\s+fieldName\s+in\s+this\.__entity\s*\)/ },
93
- { name: 'Skip navigational', pattern: /isNavigational/ },
94
- { name: 'defineProperty', pattern: /Object\.defineProperty/ },
95
- { name: 'Track entity', pattern: /this\.__context\.__track/ },
96
- { name: 'Return entity', pattern: /return\s+newEntity/ }
97
- ];
98
-
99
- let allChecksPass = true;
100
-
101
- checks.forEach(check => {
102
- if(check.pattern.test(methodCode)) {
103
- console.log(` ✓ ${check.name}`);
104
- } else {
105
- console.log(` ✗ Missing: ${check.name}`);
106
- allChecksPass = false;
107
- }
108
- });
109
-
110
- if(allChecksPass) {
111
- passed++;
112
- } else {
113
- failed++;
114
- }
115
- } else {
116
- console.log(" ✗ Could not extract new() method code");
117
- failed++;
118
- }
119
- } catch(err) {
120
- console.log(` ✗ Error: ${err.message}`);
121
- failed++;
122
- }
123
-
124
- // Test 4: Check method placement
125
- console.log("\n📝 Test 4: Verify method placement in file");
126
- console.log("──────────────────────────────────────────────────");
127
-
128
- try {
129
- const content = fs.readFileSync(queryMethodsPath, 'utf8');
130
-
131
- // Check that new() comes before add()
132
- const newIndex = content.indexOf('new()');
133
- const addIndex = content.indexOf('add(entityValue)');
134
-
135
- if(newIndex > 0 && addIndex > 0 && newIndex < addIndex) {
136
- console.log(" ✓ new() method placed before add()");
137
- console.log(" ✓ Correct location in class structure");
138
- passed++;
139
- } else if(newIndex > 0 && addIndex > 0) {
140
- console.log(" ⚠ new() method exists but not in expected location");
141
- console.log(` ℹ new() at position ${newIndex}, add() at ${addIndex}`);
142
- passed++;
143
- } else {
144
- console.log(" ✗ Could not verify method placement");
145
- failed++;
146
- }
147
- } catch(err) {
148
- console.log(` ✗ Error: ${err.message}`);
149
- failed++;
150
- }
151
-
152
- // Summary
153
- console.log("\n\n╔════════════════════════════════════════════════════════════════╗");
154
- console.log("║ Verification Summary ║");
155
- console.log("╚════════════════════════════════════════════════════════════════╝");
156
-
157
- const total = passed + failed;
158
- const successRate = total > 0 ? Math.round((passed/total)*100) : 0;
159
-
160
- console.log(`\n Total Checks: ${total}`);
161
- console.log(` ✅ Passed: ${passed}`);
162
- console.log(` ❌ Failed: ${failed}`);
163
- console.log(` Success Rate: ${successRate}%\n`);
164
-
165
- if(failed === 0){
166
- console.log("🎉 All verification checks passed!");
167
- console.log("\n✨ .new() Method Successfully Implemented!");
168
- console.log("\n📖 Implementation Details:");
169
- console.log(" File: QueryLanguage/queryMethods.js");
170
- console.log(" Method: new()");
171
- console.log(" Purpose: Create empty entity instances for INSERT operations");
172
- console.log("\n📖 Features:");
173
- console.log(" ✓ Creates entity with __state = 'insert'");
174
- console.log(" ✓ Sets up property getters/setters for all fields");
175
- console.log(" ✓ Tracks dirty fields automatically");
176
- console.log(" ✓ Skips navigational properties (relationships)");
177
- console.log(" ✓ Automatically tracks entity in context");
178
- console.log("\n📖 Usage Example:");
179
- console.log(" const job = context.QaIntelligenceJob.new();");
180
- console.log(" job.annotation_id = 123;");
181
- console.log(" job.job_type = 'auto_rewrite';");
182
- console.log(" job.status = 'queued';");
183
- console.log(" job.created_at = Date.now().toString();");
184
- console.log(" context.saveChanges(); // INSERT INTO QaIntelligenceJob...");
185
- console.log("\n✅ Bug Fixed: context.QaIntelligenceJob.new() now works!\n");
186
- process.exit(0);
187
- } else {
188
- console.log("⚠️ Some verification checks failed.");
189
- console.log(" Review the implementation in queryMethods.js");
190
- process.exit(1);
191
- }