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.
- package/.claude/settings.local.json +2 -1
- package/context.js +121 -1
- package/package.json +1 -1
- package/test/pool-registry-test.js +303 -0
- package/test/multiContextCacheSimple.test.js +0 -185
- package/test/simple-id-test.js +0 -61
- package/test/single-user-id-test.js +0 -70
- package/test/verifyFindById.js +0 -169
- package/test/verifyNewMethod.js +0 -191
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
}
|
package/test/simple-id-test.js
DELETED
|
@@ -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);
|
package/test/verifyFindById.js
DELETED
|
@@ -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
|
-
}
|
package/test/verifyNewMethod.js
DELETED
|
@@ -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
|
-
}
|