masterrecord 0.3.25 → 0.3.27
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/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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "masterrecord",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.27",
|
|
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": {
|
package/readme.md
CHANGED
|
@@ -416,7 +416,7 @@ const user = db.User.new();
|
|
|
416
416
|
user.tags = ['admin', 'moderator']; // Assign array
|
|
417
417
|
await db.saveChanges(); // Stored as '["admin","moderator"]'
|
|
418
418
|
|
|
419
|
-
const loaded = db.User.findById(user.id);
|
|
419
|
+
const loaded = await db.User.findById(user.id);
|
|
420
420
|
console.log(loaded.tags); // ['admin', 'moderator'] - JavaScript array!
|
|
421
421
|
```
|
|
422
422
|
|
|
@@ -425,19 +425,19 @@ console.log(loaded.tags); // ['admin', 'moderator'] - JavaScript array!
|
|
|
425
425
|
### Basic Queries
|
|
426
426
|
|
|
427
427
|
```javascript
|
|
428
|
-
// Find all
|
|
429
|
-
const users = db.User.toList();
|
|
428
|
+
// Find all (requires await)
|
|
429
|
+
const users = await db.User.toList();
|
|
430
430
|
|
|
431
|
-
// Find by primary key
|
|
432
|
-
const user = db.User.findById(123);
|
|
431
|
+
// Find by primary key (requires await)
|
|
432
|
+
const user = await db.User.findById(123);
|
|
433
433
|
|
|
434
|
-
// Find single with where clause
|
|
435
|
-
const alice = db.User
|
|
434
|
+
// Find single with where clause (requires await)
|
|
435
|
+
const alice = await db.User
|
|
436
436
|
.where(u => u.email == $$, 'alice@example.com')
|
|
437
437
|
.single();
|
|
438
438
|
|
|
439
|
-
// Find multiple with conditions
|
|
440
|
-
const adults = db.User
|
|
439
|
+
// Find multiple with conditions (requires await)
|
|
440
|
+
const adults = await db.User
|
|
441
441
|
.where(u => u.age >= $$, 18)
|
|
442
442
|
.toList();
|
|
443
443
|
```
|
|
@@ -447,16 +447,16 @@ const adults = db.User
|
|
|
447
447
|
**Always use `$$` placeholders** for SQL injection protection:
|
|
448
448
|
|
|
449
449
|
```javascript
|
|
450
|
-
// Single parameter
|
|
451
|
-
const user = db.User.where(u => u.id == $$, 123).single();
|
|
450
|
+
// Single parameter (requires await)
|
|
451
|
+
const user = await db.User.where(u => u.id == $$, 123).single();
|
|
452
452
|
|
|
453
|
-
// Multiple parameters
|
|
454
|
-
const results = db.User
|
|
453
|
+
// Multiple parameters (requires await)
|
|
454
|
+
const results = await db.User
|
|
455
455
|
.where(u => u.age > $$ && u.status == $$, 25, 'active')
|
|
456
456
|
.toList();
|
|
457
457
|
|
|
458
|
-
// Single $ for OR conditions
|
|
459
|
-
const results = db.User
|
|
458
|
+
// Single $ for OR conditions (requires await)
|
|
459
|
+
const results = await db.User
|
|
460
460
|
.where(u => u.status == $ || u.status == null, 'active')
|
|
461
461
|
.toList();
|
|
462
462
|
```
|
|
@@ -464,22 +464,22 @@ const results = db.User
|
|
|
464
464
|
### IN Clauses
|
|
465
465
|
|
|
466
466
|
```javascript
|
|
467
|
-
// Array parameter with .includes()
|
|
467
|
+
// Array parameter with .includes() (requires await)
|
|
468
468
|
const ids = [1, 2, 3, 4, 5];
|
|
469
|
-
const users = db.User
|
|
469
|
+
const users = await db.User
|
|
470
470
|
.where(u => $$.includes(u.id), ids)
|
|
471
471
|
.toList();
|
|
472
472
|
|
|
473
473
|
// Generated SQL: WHERE id IN ($1, $2, $3, $4, $5)
|
|
474
474
|
// PostgreSQL parameters: [1, 2, 3, 4, 5]
|
|
475
475
|
|
|
476
|
-
// Alternative .any() syntax
|
|
477
|
-
const users = db.User
|
|
476
|
+
// Alternative .any() syntax (requires await)
|
|
477
|
+
const users = await db.User
|
|
478
478
|
.where(u => u.id.any($$), [1, 2, 3])
|
|
479
479
|
.toList();
|
|
480
480
|
|
|
481
|
-
// Comma-separated strings (auto-splits)
|
|
482
|
-
const users = db.User
|
|
481
|
+
// Comma-separated strings (auto-splits) (requires await)
|
|
482
|
+
const users = await db.User
|
|
483
483
|
.where(u => u.id.any($$), "1,2,3,4,5")
|
|
484
484
|
.toList();
|
|
485
485
|
```
|
|
@@ -498,8 +498,8 @@ if (minAge) {
|
|
|
498
498
|
query = query.where(u => u.age >= $$, minAge);
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
-
// Add sorting and pagination
|
|
502
|
-
const users = query
|
|
501
|
+
// Add sorting and pagination (requires await)
|
|
502
|
+
const users = await query
|
|
503
503
|
.orderBy(u => u.created_at)
|
|
504
504
|
.skip(offset)
|
|
505
505
|
.take(limit)
|
|
@@ -509,13 +509,13 @@ const users = query
|
|
|
509
509
|
### Ordering
|
|
510
510
|
|
|
511
511
|
```javascript
|
|
512
|
-
// Ascending
|
|
513
|
-
const users = db.User
|
|
512
|
+
// Ascending (requires await)
|
|
513
|
+
const users = await db.User
|
|
514
514
|
.orderBy(u => u.name)
|
|
515
515
|
.toList();
|
|
516
516
|
|
|
517
|
-
// Descending
|
|
518
|
-
const users = db.User
|
|
517
|
+
// Descending (requires await)
|
|
518
|
+
const users = await db.User
|
|
519
519
|
.orderByDescending(u => u.created_at)
|
|
520
520
|
.toList();
|
|
521
521
|
```
|
|
@@ -523,17 +523,17 @@ const users = db.User
|
|
|
523
523
|
### Pagination
|
|
524
524
|
|
|
525
525
|
```javascript
|
|
526
|
-
// Skip 20, take 10
|
|
527
|
-
const users = db.User
|
|
526
|
+
// Skip 20, take 10 (requires await)
|
|
527
|
+
const users = await db.User
|
|
528
528
|
.orderBy(u => u.id)
|
|
529
529
|
.skip(20)
|
|
530
530
|
.take(10)
|
|
531
531
|
.toList();
|
|
532
532
|
|
|
533
|
-
// Page-based pagination
|
|
533
|
+
// Page-based pagination (requires await)
|
|
534
534
|
const page = 2;
|
|
535
535
|
const pageSize = 10;
|
|
536
|
-
const users = db.User
|
|
536
|
+
const users = await db.User
|
|
537
537
|
.skip(page * pageSize)
|
|
538
538
|
.take(pageSize)
|
|
539
539
|
.toList();
|
|
@@ -542,11 +542,11 @@ const users = db.User
|
|
|
542
542
|
### Counting
|
|
543
543
|
|
|
544
544
|
```javascript
|
|
545
|
-
// Count all
|
|
546
|
-
const total = db.User.count();
|
|
545
|
+
// Count all (requires await)
|
|
546
|
+
const total = await db.User.count();
|
|
547
547
|
|
|
548
|
-
// Count with conditions
|
|
549
|
-
const activeCount = db.User
|
|
548
|
+
// Count with conditions (requires await)
|
|
549
|
+
const activeCount = await db.User
|
|
550
550
|
.where(u => u.status == $$, 'active')
|
|
551
551
|
.count();
|
|
552
552
|
```
|
|
@@ -554,19 +554,19 @@ const activeCount = db.User
|
|
|
554
554
|
### Complex Queries
|
|
555
555
|
|
|
556
556
|
```javascript
|
|
557
|
-
// Multiple conditions with OR
|
|
558
|
-
const results = db.User
|
|
557
|
+
// Multiple conditions with OR (requires await)
|
|
558
|
+
const results = await db.User
|
|
559
559
|
.where(u => (u.status == 'active' || u.status == 'pending') && u.age >= $$, 18)
|
|
560
560
|
.orderBy(u => u.name)
|
|
561
561
|
.toList();
|
|
562
562
|
|
|
563
|
-
// Nullable checks
|
|
564
|
-
const usersWithoutEmail = db.User
|
|
563
|
+
// Nullable checks (requires await)
|
|
564
|
+
const usersWithoutEmail = await db.User
|
|
565
565
|
.where(u => u.email == null)
|
|
566
566
|
.toList();
|
|
567
567
|
|
|
568
|
-
// LIKE queries
|
|
569
|
-
const matching = db.User
|
|
568
|
+
// LIKE queries (requires await)
|
|
569
|
+
const matching = await db.User
|
|
570
570
|
.where(u => u.name.like($$), '%john%')
|
|
571
571
|
.toList();
|
|
572
572
|
```
|
|
@@ -838,13 +838,13 @@ const user = db.User.findById(1); // DB query
|
|
|
838
838
|
const user2 = db.User.findById(1); // DB query again (no cache)
|
|
839
839
|
|
|
840
840
|
// OPT-IN: Enable caching with .cache()
|
|
841
|
-
const categories = db.Categories.cache().toList(); // DB query, cached
|
|
842
|
-
const categories2 = db.Categories.cache().toList(); // Cache hit! (instant)
|
|
841
|
+
const categories = await db.Categories.cache().toList(); // DB query, cached
|
|
842
|
+
const categories2 = await db.Categories.cache().toList(); // Cache hit! (instant)
|
|
843
843
|
|
|
844
844
|
// Update invalidates cache automatically
|
|
845
|
-
const cat = db.Categories.findById(1);
|
|
845
|
+
const cat = await db.Categories.findById(1);
|
|
846
846
|
cat.name = "Updated";
|
|
847
|
-
db.saveChanges(); // Cache for Categories table cleared
|
|
847
|
+
await db.saveChanges(); // Cache for Categories table cleared
|
|
848
848
|
|
|
849
849
|
// End request (clears cache - like Active Record)
|
|
850
850
|
db.endRequest(); // Cache cleared for next request
|
|
@@ -865,9 +865,9 @@ app.use((req, res, next) => {
|
|
|
865
865
|
});
|
|
866
866
|
|
|
867
867
|
// In your routes
|
|
868
|
-
app.get('/categories', (req, res) => {
|
|
868
|
+
app.get('/categories', async (req, res) => {
|
|
869
869
|
// Cache is fresh for this request
|
|
870
|
-
const categories = req.db.Categories.cache().toList();
|
|
870
|
+
const categories = await req.db.Categories.cache().toList();
|
|
871
871
|
res.json(categories);
|
|
872
872
|
// Cache auto-cleared after response
|
|
873
873
|
});
|
|
@@ -900,14 +900,14 @@ Use `.cache()` for frequently accessed, rarely changed data:
|
|
|
900
900
|
|
|
901
901
|
```javascript
|
|
902
902
|
// DEFAULT: Always hits database (safe)
|
|
903
|
-
const liveData = db.Analytics
|
|
903
|
+
const liveData = await db.Analytics
|
|
904
904
|
.where(a => a.date == $$, today)
|
|
905
905
|
.toList(); // No caching (default)
|
|
906
906
|
|
|
907
907
|
// OPT-IN: Cache reference data
|
|
908
|
-
const categories = db.Categories.cache().toList(); // Cached for 5 seconds (default TTL)
|
|
909
|
-
const settings = db.Settings.cache().toList(); // Cached
|
|
910
|
-
const countries = db.Countries.cache().toList(); // Cached
|
|
908
|
+
const categories = await db.Categories.cache().toList(); // Cached for 5 seconds (default TTL)
|
|
909
|
+
const settings = await db.Settings.cache().toList(); // Cached
|
|
910
|
+
const countries = await db.Countries.cache().toList(); // Cached
|
|
911
911
|
|
|
912
912
|
// When to use .cache():
|
|
913
913
|
// ✅ Reference data (categories, settings, countries)
|
|
@@ -940,7 +940,7 @@ db.clearQueryCache();
|
|
|
940
940
|
|
|
941
941
|
// Disable caching temporarily
|
|
942
942
|
db.setQueryCacheEnabled(false);
|
|
943
|
-
const freshData = db.User.toList();
|
|
943
|
+
const freshData = await db.User.toList();
|
|
944
944
|
db.setQueryCacheEnabled(true);
|
|
945
945
|
```
|
|
946
946
|
|
|
@@ -983,21 +983,21 @@ MasterRecord automatically invalidates cache entries when data changes:
|
|
|
983
983
|
|
|
984
984
|
```javascript
|
|
985
985
|
// Query with caching enabled
|
|
986
|
-
const categories = db.Categories.cache().toList(); // DB query, cached
|
|
986
|
+
const categories = await db.Categories.cache().toList(); // DB query, cached
|
|
987
987
|
|
|
988
988
|
// Any modification to Categories table invalidates ALL cached Category queries
|
|
989
|
-
const cat = db.Categories.findById(1);
|
|
989
|
+
const cat = await db.Categories.findById(1);
|
|
990
990
|
cat.name = "Updated";
|
|
991
|
-
db.saveChanges(); // Invalidates all cached Categories queries
|
|
991
|
+
await db.saveChanges(); // Invalidates all cached Categories queries
|
|
992
992
|
|
|
993
993
|
// Next cached query hits database (fresh data)
|
|
994
|
-
const categoriesAgain = db.Categories.cache().toList(); // DB query (cache cleared)
|
|
994
|
+
const categoriesAgain = await db.Categories.cache().toList(); // DB query (cache cleared)
|
|
995
995
|
|
|
996
996
|
// Non-cached queries are unaffected (always fresh)
|
|
997
|
-
const users = db.User.toList(); // No .cache() = always DB query
|
|
997
|
+
const users = await db.User.toList(); // No .cache() = always DB query
|
|
998
998
|
|
|
999
999
|
// Queries for OTHER tables' caches are unaffected
|
|
1000
|
-
const settings = db.Settings.cache().toList(); // Still cached (different table)
|
|
1000
|
+
const settings = await db.Settings.cache().toList(); // Still cached (different table)
|
|
1001
1001
|
```
|
|
1002
1002
|
|
|
1003
1003
|
**Invalidation rules:**
|
|
@@ -1024,12 +1024,12 @@ Expected performance improvements:
|
|
|
1024
1024
|
**DO use .cache():**
|
|
1025
1025
|
```javascript
|
|
1026
1026
|
// Reference data (rarely changes)
|
|
1027
|
-
const categories = db.Categories.cache().toList();
|
|
1028
|
-
const settings = db.Settings.cache().toList();
|
|
1029
|
-
const countries = db.Countries.cache().toList();
|
|
1027
|
+
const categories = await db.Categories.cache().toList();
|
|
1028
|
+
const settings = await db.Settings.cache().toList();
|
|
1029
|
+
const countries = await db.Countries.cache().toList();
|
|
1030
1030
|
|
|
1031
1031
|
// Expensive aggregations (stable results)
|
|
1032
|
-
const totalRevenue = db.Orders
|
|
1032
|
+
const totalRevenue = await db.Orders
|
|
1033
1033
|
.where(o => o.year == $$, 2024)
|
|
1034
1034
|
.cache()
|
|
1035
1035
|
.count();
|
|
@@ -1038,20 +1038,20 @@ const totalRevenue = db.Orders
|
|
|
1038
1038
|
**DON'T use .cache():**
|
|
1039
1039
|
```javascript
|
|
1040
1040
|
// User-specific data (default is safe - no caching)
|
|
1041
|
-
const user = db.User.findById(userId); // Always fresh
|
|
1041
|
+
const user = await db.User.findById(userId); // Always fresh
|
|
1042
1042
|
|
|
1043
1043
|
// Real-time data (default is safe)
|
|
1044
|
-
const liveOrders = db.Orders
|
|
1044
|
+
const liveOrders = await db.Orders
|
|
1045
1045
|
.where(o => o.status == $$, 'pending')
|
|
1046
1046
|
.toList(); // Always fresh
|
|
1047
1047
|
|
|
1048
1048
|
// Financial transactions (default is safe)
|
|
1049
|
-
const balance = db.Transactions
|
|
1049
|
+
const balance = await db.Transactions
|
|
1050
1050
|
.where(t => t.user_id == $$, userId)
|
|
1051
1051
|
.toList(); // Always fresh
|
|
1052
1052
|
|
|
1053
1053
|
// User-specific sensitive data (default is safe)
|
|
1054
|
-
const permissions = db.UserPermissions
|
|
1054
|
+
const permissions = await db.UserPermissions
|
|
1055
1055
|
.where(p => p.user_id == $$, userId)
|
|
1056
1056
|
.toList(); // Always fresh
|
|
1057
1057
|
```
|
|
@@ -1089,12 +1089,12 @@ app.use((req, res, next) => {
|
|
|
1089
1089
|
});
|
|
1090
1090
|
|
|
1091
1091
|
// In routes - cache is fresh per request
|
|
1092
|
-
app.get('/api/categories', (req, res) => {
|
|
1092
|
+
app.get('/api/categories', async (req, res) => {
|
|
1093
1093
|
// First call in this request - DB query
|
|
1094
|
-
const categories = req.db.Categories.cache().toList();
|
|
1094
|
+
const categories = await req.db.Categories.cache().toList();
|
|
1095
1095
|
|
|
1096
1096
|
// Second call in same request - cache hit
|
|
1097
|
-
const categoriesAgain = req.db.Categories.cache().toList();
|
|
1097
|
+
const categoriesAgain = await req.db.Categories.cache().toList();
|
|
1098
1098
|
|
|
1099
1099
|
res.json(categories);
|
|
1100
1100
|
// After response, cache is automatically cleared
|
|
@@ -1118,18 +1118,18 @@ const db1 = new AppContext();
|
|
|
1118
1118
|
const db2 = new AppContext();
|
|
1119
1119
|
|
|
1120
1120
|
// Context 1: Cache data with .cache()
|
|
1121
|
-
const categories1 = db1.Categories.cache().toList(); // DB query, cached
|
|
1121
|
+
const categories1 = await db1.Categories.cache().toList(); // DB query, cached
|
|
1122
1122
|
|
|
1123
1123
|
// Context 2: Sees cached data
|
|
1124
|
-
const categories2 = db2.Categories.cache().toList(); // Cache hit!
|
|
1124
|
+
const categories2 = await db2.Categories.cache().toList(); // Cache hit!
|
|
1125
1125
|
|
|
1126
1126
|
// Context 2: Updates invalidate cache for BOTH contexts
|
|
1127
|
-
const cat = db2.Categories.findById(1);
|
|
1127
|
+
const cat = await db2.Categories.findById(1);
|
|
1128
1128
|
cat.name = "Updated";
|
|
1129
|
-
db2.saveChanges(); // Invalidates shared cache
|
|
1129
|
+
await db2.saveChanges(); // Invalidates shared cache
|
|
1130
1130
|
|
|
1131
1131
|
// Context 1: Sees fresh data
|
|
1132
|
-
const categories3 = db1.Categories.cache().toList(); // Cache miss, fresh data
|
|
1132
|
+
const categories3 = await db1.Categories.cache().toList(); // Cache miss, fresh data
|
|
1133
1133
|
console.log(categories3[0].name); // "Updated"
|
|
1134
1134
|
```
|
|
1135
1135
|
|
|
@@ -1168,8 +1168,9 @@ class AnalyticsContext extends context {
|
|
|
1168
1168
|
const userDb = new UserContext();
|
|
1169
1169
|
const analyticsDb = new AnalyticsContext();
|
|
1170
1170
|
|
|
1171
|
-
const user = userDb.User.findById(123);
|
|
1172
|
-
analyticsDb.Event.new()
|
|
1171
|
+
const user = await userDb.User.findById(123);
|
|
1172
|
+
const event = analyticsDb.Event.new();
|
|
1173
|
+
event.log('user_login', user.id);
|
|
1173
1174
|
await analyticsDb.saveChanges();
|
|
1174
1175
|
```
|
|
1175
1176
|
|
|
@@ -1232,7 +1233,7 @@ context.setQueryCacheEnabled(bool) // Enable/disable caching
|
|
|
1232
1233
|
### Query Methods
|
|
1233
1234
|
|
|
1234
1235
|
```javascript
|
|
1235
|
-
// Chainable query builders
|
|
1236
|
+
// Chainable query builders (do not execute query)
|
|
1236
1237
|
.where(query, ...params) // Add WHERE condition
|
|
1237
1238
|
.and(query, ...params) // Add AND condition
|
|
1238
1239
|
.orderBy(field) // Sort ascending
|
|
@@ -1242,18 +1243,18 @@ context.setQueryCacheEnabled(bool) // Enable/disable caching
|
|
|
1242
1243
|
.include(relationship) // Eager load
|
|
1243
1244
|
.cache() // Enable caching for this query (opt-in)
|
|
1244
1245
|
|
|
1245
|
-
// Terminal methods (execute query)
|
|
1246
|
-
.toList()
|
|
1247
|
-
.single()
|
|
1248
|
-
.first()
|
|
1249
|
-
.count()
|
|
1250
|
-
.any()
|
|
1246
|
+
// Terminal methods (execute query - ALL REQUIRE AWAIT)
|
|
1247
|
+
await .toList() // Return array of all records
|
|
1248
|
+
await .single() // Return one or null
|
|
1249
|
+
await .first() // Return first or null
|
|
1250
|
+
await .count() // Return count
|
|
1251
|
+
await .any() // Return boolean
|
|
1251
1252
|
|
|
1252
|
-
// Convenience methods
|
|
1253
|
-
.findById(id)
|
|
1254
|
-
.new() // Create new entity instance
|
|
1253
|
+
// Convenience methods (REQUIRE AWAIT)
|
|
1254
|
+
await .findById(id) // Find by primary key
|
|
1255
|
+
.new() // Create new entity instance (synchronous)
|
|
1255
1256
|
|
|
1256
|
-
// Entity methods (Active Record style)
|
|
1257
|
+
// Entity methods (Active Record style - REQUIRE AWAIT)
|
|
1257
1258
|
await entity.save() // Save this entity (and all tracked changes)
|
|
1258
1259
|
```
|
|
1259
1260
|
|
|
@@ -1315,14 +1316,14 @@ demo();
|
|
|
1315
1316
|
async function getUsers(page = 0, pageSize = 10) {
|
|
1316
1317
|
const db = new AppContext();
|
|
1317
1318
|
|
|
1318
|
-
const users = db.User
|
|
1319
|
+
const users = await db.User
|
|
1319
1320
|
.where(u => u.status == $$, 'active')
|
|
1320
1321
|
.orderBy(u => u.created_at)
|
|
1321
1322
|
.skip(page * pageSize)
|
|
1322
1323
|
.take(pageSize)
|
|
1323
1324
|
.toList();
|
|
1324
1325
|
|
|
1325
|
-
const total = db.User
|
|
1326
|
+
const total = await db.User
|
|
1326
1327
|
.where(u => u.status == $$, 'active')
|
|
1327
1328
|
.count();
|
|
1328
1329
|
|
|
@@ -1373,7 +1374,7 @@ async function searchUsers(filters) {
|
|
|
1373
1374
|
.take(filters.pageSize);
|
|
1374
1375
|
}
|
|
1375
1376
|
|
|
1376
|
-
return query.toList();
|
|
1377
|
+
return await query.toList();
|
|
1377
1378
|
}
|
|
1378
1379
|
```
|
|
1379
1380
|
|
|
@@ -1403,7 +1404,7 @@ post.author_id = author.id;
|
|
|
1403
1404
|
await db.saveChanges();
|
|
1404
1405
|
|
|
1405
1406
|
// Query with relationships
|
|
1406
|
-
const posts = db.Post
|
|
1407
|
+
const posts = await db.Post
|
|
1407
1408
|
.where(p => p.author_id == $$, author.id)
|
|
1408
1409
|
.toList();
|
|
1409
1410
|
|
|
@@ -1416,15 +1417,15 @@ console.log(`${author.name} has ${posts.length} posts`);
|
|
|
1416
1417
|
|
|
1417
1418
|
```javascript
|
|
1418
1419
|
// ✅ GOOD: Cache reference data that rarely changes
|
|
1419
|
-
const categories = db.Categories.cache().toList(); // Opt-in caching
|
|
1420
|
-
const settings = db.Settings.cache().toList();
|
|
1420
|
+
const categories = await db.Categories.cache().toList(); // Opt-in caching
|
|
1421
|
+
const settings = await db.Settings.cache().toList();
|
|
1421
1422
|
|
|
1422
1423
|
// ✅ GOOD: Queries without .cache() are always fresh (safe default)
|
|
1423
|
-
const user1 = db.User.findById(123); // Always DB query (no cache)
|
|
1424
|
-
const user2 = db.User.findById(123); // Always DB query (no cache)
|
|
1424
|
+
const user1 = await db.User.findById(123); // Always DB query (no cache)
|
|
1425
|
+
const user2 = await db.User.findById(123); // Always DB query (no cache)
|
|
1425
1426
|
|
|
1426
1427
|
// ✅ GOOD: Cache expensive queries with stable results
|
|
1427
|
-
const revenue2024 = db.Orders
|
|
1428
|
+
const revenue2024 = await db.Orders
|
|
1428
1429
|
.where(o => o.year == $$, 2024)
|
|
1429
1430
|
.cache() // Historical data doesn't change
|
|
1430
1431
|
.count();
|
|
@@ -1472,13 +1473,13 @@ class User {
|
|
|
1472
1473
|
|
|
1473
1474
|
```javascript
|
|
1474
1475
|
// ✅ GOOD: Limit results
|
|
1475
|
-
const recentUsers = db.User
|
|
1476
|
+
const recentUsers = await db.User
|
|
1476
1477
|
.orderByDescending(u => u.created_at)
|
|
1477
1478
|
.take(100)
|
|
1478
1479
|
.toList();
|
|
1479
1480
|
|
|
1480
1481
|
// ❌ BAD: Load everything
|
|
1481
|
-
const allUsers = db.User.toList();
|
|
1482
|
+
const allUsers = await db.User.toList();
|
|
1482
1483
|
```
|
|
1483
1484
|
|
|
1484
1485
|
### 5. Use Connection Pooling (PostgreSQL)
|
|
@@ -1500,7 +1501,7 @@ MasterRecord uses **parameterized queries throughout** to prevent SQL injection:
|
|
|
1500
1501
|
|
|
1501
1502
|
```javascript
|
|
1502
1503
|
// ✅ SAFE: Parameterized
|
|
1503
|
-
const user = db.User.where(u => u.name == $$, userInput).single();
|
|
1504
|
+
const user = await db.User.where(u => u.name == $$, userInput).single();
|
|
1504
1505
|
|
|
1505
1506
|
// ❌ UNSAFE: Never do this
|
|
1506
1507
|
// const query = `SELECT * FROM User WHERE name = '${userInput}'`;
|
|
@@ -1520,12 +1521,12 @@ While SQL injection is prevented, always validate business logic:
|
|
|
1520
1521
|
|
|
1521
1522
|
```javascript
|
|
1522
1523
|
// Validate input before querying
|
|
1523
|
-
function getUser(userId) {
|
|
1524
|
+
async function getUser(userId) {
|
|
1524
1525
|
if (!Number.isInteger(userId) || userId <= 0) {
|
|
1525
1526
|
throw new Error('Invalid user ID');
|
|
1526
1527
|
}
|
|
1527
1528
|
|
|
1528
|
-
return db.User.findById(userId);
|
|
1529
|
+
return await db.User.findById(userId);
|
|
1529
1530
|
}
|
|
1530
1531
|
```
|
|
1531
1532
|
|
|
File without changes
|
|
File without changes
|