masterrecord 0.2.9 → 0.2.10
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 +60 -0
- package/Migrations/schema.js +38 -0
- package/mySQLEngine.js +12 -2
- package/mySQLSyncConnect.js +19 -6
- package/package.json +1 -1
package/Migrations/cli.js
CHANGED
|
@@ -74,6 +74,66 @@ program.option('-V', 'output the version');
|
|
|
74
74
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
program
|
|
78
|
+
.command('ensure-database <contextFileName>')
|
|
79
|
+
.alias('ed')
|
|
80
|
+
.description('Ensure the target database exists for the given context (MySQL)')
|
|
81
|
+
.action(function(contextFileName){
|
|
82
|
+
var executedLocation = process.cwd();
|
|
83
|
+
contextFileName = contextFileName.toLowerCase();
|
|
84
|
+
var migration = new Migration();
|
|
85
|
+
try{
|
|
86
|
+
var search = `${executedLocation}/**/*${contextFileName}_contextSnapShot.json`;
|
|
87
|
+
var files = globSearch.sync(search, executedLocation);
|
|
88
|
+
var file = files && files[0];
|
|
89
|
+
if(!file){
|
|
90
|
+
console.log(`Error - Cannot read or find Context snapshot '${contextFileName}_contextSnapShot.json' in '${executedLocation}'.`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
var contextSnapshot;
|
|
94
|
+
try{
|
|
95
|
+
contextSnapshot = require(file);
|
|
96
|
+
}catch(_){
|
|
97
|
+
console.log(`Error - Cannot read context snapshot at '${file}'.`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Find latest migration file (so we can use its class which extends schema)
|
|
101
|
+
var searchMigration = `${contextSnapshot.migrationFolder}/**/*_migration.js`;
|
|
102
|
+
var migrationFiles = globSearch.sync(searchMigration, contextSnapshot.migrationFolder);
|
|
103
|
+
if(!(migrationFiles && migrationFiles.length)){
|
|
104
|
+
console.log("Error - Cannot read or find migration file");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
var mFiles = migrationFiles.slice().sort(function(a, b){
|
|
108
|
+
return __getMigrationTimestamp(a) - __getMigrationTimestamp(b);
|
|
109
|
+
});
|
|
110
|
+
var mFile = mFiles[mFiles.length -1];
|
|
111
|
+
|
|
112
|
+
let ContextCtor;
|
|
113
|
+
try{
|
|
114
|
+
ContextCtor = require(contextSnapshot.contextLocation);
|
|
115
|
+
}catch(_){
|
|
116
|
+
console.log(`Error - Cannot load Context file at '${contextSnapshot.contextLocation}'.`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Use the migration class (extends schema) so createdatabase is available
|
|
121
|
+
var MigrationCtor = require(mFile);
|
|
122
|
+
var mig = new MigrationCtor(ContextCtor);
|
|
123
|
+
if(typeof mig.createdatabase === 'function'){
|
|
124
|
+
try{ mig.createdatabase(); }catch(_){ /* best-effort */ }
|
|
125
|
+
console.log('database ensured');
|
|
126
|
+
} else if(typeof mig.createDatabase === 'function'){
|
|
127
|
+
try{ mig.createDatabase(); }catch(_){ }
|
|
128
|
+
console.log('database ensured');
|
|
129
|
+
} else {
|
|
130
|
+
console.log('Error - Migration class missing createDatabase method');
|
|
131
|
+
}
|
|
132
|
+
}catch(e){
|
|
133
|
+
console.log('Error - Cannot read or find file ', e);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
77
137
|
// program
|
|
78
138
|
// .command('create-database <contextFileName> <dbName>')
|
|
79
139
|
// .alias('cd')
|
package/Migrations/schema.js
CHANGED
|
@@ -3,6 +3,7 @@ class schema{
|
|
|
3
3
|
|
|
4
4
|
constructor(context){
|
|
5
5
|
this.context = new context();
|
|
6
|
+
this._dbEnsured = false;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
|
|
@@ -10,6 +11,10 @@ class schema{
|
|
|
10
11
|
if(table){
|
|
11
12
|
this.fullTable = table.___table;
|
|
12
13
|
}
|
|
14
|
+
// Ensure backing database exists for MySQL before running any DDL
|
|
15
|
+
if(this.context && this.context.isMySQL && this._dbEnsured !== true){
|
|
16
|
+
try{ this.createDatabase(); }catch(_){ /* best-effort */ }
|
|
17
|
+
}
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
// create obj to convert into create sql
|
|
@@ -253,6 +258,39 @@ class schema{
|
|
|
253
258
|
}
|
|
254
259
|
}
|
|
255
260
|
|
|
261
|
+
// EnsureCreated equivalent for MySQL: create DB if missing
|
|
262
|
+
createDatabase(){
|
|
263
|
+
try{
|
|
264
|
+
if(!(this.context && this.context.isMySQL)){ return; }
|
|
265
|
+
const MySQLClient = require('masterrecord/mySQLSyncConnect');
|
|
266
|
+
const client = this.context.db; // main client (may not be connected yet)
|
|
267
|
+
if(!client || !client.config || !client.config.database){ return; }
|
|
268
|
+
const dbName = client.config.database;
|
|
269
|
+
// Build server-level connection (no database)
|
|
270
|
+
const baseConfig = { ...client.config };
|
|
271
|
+
delete baseConfig.database;
|
|
272
|
+
const admin = new MySQLClient(baseConfig);
|
|
273
|
+
admin.connect();
|
|
274
|
+
if(!admin.connection){ return; }
|
|
275
|
+
const check = admin.query(`SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '${dbName}'`);
|
|
276
|
+
const exists = Array.isArray(check) ? check.length > 0 : !!check?.length;
|
|
277
|
+
if(!exists){
|
|
278
|
+
// Create with sensible defaults
|
|
279
|
+
admin.query(`CREATE DATABASE \`${dbName}\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
|
|
280
|
+
}
|
|
281
|
+
admin.close();
|
|
282
|
+
this._dbEnsured = true;
|
|
283
|
+
}catch(err){
|
|
284
|
+
// Non-fatal: migrations may still proceed if DB already exists or permissions blocked
|
|
285
|
+
try{ console.error(err); }catch(_){ }
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Alias for consistency with user expectation
|
|
290
|
+
createdatabase(table){
|
|
291
|
+
return this.createDatabase(table);
|
|
292
|
+
}
|
|
293
|
+
|
|
256
294
|
|
|
257
295
|
//"dbo.People", "Location"
|
|
258
296
|
alterColumn(table){
|
package/mySQLEngine.js
CHANGED
|
@@ -574,14 +574,24 @@ class SQLLiteEngine {
|
|
|
574
574
|
this.db.connect(this.db);
|
|
575
575
|
const res = this.db.query(query);
|
|
576
576
|
if(res === null){
|
|
577
|
-
|
|
577
|
+
const dbName = (this.db && this.db.config && this.db.config.database) ? this.db.config.database : '(unknown)';
|
|
578
|
+
if(this.db && this.db.lastErrorCode === 'ER_BAD_DB_ERROR'){
|
|
579
|
+
console.error(`MySQL execute skipped: database '${dbName}' does not exist`);
|
|
580
|
+
}else{
|
|
581
|
+
console.error('MySQL execute skipped: connection not defined');
|
|
582
|
+
}
|
|
578
583
|
return null;
|
|
579
584
|
}
|
|
580
585
|
return res;
|
|
581
586
|
}catch(err){
|
|
582
587
|
const code = err && err.code ? err.code : '';
|
|
583
588
|
if(code === 'ER_BAD_DB_ERROR' || code === 'ECONNREFUSED' || code === 'PROTOCOL_CONNECTION_LOST'){
|
|
584
|
-
|
|
589
|
+
const dbName = (this.db && this.db.config && this.db.config.database) ? this.db.config.database : '(unknown)';
|
|
590
|
+
if(code === 'ER_BAD_DB_ERROR'){
|
|
591
|
+
console.error(`MySQL execute skipped: database '${dbName}' does not exist`);
|
|
592
|
+
} else {
|
|
593
|
+
console.error('MySQL execute skipped: connection not defined');
|
|
594
|
+
}
|
|
585
595
|
return null;
|
|
586
596
|
}
|
|
587
597
|
console.error(err);
|
package/mySQLSyncConnect.js
CHANGED
|
@@ -9,6 +9,8 @@ class MySQLClient {
|
|
|
9
9
|
delete this.config.type;
|
|
10
10
|
}
|
|
11
11
|
this.connection = null;
|
|
12
|
+
this.lastErrorCode = null;
|
|
13
|
+
this.lastErrorMessage = null;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
connect() {
|
|
@@ -19,7 +21,14 @@ class MySQLClient {
|
|
|
19
21
|
}
|
|
20
22
|
} catch (err) {
|
|
21
23
|
// Swallow connection errors and leave connection undefined so callers can react gracefully
|
|
22
|
-
|
|
24
|
+
this.lastErrorCode = err && err.code ? err.code : null;
|
|
25
|
+
this.lastErrorMessage = err && err.message ? err.message : String(err);
|
|
26
|
+
if(this.lastErrorCode === 'ER_BAD_DB_ERROR'){
|
|
27
|
+
const dbName = this.config && this.config.database ? this.config.database : '(unknown)';
|
|
28
|
+
console.error(`MySQL connect error: database '${dbName}' does not exist`);
|
|
29
|
+
}else{
|
|
30
|
+
console.error('MySQL connect error:', this.lastErrorCode || this.lastErrorMessage);
|
|
31
|
+
}
|
|
23
32
|
this.connection = null;
|
|
24
33
|
return null;
|
|
25
34
|
}
|
|
@@ -35,12 +44,16 @@ class MySQLClient {
|
|
|
35
44
|
return jj;
|
|
36
45
|
} catch (err) {
|
|
37
46
|
// If the underlying driver surfaces bad DB or network errors, normalize to null
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
this.lastErrorCode = err && err.code ? err.code : null;
|
|
48
|
+
this.lastErrorMessage = err && err.message ? err.message : String(err);
|
|
49
|
+
if(this.lastErrorCode === 'ER_BAD_DB_ERROR'){
|
|
50
|
+
const dbName = this.config && this.config.database ? this.config.database : '(unknown)';
|
|
51
|
+
console.error(`MySQL error: database '${dbName}' does not exist`);
|
|
52
|
+
} else if(this.lastErrorCode === 'ECONNREFUSED' || this.lastErrorCode === 'PROTOCOL_CONNECTION_LOST'){
|
|
53
|
+
console.error('MySQL connection error:', this.lastErrorCode);
|
|
54
|
+
} else {
|
|
55
|
+
console.error(err);
|
|
42
56
|
}
|
|
43
|
-
console.error(err);
|
|
44
57
|
return null;
|
|
45
58
|
}
|
|
46
59
|
|
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.10",
|
|
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": {
|