masterrecord 0.3.24 → 0.3.26

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.
@@ -36,7 +36,9 @@
36
36
  "WebSearch",
37
37
  "WebFetch(domain:github.com)",
38
38
  "Bash(master=development masterrecord update-database qaContext)",
39
- "Bash(chmod:*)"
39
+ "Bash(chmod:*)",
40
+ "Bash(npm install:*)",
41
+ "Bash(sqlite3:*)"
40
42
  ],
41
43
  "deny": [],
42
44
  "ask": []
@@ -284,7 +284,7 @@ class queryMethods{
284
284
  const placeholders = this.__queryObject.parameters.addParams(itemArray, dbType);
285
285
  // Replace $$ first (preferred), then $ (backwards compatibility)
286
286
  if(str.includes('$$')){
287
- str = str.replace("$$", placeholders);
287
+ str = str.replace(/\$\$/g, placeholders);
288
288
  } else {
289
289
  // Replace single $ but not $N (postgres placeholders)
290
290
  str = str.replace(/\$(?!\d)/, placeholders);
@@ -305,7 +305,7 @@ class queryMethods{
305
305
  const placeholder = this.__queryObject.parameters.addParam(item, dbType);
306
306
  // Replace $$ first (preferred), then $ (backwards compatibility)
307
307
  if(str.includes('$$')){
308
- str = str.replace("$$", placeholder);
308
+ str = str.replace(/\$\$/g, placeholder);
309
309
  } else {
310
310
  // Replace single $ but not $N (postgres placeholders)
311
311
  str = str.replace(/\$(?!\d)/, placeholder);
package/SQLLiteEngine.js CHANGED
@@ -1251,11 +1251,10 @@ class SQLLiteEngine {
1251
1251
  * Close database connection
1252
1252
  * Required for proper cleanup of better-sqlite3 native bindings
1253
1253
  */
1254
- close() {
1255
- if (this.db) {
1256
- this.db.close();
1257
- console.log('SQLite database closed');
1258
- }
1254
+ async close() {
1255
+ return Promise.resolve(
1256
+ this.db ? (this.db.close(), console.log('SQLite database closed')) : null
1257
+ );
1259
1258
  }
1260
1259
  }
1261
1260
 
package/context.js CHANGED
@@ -20,7 +20,7 @@ const modelBuilder = require('./Entity/entityModelBuilder');
20
20
  const query = require('masterrecord/QueryLanguage/queryMethods');
21
21
  const tools = require('./Tools');
22
22
  const SQLLiteEngine = require('masterrecord/SQLLiteEngine');
23
- const MySQLEngine = require('masterrecord/realMySQLEngine');
23
+ const MySQLEngine = require('masterrecord/mySQLEngine');
24
24
  const PostgresEngine = require('masterrecord/postgresEngine');
25
25
  const insertManager = require('./insertManager');
26
26
  const deleteManager = require('./deleteManager');
@@ -28,7 +28,7 @@ const globSearch = require('glob');
28
28
  const fs = require('fs');
29
29
  const path = require('path');
30
30
  const appRoot = require('app-root-path');
31
- const MySQLAsyncClient = require('masterrecord/mySQLAsyncConnect');
31
+ const MySQLAsyncClient = require('masterrecord/mySQLConnect');
32
32
  const PostgresClient = require('masterrecord/postgresSyncConnect');
33
33
  const QueryCache = require('./Cache/QueryCache');
34
34
 
@@ -0,0 +1,270 @@
1
+ # MasterRecord Troubleshooting Guide
2
+
3
+ This guide covers common issues and potential pitfalls when using MasterRecord.
4
+
5
+ ## Navigation Properties vs Regular Properties
6
+
7
+ ### Understanding the Difference
8
+
9
+ MasterRecord provides **two different ways** to access related entities, and mixing them causes bugs.
10
+
11
+ #### 1. Navigation Properties (Lazy Loading)
12
+
13
+ When you access a related entity using a **capital letter that matches the entity name**, MasterRecord treats it as a navigation property with lazy loading:
14
+
15
+ ```javascript
16
+ // Accessing with capital letter triggers lazy loading
17
+ const auth = await user.Auth; // Executes a database query
18
+ ```
19
+
20
+ **How it works:**
21
+ - MasterRecord looks up the entity definition in your context
22
+ - Executes a SQL query to load the related record
23
+ - Returns a **Promise** that must be awaited
24
+ - Defined in your entity's relationship configuration (hasOne, hasMany, belongsTo)
25
+
26
+ #### 2. Regular Properties (Direct Access)
27
+
28
+ When you manually attach an object to an entity as a regular property, you access it directly:
29
+
30
+ ```javascript
31
+ // Attach as regular property (any case except navigation property name)
32
+ user.auth = someAuthObject; // lowercase
33
+
34
+ // Access directly - no query, just returns the object
35
+ console.log(user.auth.password_hash); // Immediate access
36
+ ```
37
+
38
+ ### Common Bug: Mixing Both Approaches
39
+
40
+ **❌ THIS CAUSES BUGS:**
41
+
42
+ ```javascript
43
+ // In authService.js - attaching as regular property (lowercase)
44
+ async findAuthByEmail(email, context) {
45
+ var user = await context.User
46
+ .where(r => r.email.toLowerCase() == $$, email.toLowerCase())
47
+ .single();
48
+
49
+ var auth = await context.Auth
50
+ .where(a => a.user_id == $$, user.id)
51
+ .single();
52
+
53
+ // Attaching as lowercase regular property
54
+ user.auth = auth;
55
+ return user;
56
+ }
57
+
58
+ // In credentialsController.js - accessing as navigation property (capital)
59
+ async login(req) {
60
+ var authObj = await req.authService.authenticate(email, password, req.userContext);
61
+
62
+ // ❌ BUG: Accessing as capital triggers navigation property!
63
+ authObj.user.Auth.temp_access_token = refreshToken; // Returns Promise, not auth object!
64
+ req.userContext.saveChanges(); // Doesn't save - Promise isn't resolved
65
+ }
66
+ ```
67
+
68
+ **What happens:**
69
+ 1. `findAuthByEmail()` attaches auth as `user.auth` (lowercase regular property)
70
+ 2. `credentialsController` tries to access `user.Auth` (capital navigation property)
71
+ 3. MasterRecord sees the capital letter and thinks "navigation property!"
72
+ 4. It executes a lazy loading query and returns a Promise
73
+ 5. The Promise isn't awaited, so you get `undefined` or the Promise object itself
74
+ 6. Your data doesn't save, properties return undefined, bcrypt fails with "Illegal arguments"
75
+
76
+ ### Solutions
77
+
78
+ #### Option 1: Use Regular Properties (Recommended when entity already loaded)
79
+
80
+ **✅ CORRECT:**
81
+
82
+ ```javascript
83
+ // Attach as lowercase regular property
84
+ user.auth = auth;
85
+
86
+ // Access as lowercase regular property
87
+ authObj.user.auth.temp_access_token = refreshToken;
88
+ authObj.user.auth.login_counter = authObj.user.auth.login_counter + 1;
89
+ req.userContext.saveChanges();
90
+ ```
91
+
92
+ **Benefits:**
93
+ - ✅ No extra database queries
94
+ - ✅ Synchronous access
95
+ - ✅ More performant
96
+
97
+ **Use when:**
98
+ - You've already loaded the related entity with an explicit query
99
+ - You want direct access without database overhead
100
+ - You need synchronous property access
101
+
102
+ #### Option 2: Use Navigation Properties with Await (When you want lazy loading)
103
+
104
+ **✅ CORRECT:**
105
+
106
+ ```javascript
107
+ // Access navigation property with await
108
+ const auth = await authObj.user.Auth; // Must await!
109
+ auth.temp_access_token = refreshToken;
110
+ auth.login_counter = auth.login_counter + 1;
111
+ req.userContext.saveChanges();
112
+ ```
113
+
114
+ **Benefits:**
115
+ - ✅ Automatic loading of related entities
116
+ - ✅ Works without manual queries
117
+
118
+ **Drawbacks:**
119
+ - ❌ Extra database query (even if you already loaded it)
120
+ - ❌ Must remember to await
121
+ - ❌ Async only
122
+
123
+ **Use when:**
124
+ - You want lazy loading behavior
125
+ - You haven't already loaded the related entity
126
+ - You're okay with the extra database query
127
+
128
+ ### Best Practice: Be Consistent
129
+
130
+ The key is **consistency** - pick one approach per relationship and stick with it:
131
+
132
+ ```javascript
133
+ // Good: Explicit loading + regular properties
134
+ async findAuthByEmail(email, context) {
135
+ const user = await context.User.where(r => r.email == $$, email).single();
136
+ const auth = await context.Auth.where(a => a.user_id == $$, user.id).single();
137
+ user.auth = auth; // lowercase
138
+ return user;
139
+ }
140
+
141
+ // Access consistently with lowercase
142
+ authObj.user.auth.temp_access_token = refreshToken; // lowercase
143
+ ```
144
+
145
+ OR
146
+
147
+ ```javascript
148
+ // Good: Let navigation properties do the work
149
+ async findAuthByEmail(email, context) {
150
+ const user = await context.User.where(r => r.email == $$, email).single();
151
+ // Don't manually load auth - let navigation property handle it
152
+ return user;
153
+ }
154
+
155
+ // Access with await
156
+ const auth = await authObj.user.Auth; // capital + await
157
+ auth.temp_access_token = refreshToken;
158
+ ```
159
+
160
+ ### Real-World Example: Authentication Bug
161
+
162
+ This bug was found in a production authentication system where login succeeded but immediately redirected back to the login page.
163
+
164
+ **The Problem:**
165
+ ```javascript
166
+ // authService.js - Line 306
167
+ user.auth = auth; // Attaching as lowercase
168
+
169
+ // credentialsController.js - Line 287-288
170
+ authObj.user.Auth.temp_access_token = refreshToken; // ❌ Accessing as capital!
171
+ req.userContext.saveChanges();
172
+ ```
173
+
174
+ **What happened:**
175
+ 1. JWT token was generated correctly
176
+ 2. `authObj.user.Auth.temp_access_token = refreshToken` didn't actually save the token
177
+ 3. It set the property on a Promise object instead of the auth entity
178
+ 4. Database query `WHERE temp_access_token = ?` returned no results
179
+ 5. `currentUser()` check failed
180
+ 6. User was redirected back to login
181
+
182
+ **The Fix:**
183
+ ```javascript
184
+ // credentialsController.js - Line 287-288
185
+ authObj.user.auth.temp_access_token = refreshToken; // ✅ lowercase matches attachment
186
+ authObj.user.auth.login_counter = authObj.user.auth.login_counter + 1;
187
+ req.userContext.saveChanges(); // Now it actually saves!
188
+ ```
189
+
190
+ ### Debugging Tips
191
+
192
+ If you're seeing any of these symptoms:
193
+ - Properties returning `undefined` when you know they exist in the database
194
+ - `bcrypt.compareSync()` failing with "Illegal arguments: string, undefined"
195
+ - Data not saving when you call `saveChanges()`
196
+ - `[object Promise]` appearing in logs instead of actual values
197
+ - Queries executing successfully but entities seem empty
198
+
199
+ **Check for navigation property mismatches:**
200
+
201
+ 1. Add debug logging:
202
+ ```javascript
203
+ console.log('Type of user.Auth:', typeof user.Auth);
204
+ console.log('Is Promise?', user.Auth instanceof Promise);
205
+ console.log('Value:', user.Auth);
206
+ ```
207
+
208
+ 2. Look for:
209
+ - Capital letter access (`user.Auth`, `auth.User`) without `await`
210
+ - Mixing lowercase attachment with capital access
211
+ - Accessing navigation properties in non-async contexts
212
+
213
+ 3. Fix by:
214
+ - Making access match attachment (both lowercase)
215
+ - OR using `await` with capital navigation properties
216
+ - OR explicitly loading entities instead of relying on lazy loading
217
+
218
+ ### Additional Examples
219
+
220
+ #### Creating New Entities with Relationships
221
+
222
+ When creating new entities, you can use capital letters during construction:
223
+
224
+ ```javascript
225
+ // ✅ This is fine - entity not yet persisted
226
+ var user = new userEntity();
227
+ var auth = new authEntity();
228
+ user.Auth = auth; // Capital is OK here
229
+ auth.password_hash = bcrypt.hashSync(password, salt);
230
+ context.User.add(user);
231
+ await context.saveChanges();
232
+
233
+ // ❌ After saveChanges, use regular property or reload
234
+ // Store reference to avoid navigation property
235
+ auth.temp_access_token = refreshToken; // Use stored reference
236
+ await context.saveChanges();
237
+
238
+ // ❌ DON'T DO THIS after saveChanges:
239
+ user.Auth.temp_access_token = refreshToken; // Triggers navigation property!
240
+ ```
241
+
242
+ #### Working with Loaded Entities
243
+
244
+ ```javascript
245
+ // Load user from database
246
+ const user = await context.User.where(r => r.id == $$, userId).single();
247
+
248
+ // ✅ Option 1: Explicitly load and attach
249
+ const auth = await context.Auth.where(a => a.user_id == $$, user.id).single();
250
+ user.auth = auth; // lowercase
251
+ console.log(user.auth.password_hash); // Direct access
252
+
253
+ // ✅ Option 2: Use navigation property with await
254
+ const auth = await user.Auth; // capital + await
255
+ console.log(auth.password_hash);
256
+
257
+ // ❌ DON'T MIX:
258
+ user.auth = await context.Auth.where(a => a.user_id == $$, user.id).single();
259
+ console.log(user.Auth.password_hash); // Wrong! This is navigation property
260
+ ```
261
+
262
+ ## Summary
263
+
264
+ - **Navigation properties** (capital letter) = lazy loading = returns Promise = must await
265
+ - **Regular properties** (lowercase or any non-entity name) = direct access = immediate value
266
+ - **Never mix them** - be consistent in how you attach and access related entities
267
+ - **When in doubt**, explicitly load entities and use lowercase regular properties
268
+ - **Add debug logging** to catch Promise objects being treated as entities
269
+
270
+ This pattern is consistent throughout MasterRecord and applies to all relationship types: `hasOne`, `hasMany`, `belongsTo`, and `hasManyThrough`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "masterrecord",
3
- "version": "0.3.24",
3
+ "version": "0.3.26",
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": {
File without changes
File without changes