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 +129 -44
- package/context.js +29 -0
- package/package.json +1 -1
- package/readme.md +29 -25
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
67
|
+
- `pg@^8.17.2` - PostgreSQL
|
|
68
68
|
- `sync-mysql2@^1.0.8` - MySQL
|
|
69
|
-
- `better-sqlite3@^12.6.
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
1588
|
+
| pg | 8.17.2+ | PostgreSQL driver |
|
|
1586
1589
|
| sync-mysql2 | 1.0.8+ | MySQL driver |
|
|
1587
|
-
| better-sqlite3| 12.6.
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
//
|
|
1821
|
+
// PostgreSQL (async/await REQUIRED):
|
|
1819
1822
|
const db = new AppContext();
|
|
1823
|
+
await db.env('./config/environments'); // Must await for PostgreSQL
|
|
1820
1824
|
|
|
1821
|
-
//
|
|
1825
|
+
// MySQL/SQLite (synchronous - no await):
|
|
1822
1826
|
const db = new AppContext();
|
|
1823
|
-
|
|
1827
|
+
db.env('./config/environments'); // No await needed for MySQL/SQLite
|
|
1824
1828
|
```
|
|
1825
1829
|
|
|
1826
1830
|
**For more details, see:** `CHANGES.md`
|