masterrecord 0.3.55 → 0.3.57

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
@@ -119,7 +119,12 @@ program.option('-V', 'output the version');
119
119
  }
120
120
  const snapDir = path.dirname(file);
121
121
  const contextAbs = path.resolve(snapDir, contextSnapshot.contextLocation || '');
122
- const migBase = path.resolve(snapDir, contextSnapshot.migrationFolder || '.');
122
+ let migBase = path.resolve(snapDir, contextSnapshot.migrationFolder || '.');
123
+ if (!fs.existsSync(migBase)) {
124
+ console.warn(`⚠️ Resolved migration path does not exist: ${migBase}`);
125
+ console.warn(` Falling back to snapshot directory: ${snapDir}`);
126
+ migBase = snapDir;
127
+ }
123
128
  // Find latest migration file (so we can use its class which extends schema)
124
129
  var migrationFiles = globSearch.sync(`**/*_migration.js`, { cwd: migBase, dot: true, windowsPathsNoEscape: true });
125
130
  migrationFiles = (migrationFiles || []).map(f => path.resolve(migBase, f));
@@ -242,7 +247,12 @@ program.option('-V', 'output the version');
242
247
  // Resolve relative paths from the snapshot directory (portable snapshots)
243
248
  const snapDir = path.dirname(file);
244
249
  const contextAbs = path.resolve(snapDir, contextSnapshot.contextLocation || '');
245
- const migBase = path.resolve(snapDir, contextSnapshot.migrationFolder || '.');
250
+ let migBase = path.resolve(snapDir, contextSnapshot.migrationFolder || '.');
251
+ if (!fs.existsSync(migBase)) {
252
+ console.warn(`⚠️ Resolved migration path does not exist: ${migBase}`);
253
+ console.warn(` Falling back to snapshot directory: ${snapDir}`);
254
+ migBase = snapDir;
255
+ }
246
256
 
247
257
  let ContextCtor;
