masterrecord 0.2.11 → 0.2.13
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 +66 -84
- package/Migrations/migrationMySQLQuery.js +10 -10
- package/package.json +1 -1
package/Migrations/cli.js
CHANGED
|
@@ -83,9 +83,8 @@ program.option('-V', 'output the version');
|
|
|
83
83
|
contextFileName = contextFileName.toLowerCase();
|
|
84
84
|
var migration = new Migration();
|
|
85
85
|
try{
|
|
86
|
-
var
|
|
87
|
-
var
|
|
88
|
-
var file = files && files[0];
|
|
86
|
+
var files = globSearch.sync(`**/*${contextFileName}_contextSnapShot.json`, { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
|
|
87
|
+
var file = files && files[0] ? path.resolve(executedLocation, files[0]) : null;
|
|
89
88
|
if(!file){
|
|
90
89
|
console.log(`Error - Cannot read or find Context snapshot '${contextFileName}_contextSnapShot.json' in '${executedLocation}'.`);
|
|
91
90
|
return;
|
|
@@ -98,8 +97,8 @@ program.option('-V', 'output the version');
|
|
|
98
97
|
return;
|
|
99
98
|
}
|
|
100
99
|
// Find latest migration file (so we can use its class which extends schema)
|
|
101
|
-
var
|
|
102
|
-
|
|
100
|
+
var migrationFiles = globSearch.sync(`**/*_migration.js`, { cwd: contextSnapshot.migrationFolder, dot: true, windowsPathsNoEscape: true });
|
|
101
|
+
migrationFiles = (migrationFiles || []).map(f => path.resolve(contextSnapshot.migrationFolder, f));
|
|
103
102
|
if(!(migrationFiles && migrationFiles.length)){
|
|
104
103
|
console.log("Error - Cannot read or find migration file");
|
|
105
104
|
return;
|
|
@@ -178,9 +177,8 @@ program.option('-V', 'output the version');
|
|
|
178
177
|
var migration = new Migration();
|
|
179
178
|
try{
|
|
180
179
|
// find context file from main folder location
|
|
181
|
-
var
|
|
182
|
-
var
|
|
183
|
-
var file = files && files[0];
|
|
180
|
+
var files = globSearch.sync(`**/*${contextFileName}_contextSnapShot.json`, { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
|
|
181
|
+
var file = files && files[0] ? path.resolve(executedLocation, files[0]) : null;
|
|
184
182
|
if(!file){
|
|
185
183
|
console.log(`Error - Cannot read or find Context snapshot '${contextFileName}_contextSnapShot.json' in '${executedLocation}'. Run 'masterrecord enable-migrations ${contextFileName}'.`);
|
|
186
184
|
return;
|
|
@@ -229,14 +227,13 @@ program.option('-V', 'output the version');
|
|
|
229
227
|
contextFileName = contextFileName.toLowerCase();
|
|
230
228
|
var migration = new Migration();
|
|
231
229
|
try{
|
|
232
|
-
// find context
|
|
233
|
-
var
|
|
234
|
-
var
|
|
235
|
-
var file = files[0];
|
|
230
|
+
// find context snapshot (cwd-based glob)
|
|
231
|
+
var files = globSearch.sync(`**/*${contextFileName}_contextSnapShot.json`, { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
|
|
232
|
+
var file = files && files[0] ? path.resolve(executedLocation, files[0]) : null;
|
|
236
233
|
if(file){
|
|
237
234
|
var contextSnapshot = require(file);
|
|
238
|
-
var
|
|
239
|
-
|
|
235
|
+
var migrationFiles = globSearch.sync(`**/*_migration.js`, { cwd: contextSnapshot.migrationFolder, dot: true, windowsPathsNoEscape: true });
|
|
236
|
+
migrationFiles = (migrationFiles || []).map(f => path.resolve(contextSnapshot.migrationFolder, f));
|
|
240
237
|
if( migrationFiles && migrationFiles.length){
|
|
241
238
|
// sort by timestamp prefix or file mtime as fallback
|
|
242
239
|
var mFiles = migrationFiles.slice().sort(function(a, b){
|
|
@@ -287,9 +284,8 @@ program.option('-V', 'output the version');
|
|
|
287
284
|
contextFileName = contextFileName.toLowerCase();
|
|
288
285
|
var migration = new Migration();
|
|
289
286
|
try{
|
|
290
|
-
var
|
|
291
|
-
var
|
|
292
|
-
var file = files && files[0];
|
|
287
|
+
var files = globSearch.sync(`**/*${contextFileName}_contextSnapShot.json`, { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
|
|
288
|
+
var file = files && files[0] ? path.resolve(executedLocation, files[0]) : null;
|
|
293
289
|
if(!file){
|
|
294
290
|
console.log(`Error - Cannot read or find Context snapshot '${contextFileName}_contextSnapShot.json' in '${executedLocation}'.`);
|
|
295
291
|
return;
|
|
@@ -301,8 +297,8 @@ program.option('-V', 'output the version');
|
|
|
301
297
|
console.log(`Error - Cannot read context snapshot at '${file}'.`);
|
|
302
298
|
return;
|
|
303
299
|
}
|
|
304
|
-
var
|
|
305
|
-
|
|
300
|
+
var migrationFiles = globSearch.sync(`**/*_migration.js`, { cwd: contextSnapshot.migrationFolder, dot: true, windowsPathsNoEscape: true });
|
|
301
|
+
migrationFiles = (migrationFiles || []).map(f => path.resolve(contextSnapshot.migrationFolder, f));
|
|
306
302
|
if(!(migrationFiles && migrationFiles.length)){
|
|
307
303
|
console.log("Error - Cannot read or find migration file");
|
|
308
304
|
return;
|
|
@@ -365,10 +361,9 @@ program.option('-V', 'output the version');
|
|
|
365
361
|
contextFileName = contextFileName.toLowerCase();
|
|
366
362
|
var migration = new Migration();
|
|
367
363
|
try{
|
|
368
|
-
// find context
|
|
369
|
-
var
|
|
370
|
-
var
|
|
371
|
-
var file = files[0];
|
|
364
|
+
// find context snapshot (cwd-based glob)
|
|
365
|
+
var files = globSearch.sync(`**/*${contextFileName}_contextSnapShot.json`, { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
|
|
366
|
+
var file = files && files[0] ? path.resolve(executedLocation, files[0]) : null;
|
|
372
367
|
if(!file){
|
|
373
368
|
console.log(`Error - Cannot read or find Context snapshot '${contextFileName}_contextSnapShot.json' in '${executedLocation}'.`);
|
|
374
369
|
return;
|
|
@@ -380,8 +375,8 @@ program.option('-V', 'output the version');
|
|
|
380
375
|
console.log(`Error - Cannot read context snapshot at '${file}'.`);
|
|
381
376
|
return;
|
|
382
377
|
}
|
|
383
|
-
var
|
|
384
|
-
|
|
378
|
+
var migrationFiles = globSearch.sync(`**/*_migration.js`, { cwd: contextSnapshot.migrationFolder, dot: true, windowsPathsNoEscape: true });
|
|
379
|
+
migrationFiles = (migrationFiles || []).map(f => path.resolve(contextSnapshot.migrationFolder, f));
|
|
385
380
|
if(!(migrationFiles && migrationFiles.length)){
|
|
386
381
|
console.log("Error - Cannot read or find migration file");
|
|
387
382
|
return;
|
|
@@ -437,9 +432,8 @@ program.option('-V', 'output the version');
|
|
|
437
432
|
.action(function(contextFileName){
|
|
438
433
|
var executedLocation = process.cwd();
|
|
439
434
|
contextFileName = contextFileName.toLowerCase();
|
|
440
|
-
var
|
|
441
|
-
var
|
|
442
|
-
var file = files[0];
|
|
435
|
+
var files = globSearch.sync(`**/*${contextFileName}_contextSnapShot.json`, { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
|
|
436
|
+
var file = files && files[0] ? path.resolve(executedLocation, files[0]) : null;
|
|
443
437
|
if(!file){
|
|
444
438
|
console.log(`Error - Cannot read or find Context snapshot '${contextFileName}_contextSnapShot.json' in '${executedLocation}'.`);
|
|
445
439
|
return;
|
|
@@ -451,13 +445,12 @@ program.option('-V', 'output the version');
|
|
|
451
445
|
console.log(`Error - Cannot read context snapshot at '${file}'.`);
|
|
452
446
|
return;
|
|
453
447
|
}
|
|
454
|
-
var
|
|
455
|
-
var migrationFiles = globSearch.sync(searchMigration, contextSnapshot.migrationFolder);
|
|
448
|
+
var migrationFiles = globSearch.sync(`**/*_migration.js`, { cwd: contextSnapshot.migrationFolder, dot: true, windowsPathsNoEscape: true });
|
|
456
449
|
if(!(migrationFiles && migrationFiles.length)){
|
|
457
450
|
console.log("No migration files found.");
|
|
458
451
|
return;
|
|
459
452
|
}
|
|
460
|
-
var sorted = migrationFiles.slice().sort((a,b) => __getMigrationTimestamp(a) - __getMigrationTimestamp(b));
|
|
453
|
+
var sorted = migrationFiles.slice().sort((a,b) => __getMigrationTimestamp(path.resolve(contextSnapshot.migrationFolder, a)) - __getMigrationTimestamp(path.resolve(contextSnapshot.migrationFolder, b)));
|
|
461
454
|
// Print relative names for readability
|
|
462
455
|
for(const f of sorted){
|
|
463
456
|
console.log(path.basename(f));
|
|
@@ -478,18 +471,17 @@ program.option('-V', 'output the version');
|
|
|
478
471
|
var targetName = path.basename(migrationFileName);
|
|
479
472
|
|
|
480
473
|
// Locate the target migration file anywhere under the current folder
|
|
481
|
-
var
|
|
482
|
-
var targetMatches = globSearch.sync(searchTarget, executedLocation);
|
|
474
|
+
var targetMatches = globSearch.sync(`**/${targetName}`, { cwd: executedLocation, dot: true, windowsPathsNoEscape: true });
|
|
483
475
|
if(!(targetMatches && targetMatches.length)){
|
|
484
476
|
console.log(`Error - Cannot read or find migration file '${targetName}' in '${executedLocation}'.`);
|
|
485
477
|
return;
|
|
486
478
|
}
|
|
487
|
-
var targetFilePath = targetMatches[0];
|
|
479
|
+
var targetFilePath = path.resolve(executedLocation, targetMatches[0]);
|
|
488
480
|
var migrationFolder = path.dirname(targetFilePath);
|
|
489
481
|
|
|
490
482
|
// Find the context snapshot within the same migrations folder
|
|
491
|
-
var snapshotMatches = globSearch.sync(
|
|
492
|
-
var snapshotFile = snapshotMatches && snapshotMatches[0];
|
|
483
|
+
var snapshotMatches = globSearch.sync(`**/*_contextSnapShot.json`, { cwd: migrationFolder, dot: true, windowsPathsNoEscape: true });
|
|
484
|
+
var snapshotFile = snapshotMatches && snapshotMatches[0] ? path.resolve(migrationFolder, snapshotMatches[0]) : null;
|
|
493
485
|
if(!snapshotFile){
|
|
494
486
|
console.log("Error - Cannot read or find Context snapshot in migration folder.");
|
|
495
487
|
return;
|
|
@@ -504,7 +496,7 @@ program.option('-V', 'output the version');
|
|
|
504
496
|
}
|
|
505
497
|
|
|
506
498
|
// Get all migration files in this folder
|
|
507
|
-
var allMigrationFiles = globSearch.sync(
|
|
499
|
+
var allMigrationFiles = globSearch.sync(`**/*_migration.js`, { cwd: migrationFolder, dot: true, windowsPathsNoEscape: true });
|
|
508
500
|
if(!(allMigrationFiles && allMigrationFiles.length)){
|
|
509
501
|
console.log("Error - Cannot read or find migration file");
|
|
510
502
|
return;
|
|
@@ -512,7 +504,7 @@ program.option('-V', 'output the version');
|
|
|
512
504
|
|
|
513
505
|
// Sort chronologically
|
|
514
506
|
var sorted = allMigrationFiles.slice().sort(function(a, b){
|
|
515
|
-
return __getMigrationTimestamp(a) - __getMigrationTimestamp(b);
|
|
507
|
+
return __getMigrationTimestamp(path.resolve(migrationFolder, a)) - __getMigrationTimestamp(path.resolve(migrationFolder, b));
|
|
516
508
|
});
|
|
517
509
|
|
|
518
510
|
// Find target index by basename match
|
|
@@ -542,7 +534,7 @@ program.option('-V', 'output the version');
|
|
|
542
534
|
|
|
543
535
|
// Roll back (down) all migrations newer than the target (i.e., strictly after targetIndex)
|
|
544
536
|
for (var i = sorted.length - 1; i > targetIndex; i--) {
|
|
545
|
-
var migFile = sorted[i];
|
|
537
|
+
var migFile = path.resolve(migrationFolder, sorted[i]);
|
|
546
538
|
var MigCtor = require(migFile);
|
|
547
539
|
var migInstance = new MigCtor(ContextCtor);
|
|
548
540
|
if(typeof migInstance.down === 'function'){
|
|
@@ -576,79 +568,69 @@ program.option('-V', 'output the version');
|
|
|
576
568
|
.action(function(){
|
|
577
569
|
var executedLocation = process.cwd();
|
|
578
570
|
try{
|
|
579
|
-
// Find all
|
|
580
|
-
var
|
|
581
|
-
if(!(
|
|
582
|
-
console.log('No
|
|
571
|
+
// Find all context snapshots and run update per snapshot (avoids unrelated framework contexts)
|
|
572
|
+
var snapshotFiles = globSearch.sync(`**/*_contextSnapShot.json`, { cwd: executedLocation, dot: true, windowsPathsNoEscape: true });
|
|
573
|
+
if(!(snapshotFiles && snapshotFiles.length)){
|
|
574
|
+
console.log('No context snapshots found. Run enable-migrations for each context first.');
|
|
583
575
|
return;
|
|
584
576
|
}
|
|
585
|
-
//
|
|
586
|
-
var
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
577
|
+
// Group snapshots by context name (case-insensitive) and pick best per group
|
|
578
|
+
var groups = {};
|
|
579
|
+
for(const snapRel of snapshotFiles){
|
|
580
|
+
const snapFile = path.resolve(executedLocation, snapRel);
|
|
581
|
+
let cs;
|
|
582
|
+
try{ cs = require(snapFile); }catch(_){ continue; }
|
|
583
|
+
const nameFromPath = path.basename(snapFile).replace(/_contextSnapShot\.json$/i, '').toLowerCase();
|
|
584
|
+
const ctxName = (cs && cs.contextLocation)
|
|
585
|
+
? path.basename(cs.contextLocation).replace(/\.js$/i, '').toLowerCase()
|
|
586
|
+
: nameFromPath;
|
|
587
|
+
const migsRel = globSearch.sync(`**/*_migration.js`, { cwd: cs.migrationFolder, dot: true, windowsPathsNoEscape: true }) || [];
|
|
588
|
+
const migs = migsRel.map(f => path.resolve(cs.migrationFolder, f));
|
|
589
|
+
if(!groups[ctxName]) groups[ctxName] = [];
|
|
590
|
+
groups[ctxName].push({ snapFile, cs, ctxName, migs });
|
|
594
591
|
}
|
|
592
|
+
|
|
595
593
|
var migration = new Migration();
|
|
596
|
-
|
|
597
|
-
for(const
|
|
594
|
+
var ctxNames = Object.keys(groups);
|
|
595
|
+
for(const name of ctxNames){
|
|
598
596
|
try{
|
|
599
|
-
var
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
var
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
console.log(`Skipping ${ctxName}: snapshot not found. Run 'masterrecord enable-migrations ${ctxName}'.`);
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
var contextSnapshot;
|
|
609
|
-
try{ contextSnapshot = require(snapFile); }catch(_){
|
|
610
|
-
console.log(`Skipping ${ctxName}: cannot read context snapshot at '${snapFile}'.`);
|
|
611
|
-
continue;
|
|
612
|
-
}
|
|
613
|
-
// Get migration files for this snapshot folder
|
|
614
|
-
var searchMigration = `${contextSnapshot.migrationFolder}/**/*_migration.js`;
|
|
615
|
-
var migrationFiles = globSearch.sync(searchMigration, contextSnapshot.migrationFolder);
|
|
616
|
-
if(!(migrationFiles && migrationFiles.length)){
|
|
617
|
-
console.log(`Skipping ${ctxName}: no migration files found.`);
|
|
597
|
+
var list = groups[name];
|
|
598
|
+
// Prefer entries that actually have migration files
|
|
599
|
+
var withMigs = list.filter(e => e.migs && e.migs.length > 0);
|
|
600
|
+
var entry = withMigs.length ? withMigs[withMigs.length - 1] : list[0];
|
|
601
|
+
if(!(entry.migs && entry.migs.length)){
|
|
602
|
+
console.log(`Skipping ${entry.ctxName}: no migration files found.`);
|
|
618
603
|
continue;
|
|
619
604
|
}
|
|
620
|
-
|
|
621
|
-
var mFiles = migrationFiles.slice().sort(function(a, b){
|
|
605
|
+
var mFiles = entry.migs.slice().sort(function(a, b){
|
|
622
606
|
return __getMigrationTimestamp(a) - __getMigrationTimestamp(b);
|
|
623
607
|
});
|
|
624
608
|
var mFile = mFiles[mFiles.length - 1];
|
|
625
609
|
|
|
626
610
|
var ContextCtor;
|
|
627
|
-
try{ ContextCtor = require(
|
|
628
|
-
console.log(`Skipping ${ctxName}: cannot load Context at '${
|
|
611
|
+
try{ ContextCtor = require(entry.cs.contextLocation); }catch(_){
|
|
612
|
+
console.log(`Skipping ${entry.ctxName}: cannot load Context at '${entry.cs.contextLocation}'.`);
|
|
629
613
|
continue;
|
|
630
614
|
}
|
|
631
615
|
var contextInstance;
|
|
632
616
|
try{ contextInstance = new ContextCtor(); }catch(_){
|
|
633
|
-
console.log(`Skipping ${ctxName}: failed to construct Context.`);
|
|
617
|
+
console.log(`Skipping ${entry.ctxName}: failed to construct Context.`);
|
|
634
618
|
continue;
|
|
635
619
|
}
|
|
636
620
|
var migrationProjectFile = require(mFile);
|
|
637
621
|
var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
|
|
638
622
|
var cleanEntities = migration.cleanEntities(contextInstance.__entities);
|
|
639
|
-
var tableObj = migration.buildUpObject(
|
|
640
|
-
// Run up for this context
|
|
623
|
+
var tableObj = migration.buildUpObject(entry.cs.schema, cleanEntities);
|
|
641
624
|
newMigrationProjectInstance.up(tableObj);
|
|
642
|
-
// Update snapshot
|
|
643
625
|
var snap = {
|
|
644
|
-
file :
|
|
626
|
+
file : entry.cs.contextLocation,
|
|
645
627
|
executedLocation : executedLocation,
|
|
646
628
|
context : contextInstance,
|
|
647
629
|
contextEntities : cleanEntities,
|
|
648
|
-
contextFileName: ctxName
|
|
630
|
+
contextFileName: entry.ctxName
|
|
649
631
|
}
|
|
650
632
|
migration.createSnapShot(snap);
|
|
651
|
-
console.log(`database updated for ${ctxName}`);
|
|
633
|
+
console.log(`database updated for ${entry.ctxName}`);
|
|
652
634
|
}catch(errCtx){
|
|
653
635
|
console.log('Error updating context: ', errCtx);
|
|
654
636
|
}
|
|
@@ -15,7 +15,7 @@ class migrationMySQLQuery {
|
|
|
15
15
|
}
|
|
16
16
|
// Map belongsTo to its foreignKey name
|
|
17
17
|
var name = (col.relationshipType === 'belongsTo' && col.foreignKey) ? col.foreignKey : col.name;
|
|
18
|
-
columnList.push(name);
|
|
18
|
+
columnList.push(`\`${name}\``);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
return columnList.join(',');
|
|
@@ -63,7 +63,7 @@ class migrationMySQLQuery {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
return
|
|
66
|
+
return `\`${tableName}\` ${type}${nullName}${defaultValue}${unique}${primaryKey}${auto}`;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
boolType(type){
|
|
@@ -135,7 +135,7 @@ class migrationMySQLQuery {
|
|
|
135
135
|
|
|
136
136
|
if(table){
|
|
137
137
|
|
|
138
|
-
return `ALTER TABLE
|
|
138
|
+
return `ALTER TABLE \`${table.tableName}\` MODIFY COLUMN ${this.#columnMapping(table.table)} `;
|
|
139
139
|
}
|
|
140
140
|
else{
|
|
141
141
|
console.log("table information is null");
|
|
@@ -152,8 +152,8 @@ class migrationMySQLQuery {
|
|
|
152
152
|
|
|
153
153
|
|
|
154
154
|
addColum(table){
|
|
155
|
-
return `ALTER TABLE
|
|
156
|
-
ADD
|
|
155
|
+
return `ALTER TABLE \`${table.tableName}\`
|
|
156
|
+
ADD \`${table.name}\` ${table.realDataType}`;
|
|
157
157
|
|
|
158
158
|
/*
|
|
159
159
|
column definations
|
|
@@ -172,7 +172,7 @@ class migrationMySQLQuery {
|
|
|
172
172
|
is indexed
|
|
173
173
|
appears in a view
|
|
174
174
|
*/
|
|
175
|
-
return `ALTER TABLE
|
|
175
|
+
return `ALTER TABLE \`${table.tableName}\` DROP COLUMN \`${table.name}\``;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
insertInto(name, table){
|
|
@@ -193,7 +193,7 @@ class migrationMySQLQuery {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
var completeQuery = `CREATE TABLE IF NOT EXISTS
|
|
196
|
+
var completeQuery = `CREATE TABLE IF NOT EXISTS \`${table.__name}\` (${queryVar.replace(/,\s*$/, "")});`;
|
|
197
197
|
return completeQuery;
|
|
198
198
|
|
|
199
199
|
/*
|
|
@@ -213,15 +213,15 @@ class migrationMySQLQuery {
|
|
|
213
213
|
|
|
214
214
|
|
|
215
215
|
dropTable(name){
|
|
216
|
-
return `DROP TABLE
|
|
216
|
+
return `DROP TABLE \`${name}\``
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
renameTable(table){
|
|
220
|
-
return `ALTER TABLE
|
|
220
|
+
return `ALTER TABLE \`${table.tableName}\` RENAME TO \`${table.newName}\``;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
renameColumn(table){
|
|
224
|
-
return `ALTER TABLE
|
|
224
|
+
return `ALTER TABLE \`${table.tableName}\` RENAME COLUMN \`${table.name}\` TO \`${table.newName}\``
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"app-root-path": "^3.1.0",
|
|
10
10
|
"better-sqlite3": "^12.4.1"
|
|
11
11
|
},
|
|
12
|
-
"version": "0.2.
|
|
12
|
+
"version": "0.2.13",
|
|
13
13
|
"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 ",
|
|
14
14
|
"homepage": "https://github.com/Tailor/MasterRecord#readme",
|
|
15
15
|
"repository": {
|