masterrecord 0.3.13 → 0.3.15

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/Migrations/cli.js CHANGED
@@ -35,6 +35,19 @@ function __getMigrationTimestamp(file){
35
35
  }
36
36
  }
37
37
 
38
+ // Helper to cleanup context and exit
39
+ async function __cleanupAndExit(contextInstance, exitCode = 0) {
40
+ try {
41
+ if (contextInstance && typeof contextInstance.close === 'function') {
42
+ await contextInstance.close();
43
+ }
44
+ } catch(err) {
45
+ console.error('Warning: Error during cleanup:', err.message);
46
+ } finally {
47
+ process.exit(exitCode);
48
+ }
49
+ }
50
+
38
51
 
39
52
  const [,, ...args] = process.argv
40
53
 
@@ -52,39 +65,44 @@ program.option('-V', 'output the version');
52
65
  .command('enable-migrations <contextFileName>')
53
66
  .alias('em')
54
67
  .description('Enables the migration in your project by creating a configuration class called ContextSnapShot.json')
55
- .action(function(contextFileName){
56
-
57
- var migration = new Migration();
58
- // location of folder where command is being executed..
59
- var executedLocation = process.cwd();
60
- // find context file from main folder location
61
- var contextFile = migration.findContextFile(executedLocation, contextFileName);
62
- if(!contextFile){
63
- console.error(`\n❌ Error - Cannot read or find Context file '${contextFileName}.js'`);
64
- console.error(`\nSearched in: ${executedLocation}`);
65
- console.error(`\nMake sure your Context file exists and is named correctly.`);
66
- return;
67
- }
68
- var snap = {
69
- file : contextFile,
70
- executedLocation : executedLocation,
71
- contextEntities : [],
72
- contextFileName: contextFileName.toLowerCase()
73
- }
74
-
75
- migration.createSnapShot(snap);
76
- console.log("✓ Migration enabled successfully")
68
+ .action(async function(contextFileName){
69
+ try {
70
+ var migration = new Migration();
71
+ // location of folder where command is being executed..
72
+ var executedLocation = process.cwd();
73
+ // find context file from main folder location
74
+ var contextFile = migration.findContextFile(executedLocation, contextFileName);
75
+ if(!contextFile){
76
+ console.error(`\n❌ Error - Cannot read or find Context file '${contextFileName}.js'`);
77
+ console.error(`\nSearched in: ${executedLocation}`);
78
+ console.error(`\nMake sure your Context file exists and is named correctly.`);
79
+ process.exit(1);
80
+ }
81
+ var snap = {
82
+ file : contextFile,
83
+ executedLocation : executedLocation,
84
+ contextEntities : [],
85
+ contextFileName: contextFileName.toLowerCase()
86
+ }
77
87
 
88
+ migration.createSnapShot(snap);
89
+ console.log("✓ Migration enabled successfully")
90
+ process.exit(0);
91
+ } catch(err) {
92
+ console.error('Error:', err);
93
+ process.exit(1);
94
+ }
78
95
  });
79
96
 
80
97
  program
81
98
  .command('ensure-database <contextFileName>')
82
99
  .alias('ed')
83
100
  .description('Ensure the target database exists for the given context (MySQL)')
84
- .action(function(contextFileName){
101
+ .action(async function(contextFileName){
85
102
  var executedLocation = process.cwd();
86
103
  contextFileName = contextFileName.toLowerCase();
87
104
  var migration = new Migration();
105
+ var contextInstance = null;
88
106
  try{
89
107
  var files = globSearch.sync(`**/*${contextFileName}_contextSnapShot.json`, { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
90
108
  var file = files && files[0] ? path.resolve(executedLocation, files[0]) : null;
@@ -107,7 +125,7 @@ program.option('-V', 'output the version');
107
125
  migrationFiles = (migrationFiles || []).map(f => path.resolve(migBase, f));
108
126
  if(!(migrationFiles && migrationFiles.length)){
109
127
  console.log("Error - Cannot read or find migration file");
110
- return;
128
+ process.exit(1);
111
129
  }
112
130
  var mFiles = migrationFiles.slice().sort(function(a, b){
113
131
  return __getMigrationTimestamp(a) - __getMigrationTimestamp(b);
@@ -125,33 +143,41 @@ program.option('-V', 'output the version');
125
143
  console.error(`\nStack trace:`);
126
144
  console.error(err.stack);
127
145
  }
128
- return;
146
+ process.exit(1);
129
147
  }
130
148
 
131
149
  // Use the migration class (extends schema) so createdatabase is available
132
150
  var MigrationCtor = require(mFile);
133
151
  var mig = new MigrationCtor(ContextCtor);
152
+ contextInstance = mig._context || mig.context || null;
153
+
134
154
  if(typeof mig.createdatabase === 'function'){
135
155
  try{
136
156
  mig.createdatabase();
137
157
  console.log('✓ Database ensured');
158
+ await __cleanupAndExit(contextInstance, 0);
138
159
  }catch(err){
139
160
  console.error(`\n❌ Error creating database:`);
140
161
  console.error(err.message);
162
+ await __cleanupAndExit(contextInstance, 1);
141
163
  }
142
164
  } else if(typeof mig.createDatabase === 'function'){
143
165
  try{
144
166
  mig.createDatabase();
145
167
  console.log('✓ Database ensured');
168
+ await __cleanupAndExit(contextInstance, 0);
146
169
  }catch(err){
147
170
  console.error(`\n❌ Error creating database:`);
148
171
  console.error(err.message);
172
+ await __cleanupAndExit(contextInstance, 1);
149
173
  }
150
174
  } else {
151
175
  console.error('❌ Error - Migration class missing createDatabase method');
176
+ await __cleanupAndExit(contextInstance, 1);
152
177
  }
153
178
  }catch(e){
154
179
  console.log('Error - Cannot read or find file ', e);
180
+ await __cleanupAndExit(contextInstance, 1);
155
181
  }
156
182
  });
157
183
 
@@ -193,7 +219,7 @@ program.option('-V', 'output the version');
193
219
  program
194
220
  .command('add-migration <name> <contextFileName>')
195
221
  .alias('am')
196
- .action(function(name, contextFileName){
222
+ .action(async function(name, contextFileName){
197
223
  var executedLocation = process.cwd();
198
224
  contextFileName = contextFileName.toLowerCase();
199
225
  var migration = new Migration();
@@ -263,12 +289,28 @@ program.option('-V', 'output the version');
263
289
  }
264
290
  var migrationDate = Date.now();
265
291
  var outputFile = `${migBase}/${migrationDate}_${name}_migration.js`
266
- fs.writeFile(outputFile, newEntity, 'utf8', function (err) {
267
- if (err) return console.log("--- Error running cammand, re-run command add-migration ---- ", err);
292
+ fs.writeFile(outputFile, newEntity, 'utf8', async function (err) {
293
+ if (err) {
294
+ console.log("--- Error running cammand, re-run command add-migration ---- ", err);
295
+ if (contextInstance && typeof contextInstance.close === 'function') {
296
+ await contextInstance.close();
297
+ }
298
+ process.exit(1);
299
+ }
300
+ console.log(`✓ Migration '${name}' created successfully at ${outputFile}`);
301
+
302
+ // Close database connections to prevent hanging
303
+ if (contextInstance && typeof contextInstance.close === 'function') {
304
+ await contextInstance.close();
305
+ }
306
+ process.exit(0);
268
307
  });
269
- console.log(`✓ Migration '${name}' created successfully at ${outputFile}`);
270
308
  }catch (e){
271
309
  console.log("Error - Cannot read or find file ", e);
310
+ if (contextInstance && typeof contextInstance.close === 'function') {
311
+ await contextInstance.close();
312
+ }
313
+ process.exit(1);
272
314
  }
273
315
  });
274
316
 
@@ -276,10 +318,11 @@ program.option('-V', 'output the version');
276
318
  .command('update-database <contextFileName>')
277
319
  .alias('ud')
278
320
  .description('Apply pending migrations to database - up method call')
279
- .action(function(contextFileName){
321
+ .action(async function(contextFileName){
280
322
  var executedLocation = process.cwd();
281
323
  contextFileName = contextFileName.toLowerCase();
282
324
  var migration = new Migration();
325
+ var contextInstance = null;
283
326
  try{
284
327
  console.log(`\n🔍 Searching for context snapshot '${contextFileName}_contextSnapShot.json'...`);
285
328
  // find context snapshot (cwd-based glob)
@@ -291,7 +334,7 @@ program.option('-V', 'output the version');
291
334
  console.error(`\nSearched for: ${contextFileName}_contextSnapShot.json`);
292
335
  console.error(`Searched in: ${executedLocation}`);
293
336
  console.error(`\n💡 Solution: Run 'masterrecord enable-migrations ${contextFileName}' first`);
294
- return;
337
+ await __cleanupAndExit(contextInstance, 1);
295
338
  }
296
339
 
297
340
  console.log(`✓ Found snapshot: ${file}`);
@@ -303,7 +346,7 @@ program.option('-V', 'output the version');
303
346
  console.error(`\n❌ Error - Cannot load context snapshot`);
304
347
  console.error(`\nFile: ${file}`);
305
348
  console.error(`Details: ${err.message}`);
306
- return;
349
+ await __cleanupAndExit(contextInstance, 1);
307
350
  }
308
351
 
309
352
  const snapDir = path.dirname(file);
@@ -318,7 +361,7 @@ program.option('-V', 'output the version');
318
361
  console.error(`\n❌ Error - No migration files found`);
319
362
  console.error(`\nSearched in: ${migBase}`);
320
363
  console.error(`\n💡 Solution: Run 'masterrecord add-migration Init ${contextFileName}' to create your first migration`);
321
- return;
364
+ await __cleanupAndExit(contextInstance, 1);
322
365
  }
323
366
 
324
367
  // sort by timestamp prefix or file mtime as fallback
@@ -343,13 +386,12 @@ program.option('-V', 'output the version');
343
386
  console.error(`\nStack trace:`);
344
387
  console.error(err.stack);
345
388
  }
346
- return;
389
+ await __cleanupAndExit(contextInstance, 1);
347
390
  }
348
391
 
349
392
  console.log(`✓ Context file loaded successfully`);
350
393
  console.log(`\n🔍 Instantiating Context (this will create the database if it doesn't exist)...`);
351
394
 
352
- var contextInstance;
353
395
  try{
354
396
  contextInstance = new ContextCtor();
355
397
  }catch(err){
@@ -367,7 +409,7 @@ program.option('-V', 'output the version');
367
409
  }
368
410
  console.error(`\n💡 Check your environment config file (e.g., config/environments/env.development.json)`);
369
411
  console.error(`💡 Make sure you're running: master=development masterrecord update-database ${contextFileName}`);
370
- return;
412
+ await __cleanupAndExit(contextInstance, 1);
371
413
  }
372
414
 
373
415
  console.log(`✓ Context instantiated successfully`);
@@ -414,7 +456,7 @@ program.option('-V', 'output the version');
414
456
  console.error(`\nStack trace:`);
415
457
  console.error(err.stack);
416
458
  }
417
- return;
459
+ await __cleanupAndExit(contextInstance, 1);
418
460
  }
419
461
 
420
462
  console.log(`\n💾 Updating snapshot...`);
@@ -441,6 +483,7 @@ program.option('-V', 'output the version');
441
483
  }
442
484
  }
443
485
 
486
+ await __cleanupAndExit(contextInstance, 0);
444
487
  }catch (e){
445
488
  console.error(`\n❌ Unexpected error during update-database`);
446
489
  console.error(`\nDetails: ${e.message}`);
@@ -448,6 +491,7 @@ program.option('-V', 'output the version');
448
491
  console.error(`\nStack trace:`);
449
492
  console.error(e.stack);
450
493
  }
494
+ await __cleanupAndExit(contextInstance, 1);
451
495
  }
452
496
  });
