masterrecord 0.3.48 → 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.
@@ -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
+ })();
@@ -116,7 +116,7 @@ test('Query builders correctly skip indexes property', () => {
116
116
  // =============================================================================
117
117
  console.log("\n📋 Test Suite 2: Index Creation with Await Keywords\n");
118
118
 
119
- test('createIndex generates code with await keyword', () => {
119
+ test('New tables do NOT generate separate createIndex calls (createTable handles them)', () => {
120
120
  const migrations = new Migrations();
121
121
  const oldSchema = [];
122
122
  const newSchema = [{
@@ -127,12 +127,12 @@ test('createIndex generates code with await keyword', () => {
127
127
 
128
128
  const migrationCode = migrations.template('TestMigration', oldSchema, newSchema);
129
129
 
130
+ // For new tables, createTable() in schema.js handles indexes - no separate calls needed
130
131
  const createIndexMatches = migrationCode.match(/await this\.createIndex/g);
131
- assert(createIndexMatches && createIndexMatches.length > 0, 'createIndex should have await keyword');
132
- assert(!migrationCode.match(/\n\s+this\.createIndex\(/), 'Should not have createIndex without await');
132
+ assert(!createIndexMatches, 'New tables should NOT have separate createIndex calls');
133
133
  });
134
134
 
135
- test('createCompositeIndex generates code with await keyword', () => {
135
+ test('New tables do NOT generate separate createCompositeIndex calls (createTable handles them)', () => {
136
136
  const migrations = new Migrations();
137
137
  const oldSchema = [];
138
138
  const newSchema = [{
@@ -147,8 +147,50 @@ test('createCompositeIndex generates code with await keyword', () => {
147
147
 
148
148
  const migrationCode = migrations.template('TestMigration', oldSchema, newSchema);
149
149
 
150
+ // For new tables, createTable() in schema.js handles composite indexes
150
151
  const compositeMatches = migrationCode.match(/await this\.createCompositeIndex/g);
151
- assert(compositeMatches && compositeMatches.length > 0, 'createCompositeIndex should have await keyword');
152
+ assert(!compositeMatches, 'New tables should NOT have separate createCompositeIndex calls');
153
+ });
154
+
155
+ test('Adding index to EXISTING table generates createIndex with await', () => {
156
+ const migrations = new Migrations();
157
+ const oldSchema = [{
158
+ __name: 'User',
159
+ id: { name: 'id', type: 'integer', primaryKey: true },
160
+ email: { name: 'email', type: 'text' }
161
+ }];
162
+ const newSchema = [{
163
+ __name: 'User',
164
+ id: { name: 'id', type: 'integer', primaryKey: true },
165
+ email: { name: 'email', type: 'text', indexes: ['idx_user_email'] }
166
+ }];
167
+
168
+ const migrationCode = migrations.template('TestMigration', oldSchema, newSchema);
169
+
170
+ const createIndexMatches = migrationCode.match(/await this\.createIndex/g);
171
+ assert(createIndexMatches && createIndexMatches.length > 0, 'Existing table with new index should have createIndex with await');
172
+ });
173
+
174
+ test('Adding composite index to EXISTING table generates createCompositeIndex with await', () => {
175
+ const migrations = new Migrations();
176
+ const oldSchema = [{
177
+ __name: 'User',
178
+ id: { name: 'id', type: 'integer', primaryKey: true }
179
+ }];
180
+ const newSchema = [{
181
+ __name: 'User',
182
+ id: { name: 'id', type: 'integer', primaryKey: true },
183
+ __compositeIndexes: [{
184
+ name: 'idx_composite',
185
+ columns: ['col1', 'col2'],
186
+ unique: false
187
+ }]
188
+ }];
189
+
190
+ const migrationCode = migrations.template('TestMigration', oldSchema, newSchema);
191
+
192
+ const compositeMatches = migrationCode.match(/await this\.createCompositeIndex/g);
193
+ assert(compositeMatches && compositeMatches.length > 0, 'Existing table with new composite index should have createCompositeIndex with await');
152
194
  });
153
195
 
154
196
  // =============================================================================
@@ -286,9 +328,9 @@ test('Complex scenario: Multiple tables with seed data, indexes, and composite i
286
328
  assert(migrationCode.includes("this.seed('Settings'"), 'Should use this.seed() for Settings');
287
329
  assert(!migrationCode.includes('table.Settings.create'), 'Should NOT use table.Settings.create');
288
330
 
289
- // Check indexes have await
290
- assert(migrationCode.includes('await this.createIndex'), 'Indexes should have await');
291
- assert(migrationCode.includes('await this.createCompositeIndex'), 'Composite indexes should have await');
331
+ // For new tables, createTable() handles indexes no separate calls in template
332
+ assert(!migrationCode.includes('await this.createIndex'), 'New tables should not have separate createIndex calls');
333
+ assert(!migrationCode.includes('await this.createCompositeIndex'), 'New tables should not have separate createCompositeIndex calls');
292
334
  });
293
335
 
294
336
  // =============================================================================
@@ -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);