masterrecord 0.1.4 → 0.2.1

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.9
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");
233
+ }
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
159
258
  }
160
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");
@@ -306,6 +434,9 @@ class context {
306
434
  var add = true;
307
435
  for (var mod in this.__trackedEntities) {
308
436
  var id = this.__trackedEntities[mod].__ID;
437
+ if(id === undefined){
438
+ id = Math.floor((Math.random() * 100000) + 1);
439
+ }
309
440
  if(id === model.__ID){
310
441
  add = false;
311
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.6
2
+ // version 0.0.15
3
3
  var tools = require('./Tools');
4
4
  var queryScript = require('masterrecord/QueryLanguage/queryScript');
5
5
 
@@ -18,45 +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
- var idVal;
36
-
37
- if(!getQueryModel[0]){
38
- idVal = getQueryModel[primaryKey]
39
- }
40
- else{
41
- idVal = getQueryModel[0][primaryKey];
42
- }
43
-
44
- currentModel[primaryKey] = idVal;
37
+ currentModel[primaryKey] = SQL.id;
45
38
  }
46
39
 
40
+ const proto = Object.getPrototypeOf(currentModel);
41
+ const props = Object.getOwnPropertyNames(proto);
42
+ const cleanPropList = tools.returnEntityList(props, modelEntity);
47
43
  const modelKeys = Object.keys(currentModel);
44
+ const mergedArray = [...new Set(modelKeys.concat(cleanPropList))];
48
45
  // loop through model properties
49
- for (const property of modelKeys) {
46
+ for (const property of mergedArray) {
50
47
  var propertyModel = currentModel[property];
51
48
  var entityProperty = modelEntity[property] ? modelEntity[property] : {};
52
49
  if(entityProperty.type === "hasOne"){
53
- // make sure property model is an object not a primary data type like number or string
54
- if(typeof(propertyModel) === "object"){
50
+ // only insert child if user provided a concrete object with data
51
+ if(propertyModel && typeof(propertyModel) === "object" ){
55
52
  // check if model has its own entity
56
53
  if(modelEntity){
57
- propertyModel.__entity = tools.getEntity(property, $that._allEntities);
58
- propertyModel[currentModel.__entity.__name] = SQL.id;
59
- $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
+ }
60
76
  }
61
77
  else{
62
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`
@@ -65,11 +81,58 @@ class InsertManager {
65
81
  }
66
82
 
67
83
  if(entityProperty.type === "hasMany"){
68
- 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)){
69
89
  const propertyKeys = Object.keys(propertyModel);
70
90
  for (const propertykey of propertyKeys) {
71
91
  if(propertyModel[propertykey]){
72
- 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;
73
136
  propertyModel[propertykey][currentModel.__entity.__name] = SQL.id;
74
137
  $that.runQueries(propertyModel[propertykey]);
75
138
  }
@@ -83,11 +146,15 @@ class InsertManager {
83
146
  }
84
147
  }
85
148
  else{
86
- var name = currentModel.__entity.__name;
87
- console.log(`entity ${name} not valid`);
149
+ var messages = this._errorModel.errors;
150
+ const combinedError = messages.join('; and ');
151
+ throw combinedError;
152
+
88
153
  }
89
154
  }
90
155
 
156
+
157
+
91
158
  // will insert belongs to row first and return the id so that next call can be make correctly
92
159
  belongsToInsert(currentModel, modelEntity){
93
160
  var $that = this;
@@ -98,7 +165,7 @@ class InsertManager {
98
165
  // check if model is a an object. If so insert the child first then the parent.
99
166
  if(typeof newPropertyModel === 'object'){
100
167
  newPropertyModel.__entity = tools.getEntity(entity, $that._allEntities);
101
- var propertyCleanCurrentModel = tools.removePrimarykeyandVirtual(newPropertyModel, newPropertyModel.__entity);
168
+ var propertyCleanCurrentModel = tools.clearAllProto(newPropertyModel);
102
169
  this.validateEntity(propertyCleanCurrentModel, newPropertyModel, newPropertyModel.__entity);
103
170
  var propertySQL = this._SQLEngine.insert(newPropertyModel);
104
171
  currentModel[foreignKey] = propertySQL.id;
@@ -111,35 +178,62 @@ class InsertManager {
111
178
  // update the currentModel.
112
179
  return currentModel;
113
180
  }
114
- // validate entity for nullable fields
181
+
182
+ // validate entity for nullable fields and if the entity has any values at all
115
183
  validateEntity(currentModel, currentRealModel, entityModel){
116
184
  for(var entity in entityModel) {
117
185
  var currentEntity = entityModel[entity];
118
186
  if (entityModel.hasOwnProperty(entity)) {
119
187
  // check if there is a default value
120
188
  if(currentEntity.default){
121
- if(!currentRealModel[entity]){
189
+ if(currentRealModel[entity] === undefined || currentRealModel[entity] === null){
122
190
  // if its empty add the default value
123
191
  currentRealModel[entity] = currentEntity.default;
124
192
  }
125
193
  }
126
194
 
127
- // SKIP belongs too
195
+ // SKIP belongs too ----- // call sets for correct data for DB
128
196
  if(currentEntity.type !== "belongsTo" && currentEntity.type !== "hasMany"){
129
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
+ }
130
204
  // primary is always null in an insert so validation insert must be null
131
205
  if(currentEntity.nullable === false && !currentEntity.primary){
132
206
  // if it doesnt have a get method then call error
133
207
  if(currentEntity.set === undefined){
134
- 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){
135
227
  this._errorModel.isValid = false;
136
228
  var errorMessage = `Entity ${currentModel.__entity.__name} column ${entity} is a required Field`;
137
229
  this._errorModel.errors.push(errorMessage);
138
- throw errorMessage;
230
+ //throw errorMessage;
139
231
  }
140
232
  }
141
233
  else{
142
- currentRealModel[entity] = currentEntity.set(currentModel[entity]);
234
+ var realData = currentEntity.set(currentModel[entity]);
235
+ currentRealModel[entity] = realData;
236
+ currentModel[entity] = realData;
143
237
  }
144
238
  }
145
239
  }