453
497
 
@@ -456,10 +500,11 @@ program.option('-V', 'output the version');
456
500
  .command('update-database-down <contextFileName>')
457
501
  .alias('udd')
458
502
  .description('Run the latest migration down method for the given context')
459
- .action(function(contextFileName){
503
+ .action(async function(contextFileName){
460
504
  var executedLocation = process.cwd();
461
505
  contextFileName = contextFileName.toLowerCase();
462
506
  var migration = new Migration();
507
+ var contextInstance = null;
463
508
  try{
464
509
  var files = globSearch.sync(`**/*${contextFileName}_contextSnapShot.json`, { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
465
510
  var file = files && files[0] ? path.resolve(executedLocation, files[0]) : null;
@@ -541,9 +586,11 @@ program.option('-V', 'output the version');
541
586
  }
542
587
  migration.createSnapShot(snap);
543
588
  console.log("✓ Database rolled back successfully");
589
+ await __cleanupAndExit(contextInstance, 0);
544
590
 
545
591
  }catch (e){
546
592
  console.log("Error - Cannot read or find file ", e);
593
+ await __cleanupAndExit(contextInstance, 1);
547
594
  }
548
595
  });
549
596
 
@@ -552,10 +599,11 @@ program.option('-V', 'output the version');
552
599
  .command('update-database-restart <contextFileName>')
553
600
  .alias('udr')
554
601
  .description('Apply pending migrations to database - up method call')
555
- .action(function(contextFileName){
602
+ .action(async function(contextFileName){
556
603
  var executedLocation = process.cwd();
557
604
  contextFileName = contextFileName.toLowerCase();
558
605
  var migration = new Migration();
606
+ var contextInstance = null;
559
607
  try{
560
608
  // find context snapshot (cwd-based glob)
561
609
  var files = globSearch.sync(`**/*${contextFileName}_contextSnapShot.json`, { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
@@ -629,13 +677,15 @@ program.option('-V', 'output the version');
629
677
  contextEntities : cleanEntities,
630
678
  contextFileName: contextFileName
631
679
  }
632
-
680
+
633
681
  migration.createSnapShot(snap);
634
682
  console.log("✓ Database restarted and updated successfully");
635
-
683
+ await __cleanupAndExit(contextInstance, 0);
684
+
636
685
  }
637
686
  catch (e){
638
687
  console.log("Error - Cannot read or find file ", e);
688
+ await __cleanupAndExit(contextInstance, 1);
639
689
  }
640
690
  });
641
691
 
@@ -677,10 +727,11 @@ program.option('-V', 'output the version');
677
727
  .command('update-database-target <migrationFileName>')
678
728
  .alias('udt')
679
729
  .description('Apply pending migrations to database - down method call')
680
- .action(function(migrationFileName){
730
+ .action(async function(migrationFileName){
681
731
  // this will call all the down methods until it gets to the one your looking for. First it needs to validate that there is such a file.
682
732
  var executedLocation = process.cwd();
683
733
  var migration = new Migration();
734
+ var contextInstance = null;
684
735
  try{
685
736
  // Accept either a bare filename or a path; normalize to basename
686
737
  var targetName = path.basename(migrationFileName);
@@ -785,9 +836,11 @@ program.option('-V', 'output the version');
785
836
  }
786
837
  migration.createSnapShot(snap);
787
838
  console.log("✓ Database rolled back to target migration successfully");
839
+ await __cleanupAndExit(contextInstance, 0);
788
840
 
789
841
  }catch (e){
790
842
  console.log("Error - Cannot read or find file ", e);
843
+ await __cleanupAndExit(contextInstance, 1);
791
844
  }
792
845
  });
793
846
 
@@ -796,8 +849,9 @@ program.option('-V', 'output the version');
796
849
  .command('add-migration-all <name>')
797
850
  .alias('ama')
798
851
  .description('Create a migration with the given name for all detected contexts')
799
- .action(function(name){
852
+ .action(async function(name){
800
853
  var executedLocation = process.cwd();
854
+ var contextInstances = [];
801
855
  try{
802
856
  var snapshotFiles = globSearch.sync('**/*_contextSnapShot.json', { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
803
857
  if(!(snapshotFiles && snapshotFiles.length)){
@@ -825,6 +879,7 @@ program.option('-V', 'output the version');
825
879
  let contextInstance;
826
880
  try{
827
881
  contextInstance = new ContextCtor();
882
+ contextInstances.push(contextInstance);
828
883
  }catch(err){
829
884
  console.error(`⚠️ Skipping ${path.basename(contextAbs)}: failed to construct Context`);
830
885
  console.error(` Details: ${err.message}`);
@@ -854,8 +909,22 @@ program.option('-V', 'output the version');
854
909
  if(created === 0){
855
910
  console.log('No migrations created.');
856
911
  }
912
+ // Cleanup all contexts
913
+ for(const ctx of contextInstances) {
914
+ if (ctx && typeof ctx.close === 'function') {
915
+ await ctx.close();
916
+ }
917
+ }
918
+ process.exit(0);
857
919
  }catch(e){
858
920
  console.log('Error - Cannot create migrations for all contexts ', e);
921
+ // Cleanup all contexts
922
+ for(const ctx of contextInstances) {
923
+ if (ctx && typeof ctx.close === 'function') {
924
+ await ctx.close();
925
+ }
926
+ }
927
+ process.exit(1);
859
928
  }
860
929
  });
861
930
 
@@ -863,8 +932,9 @@ program.option('-V', 'output the version');
863
932
  .command('update-database-all')
864
933
  .alias('uda')
865
934
  .description('Scan the project for *Context.js files and run update-database on each')
866
- .action(function(){
935
+ .action(async function(){
867
936
  var executedLocation = process.cwd();
937
+ var contextInstances = [];
868
938
  try{
869
939
  // Find all context snapshots and run update per snapshot (avoids unrelated framework contexts)
870
940
  var snapshotFiles = globSearch.sync('**/*_contextSnapShot.json', { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
@@ -924,6 +994,7 @@ program.option('-V', 'output the version');
924
994
  var contextInstance;
925
995
  try{
926
996
  contextInstance = new ContextCtor();
997
+ contextInstances.push(contextInstance);
927
998
  }catch(err){
928
999
  console.error(`⚠️ Skipping ${entry.ctxName}: failed to construct Context`);
929
1000
  console.error(` Details: ${err.message}`);
@@ -947,8 +1018,22 @@ program.option('-V', 'output the version');
947
1018
  console.log('Error updating context: ', errCtx);
948
1019
  }
949
1020
  }
1021
+ // Cleanup all contexts
1022
+ for(const ctx of contextInstances) {
1023
+ if (ctx && typeof ctx.close === 'function') {
1024
+ await ctx.close();
1025
+ }
1026
+ }
1027
+ process.exit(0);
950
1028
  }catch(e){
951
1029
  console.log('Error - Cannot read or find file ', e);
1030
+ // Cleanup all contexts
1031
+ for(const ctx of contextInstances) {
1032
+ if (ctx && typeof ctx.close === 'function') {
1033
+ await ctx.close();
1034
+ }
1035
+ }
1036
+ process.exit(1);
952
1037
  }
953
1038
  });
954
1039
 
package/context.js CHANGED
@@ -988,6 +988,9 @@ class context {
988
988
  case 'delete':
989
989
  toDelete.push(currentModel);
990
990
  break;
991
+ case 'track':
992
+ // Entity is tracked but unmodified - skip during saveChanges
993
+ break;
991
994
  default:
992
995
  console.warn(`[Context] Unknown entity state: ${currentModel.__state}`);
993
996
  }
@@ -1277,6 +1280,32 @@ class context {
1277
1280
  this.clearQueryCache();
1278
1281
  }
1279
1282
 
1283
+ /**
1284
+ * Close database connections and cleanup resources
1285
+ *
1286
+ * Call this when shutting down your application or after CLI operations
1287
+ * to properly close database connection pools and prevent hanging processes.
1288
+ *
1289
+ * @returns {Promise<void>|void} Promise for async databases (PostgreSQL), void for sync databases
1290
+ *
1291
+ * @example
1292
+ * // PostgreSQL (async)
1293
+ * const db = new AppContext();
1294
+ * // ... do work ...
1295
+ * await db.close(); // Close connection pool
1296
+ *
1297
+ * @example
1298
+ * // MySQL/SQLite (sync)
1299
+ * const db = new AppContext();
1300
+ * // ... do work ...
1301
+ * db.close(); // Close connections
1302
+ */
1303
+ async close() {
1304
+ if (this._SQLEngine && typeof this._SQLEngine.close === 'function') {
1305
+ return await this._SQLEngine.close();
1306
+ }
1307
+ }
1308
+
1280
1309
  /**
1281
1310
  * Attach a detached entity and mark it as modified
1282
1311
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "masterrecord",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
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": {
package/readme.md CHANGED
@@ -64,9 +64,9 @@ npm install masterrecord better-sqlite3 # SQLite
64
64
  ### Dependencies
65
65
 
66
66
  MasterRecord includes the following database drivers by default:
67
- - `pg@^8.16.3` - PostgreSQL
67
+ - `pg@^8.17.2` - PostgreSQL
68
68
  - `sync-mysql2@^1.0.8` - MySQL
69
- - `better-sqlite3@^12.6.0` - SQLite
69
+ - `better-sqlite3@^12.6.2` - SQLite
70
70
 
71
71
  ## Two Patterns: Entity Framework & Active Record
72
72
 
@@ -152,7 +152,7 @@ masterrecord enable-migrations AppContext
152
152
  masterrecord add-migration InitialCreate AppContext
153
153
 
154
154
  # Apply migrations
155
- masterrecord migrate AppContext
155
+ masterrecord update-database AppContext
156
156
  ```
157
157
 
158
158
  ### 4. Query Your Data
@@ -421,7 +421,7 @@ console.log(loaded.tags); // ['admin', 'moderator'] - JavaScript array!
421
421
 
422
422
  ```javascript
423
423
  // Find all
424
- const users = db.User.all();
424
+ const users = db.User.toList();
425
425
 
426
426
  // Find by primary key
427
427
  const user = db.User.findById(123);
@@ -578,7 +578,7 @@ masterrecord enable-migrations AppContext
578
578
  masterrecord add-migration MigrationName AppContext
579
579
 
580
580
  # Apply migrations
581
- masterrecord migrate AppContext
581
+ masterrecord update-database AppContext
582
582
 
583
583
  # List migrations
584
584
  masterrecord get-migrations AppContext
@@ -586,7 +586,7 @@ masterrecord get-migrations AppContext
586
586
  # Multi-context commands
587
587
  masterrecord enable-migrations-all # Enable for all contexts
588
588
  masterrecord add-migration-all Init # Create migration for all
589
- masterrecord migrate-all # Apply all pending migrations
589
+ masterrecord update-database-all # Apply all pending migrations
590
590
  ```
591
591
 
592
592
  ### Migration File Structure
@@ -859,7 +859,7 @@ Configure caching via environment variables:
859
859
 
860
860
  ```bash
861
861
  # Development (.env)
862
- QUERY_CACHE_TTL=5000 # TTL in milliseconds (default: 5 seconds - request-scoped)
862
+ QUERY_CACHE_TTL=5000 # TTL in milliseconds (5000ms = 5 seconds - request-scoped)
863
863
  QUERY_CACHE_SIZE=1000 # Max cache entries (default: 1000)
864
864
  QUERY_CACHE_ENABLED=true # Enable/disable globally (default: true)
865
865
 
@@ -885,7 +885,7 @@ const liveData = db.Analytics
885
885
  .toList(); // No caching (default)
886
886
 
887
887
  // OPT-IN: Cache reference data
888
- const categories = db.Categories.cache().toList(); // Cached for 5 minutes
888
+ const categories = db.Categories.cache().toList(); // Cached for 5 seconds (default TTL)
889
889
  const settings = db.Settings.cache().toList(); // Cached
890
890
  const countries = db.Countries.cache().toList(); // Cached
891
891
 
@@ -1155,7 +1155,7 @@ await analyticsDb.saveChanges();
1155
1155
 
1156
1156
  ```bash
1157
1157
  # Migrate all contexts at once
1158
- masterrecord migrate-all
1158
+ masterrecord update-database-all
1159
1159
  ```
1160
1160
 
1161
1161
  ### Raw SQL Queries
@@ -1163,6 +1163,10 @@ masterrecord migrate-all
1163
1163
  When you need full control:
1164
1164
 
1165
1165
  ```javascript
1166
+ // ⚠️ Advanced: Direct SQL execution (using internal API)
1167
+ // For complex queries not supported by the query builder
1168
+ // Note: This is an internal API. Prefer using the query builder when possible.
1169
+
1166
1170
  // PostgreSQL parameterized query
1167
1171
  const users = await db._SQLEngine.exec(
1168
1172
  'SELECT * FROM "User" WHERE age > $1 AND status = $2',
@@ -1220,12 +1224,11 @@ context.setQueryCacheEnabled(bool) // Enable/disable caching
1220
1224
  .cache() // Enable caching for this query (opt-in)
1221
1225
 
1222
1226
  // Terminal methods (execute query)
1223
- .toList() // Return array
1227
+ .toList() // Return array of all records
1224
1228
  .single() // Return one or null
1225
1229
  .first() // Return first or null
1226
1230
  .count() // Return count
1227
1231
  .any() // Return boolean
1228
- .all() // Return all records
1229
1232
 
1230
1233
  // Convenience methods
1231
1234
  .findById(id) // Find by primary key
@@ -1456,7 +1459,7 @@ const recentUsers = db.User
1456
1459
  .toList();
1457
1460
 
1458
1461
  // ❌ BAD: Load everything
1459
- const allUsers = db.User.all();
1462
+ const allUsers = db.User.toList();
1460
1463
  ```
1461
1464
 
1462
1465
  ### 5. Use Connection Pooling (PostgreSQL)
@@ -1513,7 +1516,7 @@ function getUser(userId) {
1513
1516
 
1514
1517
  ```bash
1515
1518
  # Error: Cannot find module 'pg'
1516
- npm install pg@^8.16.3
1519
+ npm install pg@^8.17.2
1517
1520
 
1518
1521
  # Error: Connection refused
1519
1522
  # Check PostgreSQL is running: sudo service postgresql status
@@ -1543,7 +1546,7 @@ GRANT ALL PRIVILEGES ON myapp.* TO 'user'@'localhost';
1543
1546
  # Cannot find context file
1544
1547
  # Ensure you're running from project root
1545
1548
  cd /path/to/project
1546
- masterrecord migrate AppContext
1549
+ masterrecord update-database AppContext
1547
1550
 
1548
1551
  # No migrations found
1549
1552
  # Check migrations directory exists
@@ -1577,14 +1580,14 @@ user.name = null; // Error if name is { nullable: false }
1577
1580
 
1578
1581
  | Component | Version | Notes |
1579
1582
  |---------------|---------------|------------------------------------------|
1580
- | MasterRecord | 0.3.0+ | Current version with PostgreSQL support |
1583
+ | MasterRecord | 0.3.13 | Current version with PostgreSQL support |
1581
1584
  | Node.js | 14+ | Async/await support required |
1582
1585
  | PostgreSQL | 9.6+ (12+) | Tested with 12, 13, 14, 15, 16 |
1583
1586
  | MySQL | 5.7+ (8.0+) | Tested with 8.0+ |
1584
1587
  | SQLite | 3.x | Any recent version |
1585
- | pg | 8.16.3+ | PostgreSQL driver |
1588
+ | pg | 8.17.2+ | PostgreSQL driver |
1586
1589
  | sync-mysql2 | 1.0.8+ | MySQL driver |
1587
- | better-sqlite3| 12.6.0+ | SQLite driver |
1590
+ | better-sqlite3| 12.6.2+ | SQLite driver |
1588
1591
 
1589
1592
  ## Documentation
1590
1593
 
@@ -1630,11 +1633,11 @@ Created by Alexander Rich
1630
1633
 
1631
1634
  ---
1632
1635
 
1633
- ## Recent Improvements (v1.0.1)
1636
+ ## Recent Improvements (v0.3.13)
1634
1637
 
1635
1638
  MasterRecord has been upgraded to meet **FAANG engineering standards** (Google/Meta/Amazon) with critical bug fixes and performance improvements:
1636
1639
 
1637
- ### Migration System Fixes (v1.0.1)
1640
+ ### Migration System Fixes (v0.3.13)
1638
1641
 
1639
1642
  **Critical Path Bug Fixed:**
1640
1643
  - ✅ **Duplicate db/migrations Path Fixed** - Resolved bug where snapshot files were created with duplicate nested paths
@@ -1657,7 +1660,7 @@ MasterRecord has been upgraded to meet **FAANG engineering standards** (Google/M
1657
1660
  2. **From migration directory** - cd into the specific migration area and run CLI there:
1658
1661
  ```bash
1659
1662
  cd components/qa/db/migrations
1660
- npx masterrecord update-database ../../app/models/qaContext
1663
+ masterrecord enable-migrations qacontext
1661
1664
  ```
1662
1665
  - MasterRecord uses intelligent path resolution to locate migrations regardless of where you run the command
1663
1666
 
@@ -1708,7 +1711,7 @@ try {
1708
1711
  }
1709
1712
  ```
1710
1713
 
1711
- ### Insert Manager Improvements (v1.0.1)
1714
+ ### Insert Manager Improvements (v0.3.13)
1712
1715
 
1713
1716
  **Security Fixes:**
1714
1717
  - ✅ **SQL Injection Prevention** - Added identifier validation for dynamic query construction
@@ -1776,7 +1779,7 @@ if(entityProperty.type === "hasMany"){
1776
1779
  }
1777
1780
  // ... 50 lines later, nearly identical code for hasManyThrough
1778
1781
 
1779
- // AFTER (v1.0.0) - secure and DRY:
1782
+ // AFTER (v0.3.13) - secure and DRY:
1780
1783
  if (entityProperty.type === RELATIONSHIP_TYPES.HAS_MANY) {
1781
1784
  this._processArrayRelationship(propertyModel, entityProperty, property, currentModel, SQL, RELATIONSHIP_TYPES.HAS_MANY);
1782
1785
  }
@@ -1815,12 +1818,13 @@ $ grep -A1 "catch.*{$" insertManager.js | grep "^\s*}$"
1815
1818
 
1816
1819
  **PostgreSQL users must now await `env()`:**
1817
1820
  ```javascript
1818
- // OLD:
1821
+ // PostgreSQL (async/await REQUIRED):
1819
1822
  const db = new AppContext();
1823
+ await db.env('./config/environments'); // Must await for PostgreSQL
1820
1824
 
1821
- // NEW:
1825
+ // MySQL/SQLite (synchronous - no await):
1822
1826
  const db = new AppContext();
1823
- await db.env('./config/environments'); // Must await for PostgreSQL
1827
+ db.env('./config/environments'); // No await needed for MySQL/SQLite
1824
1828
  ```
1825
1829
 
1826
1830
  **For more details, see:** `CHANGES.md`