masterrecord 0.1.3 → 0.2.0

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/context.js CHANGED
@@ -1,4 +1,4 @@
1
- // Version 0.0.8
1
+ // Version 0.0.15
2
2
 
3
3
  var modelBuilder = require('./Entity/entityModelBuilder');
4
4
  var query = require('masterrecord/QueryLanguage/queryMethods');
@@ -9,6 +9,7 @@ var insertManager = require('./insertManager');
9
9
  var deleteManager = require('./deleteManager');
10
10
  var globSearch = require("glob");
11
11
  var fs = require('fs');
12
+ var path = require('path');
12
13
  const appRoot = require('app-root-path');
13
14
  const MySQLClient = require('masterrecord/mySQLSyncConnect');
14
15
 
@@ -54,6 +55,7 @@ class context {
54
55
  }
55
56
  catch (e) {
56
57
  console.log("error SQL", e);
58
+ throw new Error(String(e))
57
59
  }
58
60
  }
59
61
 
@@ -90,74 +92,187 @@ class context {
90
92
  };
91
93
 
92
94
  __findSettings(root, rootFolderLocation, envType){
93
-
94
95
  if(envType === undefined){
95
96
  envType = "development";
96
97
  }
97
- var rootFolder = `${root}/${rootFolderLocation}`;
98
- var search = `${rootFolder}/**/*env.${envType}.json`;
99
- var files = globSearch.sync(search, rootFolder);
100
- var file = files[0];
101
- if(file === undefined){
102
- root = tools.removeBackwardSlashSection(root, 1, "/");
103
- rootFolder = `${root}/${rootFolderLocation}`;
104
- var search = `${rootFolder}/**/*env.${envType}.json`;
105
- var files = globSearch.sync(search,rootFolder);
106
- file = files[0];
107
- if(file === undefined){
108
- root = tools.removeBackwardSlashSection(root, 1, "/");
109
- rootFolder = `${root}/${rootFolderLocation}`;
110
- var search = `${rootFolder}/**/*env.${envType}.json`;
111
- var files = globSearch.sync(search,rootFolder);
112
- file = files[0];
113
- if(file === undefined){
114
- console.log(`could not find file - ${rootFolder}/env.${envType}.json`);
115
- throw error(`could not find file - ${rootFolder}/env.${envType}.json`);
98
+ let currentRoot = root;
99
+ const maxHops = 12;
100
+ for(let i = 0; i < maxHops; i++){
101
+ const rootFolder = path.isAbsolute(rootFolderLocation) ? rootFolderLocation : `${currentRoot}/${rootFolderLocation}`;
102
+ // Support both env.development.json and development.json naming
103
+ const searchA = `${rootFolder}/**/*env.${envType}.json`;
104
+ const searchB = `${rootFolder}/**/*${envType}.json`;
105
+ let files = globSearch.sync(searchA, currentRoot);
106
+ if(!files || files.length === 0){
107
+ files = globSearch.sync(searchB, currentRoot);
108
+ }
109
+ const file = files && files[0];
110
+ if(file){
111
+ return { file: file, rootFolder: currentRoot };
112
+ }
113
+ const parent = tools.removeBackwardSlashSection(currentRoot, 1, "/");
114
+ if(parent === currentRoot || parent === ""){
115
+ break;
116
+ }
117
+ currentRoot = parent;
118
+ }
119
+ const msg = `could not find env file '${rootFolderLocation}/env.${envType}.json' starting at ${root}`;
120
+ console.log(msg);
121
+ throw new Error(msg);
122
+ }
123
+
124
+ // Auto-detect DB type (sqlite or mysql) using environment JSON
125
+ env(rootFolderLocation){
126
+ try{
127
+ // Determine environment: prefer explicit, then NODE_ENV, fallback 'development'
128
+ let envType = this.__environment || process.env.NODE_ENV || 'development';
129
+ const contextName = this.__name;
130
+
131
+ // Try multiple base roots for robustness
132
+ const candidateRoots = [ process.cwd(), appRoot.path, __dirname ];
133
+ let file;
134
+ for(let i = 0; i < candidateRoots.length; i++){
135
+ try{
136
+ file = this.__findSettings(candidateRoots[i], rootFolderLocation, envType);
137
+ if(file) break;
138
+ }catch(_){ /* try next */ }
139
+ }
140
+ // If still not found and an absolute path was provided, try directly
141
+ if(!file && path.isAbsolute(rootFolderLocation)){
142
+ const directFolder = rootFolderLocation;
143
+ const envFileA = path.join(directFolder, `env.${envType}.json`);
144
+ const envFileB = path.join(directFolder, `${envType}.json`);
145
+ const picked = fs.existsSync(envFileA) ? envFileA : (fs.existsSync(envFileB) ? envFileB : null);
146
+ if(picked){
147
+ file = { file: picked, rootFolder: path.dirname(path.dirname(picked)) };
116
148
  }
149
+ }
150
+ if(!file){
151
+ throw new Error(`Environment config not found for '${envType}' under '${rootFolderLocation}'.`);
152
+ }
117
153
 
154
+ const settings = require(file.file);
155
+ const options = settings[contextName];
156
+ if(options === undefined){
157
+ console.log("settings missing context name settings");
158
+ throw new Error("settings missing context name settings");
118
159
  }
119
-
160
+
161
+ const type = String(options.type || '').toLowerCase();
162
+
163
+ if(type === 'sqlite' || type === 'better-sqlite3'){
164
+ this.isSQLite = true; this.isMySQL = false;
165
+ // Back-compat: treat leading '/' as project-root relative, not filesystem root
166
+ let dbPath = options.connection || '';
167
+ if(dbPath){
168
+ if(dbPath.startsWith(path.sep) || !path.isAbsolute(dbPath)){
169
+ dbPath = path.join(file.rootFolder, dbPath);
170
+ }
171
+ }
172
+ const dbDir = path.dirname(dbPath);
173
+ if(!fs.existsSync(dbDir)){
174
+ fs.mkdirSync(dbDir, { recursive: true });
175
+ }
176
+ const sqliteOptions = { ...options, completeConnection: dbPath };
177
+ this.db = this.__SQLiteInit(sqliteOptions, 'better-sqlite3');
178
+ this._SQLEngine.setDB(this.db, 'better-sqlite3');
179
+ return this;
180
+ }
181
+
182
+ if(type === 'mysql'){
183
+ this.isMySQL = true; this.isSQLite = false;
184
+ this.db = this.__mysqlInit(options, 'mysql2');
185
+ this._SQLEngine.setDB(this.db, 'mysql');
186
+ return this;
187
+ }
188
+
189
+ throw new Error(`Unsupported database type '${options.type}'. Expected 'sqlite' or 'mysql'.`);
190
+ }
191
+ catch(err){
192
+ console.log("error:", err);
193
+ throw new Error(String(err));
120
194
  }
121
-
122
- return {
123
- file: file,
124
- rootFolder : root
125
- };
126
195
  }
127
196
 
128
197
  useSqlite(rootFolderLocation){
129
- this.isSQLite = true;
130
- var root = process.cwd();
131
- var envType = this.__environment;
132
- var contextName = this.__name;
133
- var file = this.__findSettings(root, rootFolderLocation, envType);
134
- var settings = require(file.file);
135
- var options = settings[contextName];
136
-
137
- if(options === undefined){
138
- console.log("settings missing context name settings");
139
- throw error("settings missing context name settings");
140
- }
198
+ try{
199
+ this.isSQLite = true;
200
+ var root = process.cwd();
201
+ var envType = this.__environment;
202
+ var contextName = this.__name;
203
+ var file = this.__findSettings(root, rootFolderLocation, envType);
204
+ var settings = require(file.file);
205
+ var options = settings[contextName];
206
+
207
+ if(options === undefined){
208
+ console.log("settings missing context name settings");
209
+ throw new Error("settings missing context name settings");
210
+ }
141
211
 
142
- this.validateSQLiteOptions(options);
143
- options.completeConnection = `${file.rootFolder}${options.connection}`;
144
- var dbDirectory = options.completeConnection.substr(0, options.completeConnection.lastIndexOf("\/"));
145
-
146
- if (!fs.existsSync(dbDirectory)){
147
- fs.mkdirSync(dbDirectory);
148
- }
212
+ this.validateSQLiteOptions(options);
213
+ options.completeConnection = `${file.rootFolder}${options.connection}`;
214
+ var dbDirectory = options.completeConnection.substr(0, options.completeConnection.lastIndexOf("\/"));
215
+
216
+ if (!fs.existsSync(dbDirectory)){
217
+ fs.mkdirSync(dbDirectory);
218
+ }
149
219
 
150
- this.db = this.__SQLiteInit(options, "better-sqlite3");
151
- this._SQLEngine.setDB(this.db, "better-sqlite3");
152
- return this;
220
+ this.db = this.__SQLiteInit(options, "better-sqlite3");
221
+ this._SQLEngine.setDB(this.db, "better-sqlite3");
222
+ return this;
223
+ }
224
+ catch(err){
225
+ console.log("error:",err );
226
+ throw new Error(String(err));
227
+ }
153
228
  }
154
229
 
155
230
  validateSQLiteOptions(options){
156
- if(options.hasOwnProperty('connect') === undefined){
157
- console.log("connnect string settings is missing")
158
- throw error("connection string settings is missing");
231
+ if(!options || typeof options !== 'object'){
232
+ throw new Error("settings object is missing or invalid");
159
233
  }
160
234
 
235
+ // Normalize type
236
+ let type = (options.type || '').toString().toLowerCase();
237
+ if(!type){
238
+ // Infer when not provided
239
+ if(typeof options.connection === 'string'){
240
+ type = 'sqlite';
241
+ options.type = 'sqlite';
242
+ }
243
+ else if(options.host || options.user || options.database){
244
+ type = 'mysql';
245
+ options.type = 'mysql';
246
+ }
247
+ }
248
+
249
+ if(type === 'sqlite' || type === 'better-sqlite3'){
250
+ // Required
251
+ if(!options.connection || typeof options.connection !== 'string' || options.connection.trim() === ''){
252
+ throw new Error("connection string settings is missing");
253
+ }
254
+ // Defaults
255
+ if(options.username === undefined){ options.username = ''; }
256
+ if(options.password === undefined){ options.password = ''; }
257
+ return; // valid
258
+ }
259
+
260
+ if(type === 'mysql'){
261
+ // Defaults
262
+ if(!options.host){ options.host = 'localhost'; }
263
+ if(options.port === undefined){ options.port = 3306; }
264
+ if(options.password === undefined){ options.password = ''; }
265
+ // Required
266
+ if(!options.user || options.user.toString().trim() === ''){
267
+ throw new Error("MySQL 'user' is required in settings");
268
+ }
269
+ if(!options.database || options.database.toString().trim() === ''){
270
+ throw new Error("MySQL 'database' is required in settings");
271
+ }
272
+ return; // valid
273
+ }
274
+
275
+ throw new Error(`Unsupported database type '${options.type}'. Expected 'sqlite' or 'mysql'.`);
161
276
  }
162
277
 
163
278
  useMySql(rootFolderLocation){
@@ -172,9 +287,10 @@ class context {
172
287
 
173
288
  if(options === undefined){
174
289
  console.log("settings missing context name settings");
175
- throw error("settings missing context name settings");
290
+ throw new Error("settings missing context name settings");
176
291
  }
177
292
 
293
+ this.validateSQLiteOptions(options);
178
294
  this.db = this.__mysqlInit(options, "mysql2");
179
295
  this._SQLEngine.setDB(this.db, "mysql");
180
296
  return this;
@@ -213,12 +329,18 @@ class context {
213
329
  break;
214
330
  case "modified":
215
331
  if(currentModel.__dirtyFields.length > 0){
216
- var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
332
+ var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
217
333
  // build columns equal to value string
218
334
  var argu = this._SQLEngine._buildSQLEqualTo(cleanCurrentModel);
219
- var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
220
- var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
221
- this._SQLEngine.update(sqlUpdate);
335
+ if(argu !== -1 ){
336
+ var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
337
+ var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
338
+ this._SQLEngine.update(sqlUpdate);
339
+ }
340
+ else{
341
+ console.log("Nothing has been tracked, modified, created or added");
342
+ }
343
+
222
344
  }
223
345
  else{
224
346
  console.log("Tracked entity modified with no values being changed");
@@ -251,9 +373,15 @@ class context {
251
373
  var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel._entity);
252
374
  // build columns equal to value string
253
375
  var argu = this._SQLEngine._buildSQLEqualTo(cleanCurrentModel);
254
- var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
255
- var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
256
- this._SQLEngine.update(sqlUpdate);
376
+ if(argu !== -1 ){
377
+ var primaryKey = tools.getPrimaryKeyObject(cleanCurrentModel.__entity);
378
+ var sqlUpdate = {tableName: cleanCurrentModel.__entity.__name, arg: argu, primaryKey : primaryKey, primaryKeyValue : cleanCurrentModel[primaryKey] };
379
+ this._SQLEngine.update(sqlUpdate);
380
+ }
381
+ else{
382
+ console.log("Nothing has been tracked, modified, created or added");
383
+ }
384
+
257
385
  }
258
386
  else{
259
387
  console.log("Tracked entity modified with no values being changed");
@@ -279,8 +407,11 @@ class context {
279
407
 
280
408
  catch(error){
281
409
  this.__clearErrorHandler();
282
- //this._SQLEngine.errorTransaction();
410
+
283
411
  console.log("error", error);
412
+ if(this.isSQLite){
413
+ this._SQLEngine.errorTransaction();
414
+ }
284
415
  this.__clearTracked();
285
416
  throw error;
286
417
  }
@@ -303,6 +434,9 @@ class context {
303
434
  var add = true;
304
435
  for (var mod in this.__trackedEntities) {
305
436
  var id = this.__trackedEntities[mod].__ID;
437
+ if(id === undefined){
438
+ id = Math.floor((Math.random() * 100000) + 1);
439
+ }
306
440
  if(id === model.__ID){
307
441
  add = false;
308
442
  }
package/deleteManager.js CHANGED
@@ -1,4 +1,4 @@
1
- // version 0.0.2
1
+ // version 0.0.3
2
2
  var tools = require('./Tools');
3
3
  class DeleteManager{
4
4
  constructor(sqlEngine, entities){
@@ -23,7 +23,7 @@ class DeleteManager{
23
23
  // loop though all entity properties
24
24
  for (const property of entityKeys) {
25
25
  // cascade delete
26
- if(currentModel.__entity[property].type === "hasOne" || currentModel.__entity[property].type === "hasMany"){
26
+ if(currentModel.__entity[property].type === "hasOne" || currentModel.__entity[property].type === "hasMany" || currentModel.__entity[property].type === "hasManyThrough"){
27
27
  var curModel = currentModel[property];
28
28
  if(curModel === null){
29
29
  // check if state is nullable - if so and nothing comes back dont call cascadeDelete
@@ -46,7 +46,7 @@ class DeleteManager{
46
46
  // loop though all entity properties
47
47
  for (const property of entityKeys) {
48
48
  // cascade delete
49
- if(currentModel[i].__entity[property].type === "hasOne" || currentModel[i].__entity[property].type === "hasMany"){
49
+ if(currentModel[i].__entity[property].type === "hasOne" || currentModel[i].__entity[property].type === "hasMany" || currentModel[i].__entity[property].type === "hasManyThrough"){
50
50
  $that.cascadeDelete( currentModel[i][property]);
51
51
  }
52
52
  }
package/insertManager.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- // version 0.0.5
2
+ // version 0.0.15
3
3
  var tools = require('./Tools');
4
4
  var queryScript = require('masterrecord/QueryLanguage/queryScript');
5
5
 
@@ -18,36 +18,61 @@ class InsertManager {
18
18
 
19
19
  runQueries(currentModel){
20
20
  var $that = this;
21
- var cleanCurrentModel = tools.removePrimarykeyandVirtual(currentModel, currentModel.__entity);
21
+ // Reset validation state for this operation to avoid stale errors
22
+ if(this._errorModel){
23
+ this._errorModel.isValid = true;
24
+ this._errorModel.errors = [];
25
+ }
26
+ var cleanCurrentModel = tools.clearAllProto(currentModel);
22
27
  this.validateEntity(cleanCurrentModel, currentModel, currentModel.__entity);
23
28
  if(this._errorModel.isValid){
24
29
 
25
30
  var modelEntity = currentModel.__entity;
26
31
  // TODO: if you try to add belongs to you must have a tag added first. if you dont throw error
27
32
  currentModel = this.belongsToInsert(currentModel, modelEntity);
28
- var SQL = this._SQLEngine.insert(currentModel);
33
+ var SQL = this._SQLEngine.insert(cleanCurrentModel);
29
34
  var primaryKey = tools.getPrimaryKeyObject(currentModel.__entity);
30
- // return all fields that have auto and dont have a value to the current model on insert
35
+ // use returned insert id directly; avoid redundant post-insert SELECT
31
36
  if(currentModel.__entity[primaryKey].auto === true){
32
- var query = `select * from ${currentModel.__entity.__name} where ${primaryKey} = ${ SQL.id }`;
33
- var jj = this.__queryObject.raw(query);
34
- var getQueryModel = this._SQLEngine.get(jj, currentModel.__entity, currentModel.__context );
35
- currentModel[primaryKey] = getQueryModel[0][primaryKey];
37
+ currentModel[primaryKey] = SQL.id;
36
38
  }
37
39
 
40
+ const proto = Object.getPrototypeOf(currentModel);
41
+ const props = Object.getOwnPropertyNames(proto);
42
+ const cleanPropList = tools.returnEntityList(props, modelEntity);
38
43
  const modelKeys = Object.keys(currentModel);
44
+ const mergedArray = [...new Set(modelKeys.concat(cleanPropList))];
39
45
  // loop through model properties
40
- for (const property of modelKeys) {
46
+ for (const property of mergedArray) {
41
47
  var propertyModel = currentModel[property];
42
48
  var entityProperty = modelEntity[property] ? modelEntity[property] : {};
43
49
  if(entityProperty.type === "hasOne"){
44
- // make sure property model is an object not a primary data type like number or string
45
- if(typeof(propertyModel) === "object"){
50
+ // only insert child if user provided a concrete object with data
51
+ if(propertyModel && typeof(propertyModel) === "object" ){
46
52
  // check if model has its own entity
47
53
  if(modelEntity){
48
- propertyModel.__entity = tools.getEntity(property, $that._allEntities);
49
- propertyModel[currentModel.__entity.__name] = SQL.id;
50
- $that.runQueries(propertyModel);
54
+ // check if property has a value because we dont want this to run on every insert if nothing was added
55
+ // ensure it has some own props; otherwise skip
56
+ const hasOwn = Object.keys(propertyModel).length > 0;
57
+ if(hasOwn){
58
+ propertyModel.__entity = tools.getEntity(property, $that._allEntities);
59
+ propertyModel[currentModel.__entity.__name] = SQL.id;
60
+ $that.runQueries(propertyModel);
61
+ // Hydrate child back as a tracked instance so subsequent property sets are tracked
62
+ try{
63
+ var childPk = tools.getPrimaryKeyObject(propertyModel.__entity);
64
+ var childId = propertyModel[childPk];
65
+ if(childId !== undefined){
66
+ var ctxSetName = tools.capitalizeFirstLetter(property);
67
+ if(currentModel.__context && currentModel.__context[ctxSetName]){
68
+ var trackedChild = currentModel.__context[ctxSetName].where(`r => r.${childPk} == $$`, childId).single();
69
+ if(trackedChild){
70
+ currentModel[property] = trackedChild;
71
+ }
72
+ }
73
+ }
74
+ }catch(_){ /* ignore tracking hydration errors */ }
75
+ }
51
76
  }
52
77
  else{
53
78
  throw `Relationship "${entityProperty.name}" could not be found please check if object has correct spelling or if it has been added to the context class`
@@ -56,11 +81,58 @@ class InsertManager {
56
81
  }
57
82
 
58
83
  if(entityProperty.type === "hasMany"){
59
- if(Array.isArray(propertyModel)){
84
+ // skip when not provided; only enforce array type if user supplied a value
85
+ if(propertyModel === undefined || propertyModel === null){
86
+ continue;
87
+ }
88
+ if(tools.checkIfArrayLike(propertyModel)){
60
89
  const propertyKeys = Object.keys(propertyModel);
61
90
  for (const propertykey of propertyKeys) {
62
91
  if(propertyModel[propertykey]){
63
- propertyModel[propertykey].__entity = tools.getEntity(property, $that._allEntities);
92
+ let targetName = entityProperty.foreignTable || property;
93
+ let resolved = tools.getEntity(targetName, $that._allEntities)
94
+ || tools.getEntity(tools.capitalize(targetName), $that._allEntities)
95
+ || tools.getEntity(property, $that._allEntities);
96
+ if(!resolved){
97
+ throw `Relationship entity for '${property}' could not be resolved. Expected '${targetName}'.`;
98
+ }
99
+ // Coerce primitive into object with primary key if user passed an id
100
+ if(typeof propertyModel[propertykey] !== "object" || propertyModel[propertykey] === null){
101
+ const childPrimaryKey = tools.getPrimaryKeyObject(resolved);
102
+ const primitiveValue = propertyModel[propertykey];
103
+ propertyModel[propertykey] = {};
104
+ propertyModel[propertykey][childPrimaryKey] = primitiveValue;
105
+ }
106
+ propertyModel[propertykey].__entity = resolved;
107
+ propertyModel[propertykey][currentModel.__entity.__name] = SQL.id;
108
+ $that.runQueries(propertyModel[propertykey]);
109
+ }
110
+ }
111
+ }
112
+ else{
113
+ throw `Relationship "${entityProperty.name}" must be an array`;
114
+ }
115
+ }
116
+
117
+ if(entityProperty.type === "hasManyThrough"){
118
+ if(tools.checkIfArrayLike(propertyModel)){
119
+ const propertyKeys = Object.keys(propertyModel);
120
+ for (const propertykey of propertyKeys) {
121
+ if(propertyModel[propertykey]){
122
+ let targetName = entityProperty.foreignTable || property;
123
+ let resolved = tools.getEntity(targetName, $that._allEntities)
124
+ || tools.getEntity(tools.capitalize(targetName), $that._allEntities)
125
+ || tools.getEntity(property, $that._allEntities);
126
+ if(!resolved){
127
+ throw `Relationship entity for '${property}' could not be resolved. Expected '${targetName}'.`;
128
+ }
129
+ if(typeof propertyModel[propertykey] !== "object" || propertyModel[propertykey] === null){
130
+ const childPrimaryKey = tools.getPrimaryKeyObject(resolved);
131
+ const primitiveValue = propertyModel[propertykey];
132
+ propertyModel[propertykey] = {};
133
+ propertyModel[propertykey][childPrimaryKey] = primitiveValue;
134
+ }
135
+ propertyModel[propertykey].__entity = resolved;
64
136
  propertyModel[propertykey][currentModel.__entity.__name] = SQL.id;
65
137
  $that.runQueries(propertyModel[propertykey]);
66
138
  }
@@ -74,11 +146,15 @@ class InsertManager {
74
146
  }
75
147
  }
76
148
  else{
77
- var name = currentModel.__entity.__name;
78
- console.log(`entity ${name} not valid`);
149
+ var messages = this._errorModel.errors;
150
+ const combinedError = messages.join('; and ');
151
+ throw combinedError;
152
+
79
153
  }
80
154
  }
81
155
 
156
+
157
+
82
158
  // will insert belongs to row first and return the id so that next call can be make correctly
83
159
  belongsToInsert(currentModel, modelEntity){
84
160
  var $that = this;
@@ -89,7 +165,7 @@ class InsertManager {
89
165
  // check if model is a an object. If so insert the child first then the parent.
90
166
  if(typeof newPropertyModel === 'object'){
91
167
  newPropertyModel.__entity = tools.getEntity(entity, $that._allEntities);
92
- var propertyCleanCurrentModel = tools.removePrimarykeyandVirtual(newPropertyModel, newPropertyModel.__entity);
168
+ var propertyCleanCurrentModel = tools.clearAllProto(newPropertyModel);
93
169
  this.validateEntity(propertyCleanCurrentModel, newPropertyModel, newPropertyModel.__entity);
94
170
  var propertySQL = this._SQLEngine.insert(newPropertyModel);
95
171
  currentModel[foreignKey] = propertySQL.id;
@@ -102,35 +178,62 @@ class InsertManager {
102
178
  // update the currentModel.
103
179
  return currentModel;
104
180
  }
105
- // validate entity for nullable fields
181
+
182
+ // validate entity for nullable fields and if the entity has any values at all
106
183
  validateEntity(currentModel, currentRealModel, entityModel){
107
184
  for(var entity in entityModel) {
108
185
  var currentEntity = entityModel[entity];
109
186
  if (entityModel.hasOwnProperty(entity)) {
110
187
  // check if there is a default value
111
188
  if(currentEntity.default){
112
- if(!currentRealModel[entity]){
189
+ if(currentRealModel[entity] === undefined || currentRealModel[entity] === null){
113
190
  // if its empty add the default value
114
191
  currentRealModel[entity] = currentEntity.default;
115
192
  }
116
193
  }
117
194
 
118
- // SKIP belongs too
195
+ // SKIP belongs too ----- // call sets for correct data for DB
119
196
  if(currentEntity.type !== "belongsTo" && currentEntity.type !== "hasMany"){
120
197
  if(currentEntity.relationshipType !== "belongsTo"){
198
+ // Auto-populate common timestamp fields if required and missing
199
+ if((entity === 'created_at' || entity === 'updated_at') && (currentRealModel[entity] === undefined || currentRealModel[entity] === null)){
200
+ var nowVal = Date.now().toString();
201
+ currentRealModel[entity] = nowVal;
202
+ currentModel[entity] = nowVal;
203
+ }
121
204
  // primary is always null in an insert so validation insert must be null
122
205
  if(currentEntity.nullable === false && !currentEntity.primary){
123
206
  // if it doesnt have a get method then call error
124
207
  if(currentEntity.set === undefined){
125
- if(currentModel[entity] === undefined || currentModel[entity] === null || currentModel[entity] === "" ){
208
+ const realVal = currentRealModel[entity];
209
+ const cleanVal = currentModel[entity];
210
+ let hasValue = (realVal !== undefined && realVal !== null) || (cleanVal !== undefined && cleanVal !== null);
211
+ // For strings, empty string should be considered invalid for notNullable
212
+ const candidate = (realVal !== undefined && realVal !== null) ? realVal : cleanVal;
213
+ if(typeof candidate === 'string' && candidate.trim() === ''){
214
+ hasValue = false;
215
+ }
216
+ // Fallback: check backing field on tracked model if both reads were undefined/null
217
+ if(!hasValue && currentRealModel && currentRealModel.__proto__){
218
+ const backing = currentRealModel.__proto__["_" + entity];
219
+ hasValue = (backing !== undefined && backing !== null);
220
+ if(hasValue){
221
+ // normalize into both models so downstream sees it
222
+ currentRealModel[entity] = backing;
223
+ currentModel[entity] = backing;
224
+ }
225
+ }
226
+ if(!hasValue){
126
227
  this._errorModel.isValid = false;
127
228
  var errorMessage = `Entity ${currentModel.__entity.__name} column ${entity} is a required Field`;
128
229
  this._errorModel.errors.push(errorMessage);
129
- throw errorMessage;
230
+ //throw errorMessage;
130
231
  }
131
232
  }
132
233
  else{
133
- currentRealModel[entity] = currentEntity.set(currentModel[entity]);
234
+ var realData = currentEntity.set(currentModel[entity]);
235
+ currentRealModel[entity] = realData;
236
+ currentModel[entity] = realData;
134
237
  }
135
238
  }
136
239
  }