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.
- package/.claude/settings.local.json +3 -1
- package/QueryLanguage/queryMethods.js +2 -2
- package/SQLLiteEngine.js +4 -5
- package/context.js +2 -2
- package/docs/TROUBLESHOOTING.md +270 -0
- package/package.json +1 -1
- /package/{mySQLAsyncConnect.js → mySQLConnect.js} +0 -0
- /package/{realMySQLEngine.js → mySQLEngine.js} +0 -0
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
1256
|
-
this.db.close()
|
|
1257
|
-
|
|
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/
|
|
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/
|
|
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.
|
|
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
|