248
258
  try{
@@ -345,7 +355,12 @@ program.option('-V', 'output the version');
345
355
 
346
356
  const snapDir = path.dirname(file);
347
357
  const contextAbs = path.resolve(snapDir, contextSnapshot.contextLocation || '');
348
- const migBase = path.resolve(snapDir, contextSnapshot.migrationFolder || '.');
358
+ let migBase = path.resolve(snapDir, contextSnapshot.migrationFolder || '.');
359
+ if (!fs.existsSync(migBase)) {
360
+ console.warn(`⚠️ Resolved migration path does not exist: ${migBase}`);
361
+ console.warn(` Falling back to snapshot directory: ${snapDir}`);
362
+ migBase = snapDir;
363
+ }
349
364
 
350
365
  console.log(`\n🔍 Searching for migration files in: ${migBase}`);
351
366
  var migrationFiles = globSearch.sync(`**/*_migration.js`, { cwd: migBase, dot: true, windowsPathsNoEscape: true });
package/insertManager.js CHANGED
@@ -342,6 +342,36 @@ class InsertManager {
342
342
  continue;
343
343
  }
344
344
 
345
+ // Detect non-primitive values (Promise, Array, plain Object) on scalar columns
346
+ const isRelationship = currentEntity.type === RELATIONSHIP_TYPES.BELONGS_TO ||
347
+ currentEntity.type === RELATIONSHIP_TYPES.HAS_MANY ||
348
+ currentEntity.type === RELATIONSHIP_TYPES.HAS_MANY_THROUGH ||
349
+ currentEntity.type === RELATIONSHIP_TYPES.HAS_ONE ||
350
+ currentEntity.relationshipType === RELATIONSHIP_TYPES.BELONGS_TO;
351
+
352
+ if (!isRelationship) {
353
+ const val = currentRealModel[entity];
354
+ if (val != null && typeof val === 'object') {
355
+ const entityName = entityModel.__name || 'unknown';
356
+ if (typeof val.then === 'function') {
357
+ this._errorModel.isValid = false;
358
+ this._errorModel.errors.push(
359
+ `Property '${entity}' on entity '${entityName}' contains a Promise. Did you forget to await an async call?`
360
+ );
361
+ } else if (Array.isArray(val)) {
362
+ this._errorModel.isValid = false;
363
+ this._errorModel.errors.push(
364
+ `Property '${entity}' on entity '${entityName}' contains an Array, expected a scalar value`
365
+ );
366
+ } else if (!(val instanceof Date)) {
367
+ this._errorModel.isValid = false;
368
+ this._errorModel.errors.push(
369
+ `Property '${entity}' on entity '${entityName}' contains an Object, expected a scalar value`
370
+ );
371
+ }
372
+ }
373
+ }
374
+
345
375
  // check if there is a default value
346
376
  if (currentEntity.default) {
347
377
  if (currentRealModel[entity] === undefined || currentRealModel[entity] === null) {
package/mySQLConnect.js CHANGED
@@ -11,6 +11,8 @@ class MySQLAsyncClient {
11
11
  database: config.database,
12
12
  waitForConnections: true,
13
13
  connectionLimit: config.connectionLimit || 10,
14
+ maxIdle: config.maxIdle ?? 2,
15
+ idleTimeout: config.idleTimeout ?? 30000,
14
16
  queueLimit: 0
15
17
  };
16
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "masterrecord",
3
- "version": "0.3.55",
3
+ "version": "0.3.57",
4
4
  "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 ",
5
5
  "main": "MasterRecord.js",
6
6
  "bin": {
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Test: Promise / non-primitive detection in InsertManager.validateEntity()
3
+ *
4
+ * Verifies that assigning a Promise, Array, or plain Object to a scalar
5
+ * entity column produces a clear validation error instead of being silently
6
+ * dropped from the INSERT statement.
7
+ */
8
+
9
+ // Alias 'masterrecord' to local root so insertManager's require('masterrecord/...') resolves
10
+ const path = require('path');
11
+ const Module = require('module');
12
+ const __MASTERRECORD_ROOT__ = path.join(__dirname, '..');
13
+ const __ORIGINAL_REQUIRE__ = Module.prototype.require;
14
+ Module.prototype.require = function(request) {
15
+ if (request === 'masterrecord' || request.startsWith('masterrecord/')) {
16
+ const resolved = request === 'masterrecord'
17
+ ? __MASTERRECORD_ROOT__
18
+ : path.join(__MASTERRECORD_ROOT__, request.slice('masterrecord/'.length));
19
+ return __ORIGINAL_REQUIRE__.call(this, resolved);
20
+ }
21
+ return __ORIGINAL_REQUIRE__.call(this, request);
22
+ };
23
+
24
+ const InsertManager = require('../insertManager');
25
+
26
+ console.log("╔════════════════════════════════════════════════════════════════╗");
27
+ console.log("║ Promise / Non-Primitive Detection Test ║");
28
+ console.log("╚════════════════════════════════════════════════════════════════╝\n");
29
+
30
+ let passed = 0;
31
+ let failed = 0;
32
+
33
+ function assert(condition, label) {
34
+ if (condition) {
35
+ console.log(` ✓ ${label}`);
36
+ passed++;
37
+ } else {
38
+ console.log(` ✗ ${label}`);
39
+ failed++;
40
+ }
41
+ }
42
+
43
+ // Minimal stubs — we only need validateEntity(), no SQL engine
44
+ function makeErrorModel() {
45
+ return { isValid: true, errors: [] };
46
+ }
47
+
48
+ function makeManager() {
49
+ const err = makeErrorModel();
50
+ const mgr = new InsertManager(null, err, []);
51
+ return { mgr, err };
52
+ }
53
+
54
+ // Entity definition for a simple "User" table
55
+ const entityModel = {
56
+ __name: 'User',
57
+ id: { type: 'integer', primary: true, auto: true, nullable: true },
58
+ user_name: { type: 'string', nullable: false },
59
+ age: { type: 'integer', nullable: true },
60
+ role: { type: 'string', nullable: true },
61
+ };
62
+
63
+ // -----------------------------------------------------------
64
+ // Test 1: Promise assigned to a string column
65
+ // -----------------------------------------------------------
66
+ console.log("Test 1: Promise on string column");
67
+ console.log("──────────────────────────────────────────────────");
68
+ {
69
+ const { mgr, err } = makeManager();
70
+ const realModel = { user_name: Promise.resolve('alice'), age: 25, __entity: entityModel };
71
+ const cleanModel = { user_name: Promise.resolve('alice'), age: 25, __entity: entityModel };
72
+
73
+ mgr.validateEntity(cleanModel, realModel, entityModel);
74
+
75
+ assert(!err.isValid, 'Validation fails');
76
+ assert(err.errors.length === 1, 'Exactly one error');
77
+ assert(err.errors[0].includes('Promise'), 'Error mentions Promise');
78
+ assert(err.errors[0].includes('user_name'), 'Error mentions field name');
79
+ assert(err.errors[0].includes('await'), 'Error suggests await');
80
+ }
81
+
82
+ // -----------------------------------------------------------
83
+ // Test 2: Array assigned to a string column
84
+ // -----------------------------------------------------------
85
+ console.log("\nTest 2: Array on string column");
86
+ console.log("──────────────────────────────────────────────────");
87
+ {
88
+ const { mgr, err } = makeManager();
89
+ const realModel = { user_name: 'alice', role: ['admin', 'user'], __entity: entityModel };
90
+ const cleanModel = { ...realModel };
91
+
92
+ mgr.validateEntity(cleanModel, realModel, entityModel);
93
+
94
+ assert(!err.isValid, 'Validation fails');
95
+ assert(err.errors[0].includes('Array'), 'Error mentions Array');
96
+ assert(err.errors[0].includes('role'), 'Error mentions field name');
97
+ }
98
+
99
+ // -----------------------------------------------------------
100
+ // Test 3: Plain object assigned to an integer column
101
+ // -----------------------------------------------------------
102
+ console.log("\nTest 3: Plain object on integer column");
103
+ console.log("──────────────────────────────────────────────────");
104
+ {
105
+ const { mgr, err } = makeManager();
106
+ const realModel = { user_name: 'alice', age: { value: 25 }, __entity: entityModel };
107
+ const cleanModel = { ...realModel };
108
+
109
+ mgr.validateEntity(cleanModel, realModel, entityModel);
110
+
111
+ assert(!err.isValid, 'Validation fails');
112
+ assert(err.errors[0].includes('Object'), 'Error mentions Object');
113
+ assert(err.errors[0].includes('age'), 'Error mentions field name');
114
+ }
115
+
116
+ // -----------------------------------------------------------
117
+ // Test 4: Normal scalar values pass validation
118
+ // -----------------------------------------------------------
119
+ console.log("\nTest 4: Normal scalar values pass validation");
120
+ console.log("──────────────────────────────────────────────────");
121
+ {
122
+ const { mgr, err } = makeManager();
123
+ const realModel = { user_name: 'alice', age: 25, role: 'admin', __entity: entityModel };
124
+ const cleanModel = { ...realModel };
125
+
126
+ mgr.validateEntity(cleanModel, realModel, entityModel);
127
+
128
+ assert(err.isValid, 'Validation passes');
129
+ assert(err.errors.length === 0, 'No errors');
130
+ }
131
+
132
+ // -----------------------------------------------------------
133
+ // Test 5: Date objects are allowed (not flagged)
134
+ // -----------------------------------------------------------
135
+ console.log("\nTest 5: Date objects are allowed");
136
+ console.log("──────────────────────────────────────────────────");
137
+ {
138
+ const entityWithDate = {
139
+ __name: 'Event',
140
+ id: { type: 'integer', primary: true, auto: true, nullable: true },
141
+ event_date: { type: 'string', nullable: true },
142
+ };
143
+ const { mgr, err } = makeManager();
144
+ const realModel = { event_date: new Date(), __entity: entityWithDate };
145
+ const cleanModel = { ...realModel };
146
+
147
+ mgr.validateEntity(cleanModel, realModel, entityWithDate);
148
+
149
+ assert(err.isValid, 'Date objects pass validation');
150
+ assert(err.errors.length === 0, 'No errors for Date');
151
+ }
152
+
153
+ // -----------------------------------------------------------
154
+ // Test 6: Relationship columns are NOT flagged for objects
155
+ // -----------------------------------------------------------
156
+ console.log("\nTest 6: Relationship columns allow objects");
157
+ console.log("──────────────────────────────────────────────────");
158
+ {
159
+ const entityWithRel = {
160
+ __name: 'Post',
161
+ id: { type: 'integer', primary: true, auto: true, nullable: true },
162
+ title: { type: 'string', nullable: false },
163
+ author: { type: 'belongsTo', relationshipType: 'belongsTo', foreignKey: 'author_id', name: 'author' },
164
+ tags: { type: 'hasMany', name: 'tags' },
165
+ meta: { type: 'hasOne', name: 'meta' },
166
+ cats: { type: 'hasManyThrough', name: 'cats' },
167
+ };
168
+ const { mgr, err } = makeManager();
169
+ const realModel = {
170
+ title: 'Hello',
171
+ author: { id: 1, name: 'Alice' },
172
+ tags: [{ id: 1 }],
173
+ meta: { key: 'val' },
174
+ cats: [{ id: 2 }],
175
+ __entity: entityWithRel,
176
+ };
177
+ const cleanModel = { ...realModel };
178
+
179
+ mgr.validateEntity(cleanModel, realModel, entityWithRel);
180
+
181
+ assert(err.isValid, 'Relationship objects pass validation');
182
+ assert(err.errors.length === 0, 'No errors for relationship objects');
183
+ }
184
+
185
+ // -----------------------------------------------------------
186
+ // Test 7: null and undefined values are NOT flagged
187
+ // -----------------------------------------------------------
188
+ console.log("\nTest 7: null/undefined values are not flagged");
189
+ console.log("──────────────────────────────────────────────────");
190
+ {
191
+ const { mgr, err } = makeManager();
192
+ const realModel = { user_name: 'alice', age: null, role: undefined, __entity: entityModel };
193
+ const cleanModel = { ...realModel };
194
+
195
+ mgr.validateEntity(cleanModel, realModel, entityModel);
196
+
197
+ assert(err.isValid, 'null/undefined pass validation');
198
+ assert(err.errors.length === 0, 'No errors for null/undefined');
199
+ }
200
+
201
+ // -----------------------------------------------------------
202
+ // Test 8: Multiple bad fields produce multiple errors
203
+ // -----------------------------------------------------------
204
+ console.log("\nTest 8: Multiple bad fields produce multiple errors");
205
+ console.log("──────────────────────────────────────────────────");
206
+ {
207
+ const { mgr, err } = makeManager();
208
+ const realModel = {
209
+ user_name: Promise.resolve('alice'),
210
+ age: [1, 2, 3],
211
+ role: { x: 1 },
212
+ __entity: entityModel,
213
+ };
214
+ const cleanModel = { ...realModel };
215
+
216
+ mgr.validateEntity(cleanModel, realModel, entityModel);
217
+
218
+ assert(!err.isValid, 'Validation fails');
219
+ assert(err.errors.length === 3, 'Three errors (Promise + Array + Object)');
220
+ }
221
+
222
+ // Summary
223
+ console.log("\n" + "=".repeat(64));
224
+ console.log(`Test Results: ${passed} passed, ${failed} failed`);
225
+ console.log("=".repeat(64));
226
+
227
+ if (failed > 0) {
228
+ process.exit(1);
229
+ }