masterrecord 0.2.8 → 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 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')
@@ -218,6 +278,84 @@ program.option('-V', 'output the version');
218
278
  });
219
279
 
220
280
 
281
+ program
282
+ .command('update-database-down <contextFileName>')
283
+ .alias('udd')
284
+ .description('Run the latest migration down method for the given context')
285
+ .action(function(contextFileName){
286
+ var executedLocation = process.cwd();
287
+ contextFileName = contextFileName.toLowerCase();
288
+ var migration = new Migration();
289
+ try{
290
+ var search = `${executedLocation}/**/*${contextFileName}_contextSnapShot.json`;
291
+ var files = globSearch.sync(search, executedLocation);
292
+ var file = files && files[0];
293
+ if(!file){
294
+ console.log(`Error - Cannot read or find Context snapshot '${contextFileName}_contextSnapShot.json' in '${executedLocation}'.`);
295
+ return;
296
+ }
297
+ var contextSnapshot;
298
+ try{
299
+ contextSnapshot = require(file);
300
+ }catch(_){
301
+ console.log(`Error - Cannot read context snapshot at '${file}'.`);
302
+ return;
303
+ }
304
+ var searchMigration = `${contextSnapshot.migrationFolder}/**/*_migration.js`;
305
+ var migrationFiles = globSearch.sync(searchMigration, contextSnapshot.migrationFolder);
306
+ if(!(migrationFiles && migrationFiles.length)){
307
+ console.log("Error - Cannot read or find migration file");
308
+ return;
309
+ }
310
+ // Sort and select latest
311
+ var mFiles = migrationFiles.slice().sort(function(a, b){
312
+ return __getMigrationTimestamp(a) - __getMigrationTimestamp(b);
313
+ });
314
+ var latestFile = mFiles[mFiles.length - 1];
315
+
316
+ // Prepare context and table object
317
+ let ContextCtor;
318
+ try{
319
+ ContextCtor = require(contextSnapshot.contextLocation);
320
+ }catch(_){
321
+ console.log(`Error - Cannot load Context file at '${contextSnapshot.contextLocation}'.`);
322
+ return;
323
+ }
324
+ var contextInstance;
325
+ try{
326
+ contextInstance = new ContextCtor();
327
+ }catch(_){
328
+ console.log(`Error - Failed to construct Context from '${contextSnapshot.contextLocation}'.`);
329
+ return;
330
+ }
331
+ var cleanEntities = migration.cleanEntities(contextInstance.__entities);
332
+ var tableObj = migration.buildUpObject(contextSnapshot.schema, cleanEntities);
333
+
334
+ var MigCtor = require(latestFile);
335
+ var migInstance = new MigCtor(ContextCtor);
336
+ if(typeof migInstance.down === 'function'){
337
+ migInstance.down(tableObj);
338
+ }else{
339
+ console.log(`Warning - Migration '${path.basename(latestFile)}' has no down method; skipping.`);
340
+ }
341
+
342
+ // Update snapshot
343
+ var snap = {
344
+ file : contextSnapshot.contextLocation,
345
+ executedLocation : executedLocation,
346
+ context : contextInstance,
347
+ contextEntities : cleanEntities,
348
+ contextFileName: contextFileName
349
+ }
350
+ migration.createSnapShot(snap);
351
+ console.log("database updated");
352
+
353
+ }catch (e){
354
+ console.log("Error - Cannot read or find file ", e);
355
+ }
356
+ });
357
+
358
+
221
359
  program
222
360
  .command('update-database-restart <contextFileName>')
223
361
  .alias('udr')
@@ -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
- console.error('MySQL execute skipped: connection not defined');
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
- console.error('MySQL execute skipped: connection not defined');
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);
@@ -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
- console.error('MySQL connect error:', err && err.code ? err.code : err);
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
- const code = err && err.code ? err.code : '';
39
- if(code === 'ER_BAD_DB_ERROR' || code === 'ECONNREFUSED' || code === 'PROTOCOL_CONNECTION_LOST'){
40
- console.error('MySQL query skipped due to connection not defined');
41
- return null;
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.8",
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": {