@dreamtree-org/korm-js 1.0.52 → 1.0.54
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/BaseHelperUtility.js +1 -1
- package/ControllerWrapper.js +1 -1
- package/Emitter.js +1 -1
- package/README.md +307 -228
- package/build.js +1 -1
- package/cli.js +2 -0
- package/clients/mysql/BaseUtility.js +1 -1
- package/clients/mysql/CurdTable.js +1 -1
- package/clients/mysql/DataTypeMap.js +1 -1
- package/clients/mysql/HookService.js +1 -1
- package/clients/mysql/QueryBuilder.js +1 -1
- package/clients/mysql/QueryService.js +1 -1
- package/clients/mysql/SyncTable.js +1 -1
- package/clients/pg/BaseUtility.js +1 -1
- package/clients/pg/CurdTable.js +1 -1
- package/clients/pg/HookService.js +1 -1
- package/clients/pg/QueryBuilder.js +1 -1
- package/clients/pg/QueryService.js +1 -1
- package/clients/pg/SyncTable.js +1 -1
- package/clients/sqlite/BaseUtility.js +1 -1
- package/clients/sqlite/CurdTable.js +1 -1
- package/clients/sqlite/HookService.js +1 -1
- package/clients/sqlite/QueryBuilder.js +1 -1
- package/clients/sqlite/QueryService.js +1 -1
- package/clients/sqlite/SyncTable.js +1 -1
- package/helpers/files.js +1 -1
- package/jest.config.js +1 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -29,6 +29,35 @@
|
|
|
29
29
|
npm install @dreamtree-org/korm-js
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## AI assistant skills (`init --ai`)
|
|
33
|
+
|
|
34
|
+
KORM-JS ships a canonical reference doc designed for AI coding assistants. After installing the package (or via `npx` without installing), drop the skill into the location your assistant picks up:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx @dreamtree-org/korm-js init --ai claude
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Supported providers:
|
|
41
|
+
|
|
42
|
+
| `--ai` value | Target file |
|
|
43
|
+
| ----------------------------------- | ----------------------------------------------------------- |
|
|
44
|
+
| `claude` | `CLAUDE.md` (idempotent block insert) |
|
|
45
|
+
| `openai` (alias: `codex`) | `AGENTS.md` (idempotent block insert) |
|
|
46
|
+
| `gemini` | `GEMINI.md` (idempotent block insert) |
|
|
47
|
+
| `copilot` (alias: `github-copilot`) | `.github/copilot-instructions.md` (idempotent block insert) |
|
|
48
|
+
| `kiro` | `.kiro/steering/korm-js.md` |
|
|
49
|
+
| `windsurf` | `.windsurf/rules/korm-js.md` |
|
|
50
|
+
| `cursor` | `.cursor/rules/korm-js.mdc` (with MDC frontmatter) |
|
|
51
|
+
| `all` | Installs every provider above |
|
|
52
|
+
|
|
53
|
+
Flags:
|
|
54
|
+
|
|
55
|
+
- `--cwd <dir>` — install into a directory other than the current working directory.
|
|
56
|
+
- `--force` — overwrite dedicated-file targets (Cursor / Windsurf / Kiro) that already exist.
|
|
57
|
+
- `--dry-run` — print what would change without writing.
|
|
58
|
+
|
|
59
|
+
Block-insert targets (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `.github/copilot-instructions.md`) wrap the skill in `<!-- BEGIN korm-js skill --> ... <!-- END korm-js skill -->` markers. Re-running the command after a library upgrade updates the block in place without touching the rest of the file.
|
|
60
|
+
|
|
32
61
|
## Quick Start with Express.js and MySQL
|
|
33
62
|
|
|
34
63
|
### 1. Basic Setup
|
|
@@ -50,15 +79,15 @@ const db = knex({
|
|
|
50
79
|
user: process.env.DB_USER || 'root',
|
|
51
80
|
password: process.env.DB_PASS || 'password',
|
|
52
81
|
database: process.env.DB_NAME || 'my_database',
|
|
53
|
-
port: process.env.DB_PORT || 3306
|
|
54
|
-
}
|
|
82
|
+
port: process.env.DB_PORT || 3306,
|
|
83
|
+
},
|
|
55
84
|
});
|
|
56
85
|
|
|
57
86
|
// Initialize KORM with MySQL
|
|
58
87
|
const korm = initializeKORM({
|
|
59
88
|
db: db,
|
|
60
89
|
dbClient: 'mysql',
|
|
61
|
-
debug: false
|
|
90
|
+
debug: false, // Set to true for SQL debugging
|
|
62
91
|
});
|
|
63
92
|
|
|
64
93
|
app.listen(3000, () => {
|
|
@@ -75,14 +104,14 @@ async function initApp() {
|
|
|
75
104
|
if (syncSchema) {
|
|
76
105
|
korm.setSchema(syncSchema);
|
|
77
106
|
}
|
|
78
|
-
|
|
107
|
+
|
|
79
108
|
// Sync database with schema (creates tables if they don't exist)
|
|
80
109
|
await korm.syncDatabase().then(() => {
|
|
81
110
|
console.log('✅ Database synced');
|
|
82
111
|
});
|
|
83
|
-
|
|
112
|
+
|
|
84
113
|
// Generate schema from existing database
|
|
85
|
-
await korm.generateSchema().then(schema => {
|
|
114
|
+
await korm.generateSchema().then((schema) => {
|
|
86
115
|
korm.setSchema(schema); // Set the schema to the korm instance
|
|
87
116
|
helperUtility.file.createDirectory('schema');
|
|
88
117
|
helperUtility.file.writeJSON('schema/schema.json', schema);
|
|
@@ -332,7 +361,49 @@ POST /api/Users/crud
|
|
|
332
361
|
42 // Number of matching records
|
|
333
362
|
```
|
|
334
363
|
|
|
335
|
-
### 7.
|
|
364
|
+
### 7. Sum Operation
|
|
365
|
+
|
|
366
|
+
Use either `data.sumColumn` (single column) or `data.sumFormula` (expression with `{columnName}` placeholders). Optional `where` filters the rows before summing.
|
|
367
|
+
|
|
368
|
+
```javascript
|
|
369
|
+
// Sum a single column
|
|
370
|
+
POST /api/Employee/crud
|
|
371
|
+
{
|
|
372
|
+
"action": "sum",
|
|
373
|
+
"data": {
|
|
374
|
+
"sumColumn": "salary"
|
|
375
|
+
},
|
|
376
|
+
"where": {
|
|
377
|
+
"is_active": true,
|
|
378
|
+
"created_at": ">=2024-01-01"
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Sum an expression (e.g. salary + bonus)
|
|
383
|
+
POST /api/Employee/crud
|
|
384
|
+
{
|
|
385
|
+
"action": "sum",
|
|
386
|
+
"data": {
|
|
387
|
+
"sumFormula": "{salary}+{bonus}"
|
|
388
|
+
},
|
|
389
|
+
"where": {
|
|
390
|
+
"is_active": true
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Response
|
|
395
|
+
125000 // Sum value (number)
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**Notes:**
|
|
399
|
+
|
|
400
|
+
- Use **either** `sumColumn` or `sumFormula`, not both. If both are provided, `sumColumn` is used.
|
|
401
|
+
- In `sumFormula`, column names are written as `{columnName}` and are safely quoted; only letters, numbers, and underscore are allowed in names.
|
|
402
|
+
- **Expression guards:** `sumFormula` allows only BODMAS-safe content: numbers (including decimals), operators `+` `-` `*` `/`, parentheses `(` `)`, and `{columnName}` placeholders. Any other character (e.g. commas, quotes, SQL) is rejected. Parentheses must be balanced and correctly ordered.
|
|
403
|
+
- Examples: `{salary}+{bonus}`, `({salary}+{bonus})*0.9`, `{amount}*1.1`, `{a}/{b}*100`.
|
|
404
|
+
- `where` supports the same operators as list/count (e.g. `>=`, `><`, `%...%`, etc.).
|
|
405
|
+
|
|
406
|
+
### 8. Replace Operation
|
|
336
407
|
|
|
337
408
|
```javascript
|
|
338
409
|
// Replace record (MySQL specific - replaces entire row)
|
|
@@ -356,7 +427,7 @@ POST /api/Users/crud
|
|
|
356
427
|
}
|
|
357
428
|
```
|
|
358
429
|
|
|
359
|
-
###
|
|
430
|
+
### 9. Upsert Operation (Insert or Update)
|
|
360
431
|
|
|
361
432
|
```javascript
|
|
362
433
|
// Upsert record (insert if not exists, update if exists)
|
|
@@ -380,7 +451,7 @@ POST /api/Users/crud
|
|
|
380
451
|
}
|
|
381
452
|
```
|
|
382
453
|
|
|
383
|
-
###
|
|
454
|
+
### 10. Sync Operation (Upsert + Delete)
|
|
384
455
|
|
|
385
456
|
```javascript
|
|
386
457
|
// Sync operation: upsert records and delete others matching where clause
|
|
@@ -558,21 +629,21 @@ POST /api/Users/crud
|
|
|
558
629
|
|
|
559
630
|
### Where Operators Reference
|
|
560
631
|
|
|
561
|
-
| Operator
|
|
562
|
-
|
|
563
|
-
| `=` (default) | Equals
|
|
564
|
-
| `>=`
|
|
565
|
-
| `<=`
|
|
566
|
-
| `>`
|
|
567
|
-
| `<`
|
|
568
|
-
| `!=`
|
|
569
|
-
| `like`
|
|
570
|
-
| `in`
|
|
571
|
-
| `notIn`
|
|
572
|
-
| `between`
|
|
573
|
-
| `notBetween`
|
|
574
|
-
| `null`
|
|
575
|
-
| `Or:column`
|
|
632
|
+
| Operator | Description | Syntax | Example |
|
|
633
|
+
| ------------- | ----------------------- | -------------- | ------------------------------- |
|
|
634
|
+
| `=` (default) | Equals | `value` | `"status": "active"` |
|
|
635
|
+
| `>=` | Greater than or equal | `>=value` | `"age": ">=18"` |
|
|
636
|
+
| `<=` | Less than or equal | `<=value` | `"age": "<=65"` |
|
|
637
|
+
| `>` | Greater than | `>value` | `"price": ">100"` |
|
|
638
|
+
| `<` | Less than | `<value` | `"price": "<500"` |
|
|
639
|
+
| `!=` | Not equal | `!value` | `"status": "!deleted"` |
|
|
640
|
+
| `like` | Pattern matching (auto) | `%value%` | `"name": "%john%"` |
|
|
641
|
+
| `in` | Value in list | `[]val1,val2` | `"status": "[]active,pending"` |
|
|
642
|
+
| `notIn` | Value not in list | `![]val1,val2` | `"role": "![]banned,suspended"` |
|
|
643
|
+
| `between` | Range (inclusive) | `><min,max` | `"age": "><18,65"` |
|
|
644
|
+
| `notBetween` | Outside range | `<>min,max` | `"score": "<>0,50"` |
|
|
645
|
+
| `null` | IS NULL check | `null` | `"deleted_at": null` |
|
|
646
|
+
| `Or:column` | OR condition prefix | `Or:column` | `"Or:name": "John"` |
|
|
576
647
|
|
|
577
648
|
**Examples:**
|
|
578
649
|
|
|
@@ -589,7 +660,7 @@ POST /api/Users/crud
|
|
|
589
660
|
"score": "><50,100"
|
|
590
661
|
}
|
|
591
662
|
}
|
|
592
|
-
// SQL: WHERE age >= 18 AND status != 'deleted' AND name LIKE '%john%'
|
|
663
|
+
// SQL: WHERE age >= 18 AND status != 'deleted' AND name LIKE '%john%'
|
|
593
664
|
// AND role IN ('admin', 'moderator', 'editor') AND score BETWEEN 50 AND 100
|
|
594
665
|
```
|
|
595
666
|
|
|
@@ -680,6 +751,7 @@ POST /api/Users/crud
|
|
|
680
751
|
```
|
|
681
752
|
|
|
682
753
|
**Available join types:**
|
|
754
|
+
|
|
683
755
|
- `join` - Regular join
|
|
684
756
|
- `innerJoin` - Inner join
|
|
685
757
|
- `leftJoin` - Left join
|
|
@@ -754,6 +826,7 @@ For many-to-many relationships, use the `through` key to specify the join/pivot
|
|
|
754
826
|
```
|
|
755
827
|
|
|
756
828
|
**Many-to-Many Structure:**
|
|
829
|
+
|
|
757
830
|
- `type`: `"many"` (always use "many" for many-to-many)
|
|
758
831
|
- `table`: The related table name (e.g., `"roles"`)
|
|
759
832
|
- `localKey`: The primary key in the current model (e.g., `"id"`)
|
|
@@ -824,7 +897,7 @@ POST /api/Post/crud
|
|
|
824
897
|
},
|
|
825
898
|
"with": ["User", "User.UserRole", "User.UserRole.Role"]
|
|
826
899
|
}
|
|
827
|
-
// SQL: WHERE EXISTS (SELECT * FROM users WHERE users.id = posts.user_id
|
|
900
|
+
// SQL: WHERE EXISTS (SELECT * FROM users WHERE users.id = posts.user_id
|
|
828
901
|
// AND EXISTS (SELECT * FROM user_roles WHERE user_roles.user_id = users.id
|
|
829
902
|
// AND EXISTS (SELECT * FROM roles WHERE roles.id = user_roles.role_id AND name = 'admin')))
|
|
830
903
|
```
|
|
@@ -843,7 +916,7 @@ POST /api/Post/crud
|
|
|
843
916
|
},
|
|
844
917
|
"with": ["User"]
|
|
845
918
|
}
|
|
846
|
-
// SQL: WHERE EXISTS (SELECT * FROM users WHERE users.id = posts.user_id
|
|
919
|
+
// SQL: WHERE EXISTS (SELECT * FROM users WHERE users.id = posts.user_id
|
|
847
920
|
// AND NOT EXISTS (SELECT * FROM user_roles WHERE user_roles.user_id = users.id))
|
|
848
921
|
|
|
849
922
|
// Get users with no posts
|
|
@@ -871,12 +944,13 @@ POST /api/Post/crud
|
|
|
871
944
|
|
|
872
945
|
**NOT EXISTS Syntax Summary:**
|
|
873
946
|
|
|
874
|
-
| Pattern
|
|
875
|
-
|
|
876
|
-
| `"!RelName": true`
|
|
877
|
-
| `"Parent.!Child": true`
|
|
878
|
-
| `"Parent.!Child.column": value` | NOT EXISTS with condition
|
|
879
|
-
|
|
947
|
+
| Pattern | Meaning | Use Case |
|
|
948
|
+
| ------------------------------- | ----------------------------- | ---------------------------------- |
|
|
949
|
+
| `"!RelName": true` | NOT EXISTS on direct relation | Users with no posts |
|
|
950
|
+
| `"Parent.!Child": true` | NOT EXISTS on nested relation | Posts where user has no roles |
|
|
951
|
+
| `"Parent.!Child.column": value` | NOT EXISTS with condition | Posts where user has no admin role |
|
|
952
|
+
|
|
953
|
+
````
|
|
880
954
|
|
|
881
955
|
#### Filtering Eager Loaded Relations with `withWhere`
|
|
882
956
|
|
|
@@ -938,15 +1012,15 @@ POST /api/User/crud
|
|
|
938
1012
|
"Or:Post.is_featured": true
|
|
939
1013
|
}
|
|
940
1014
|
}
|
|
941
|
-
|
|
1015
|
+
````
|
|
942
1016
|
|
|
943
1017
|
**Key differences between `where` and `withWhere`:**
|
|
944
1018
|
|
|
945
|
-
| Feature
|
|
946
|
-
|
|
947
|
-
| Affects parent query | Yes (filters parent rows)
|
|
948
|
-
| Purpose
|
|
949
|
-
| SQL generated
|
|
1019
|
+
| Feature | `where` (Nested) | `withWhere` |
|
|
1020
|
+
| -------------------- | ------------------------------------------- | ------------------------------------ |
|
|
1021
|
+
| Affects parent query | Yes (filters parent rows) | No (only filters related rows) |
|
|
1022
|
+
| Purpose | Filter parents that have matching relations | Filter which related rows are loaded |
|
|
1023
|
+
| SQL generated | `WHERE EXISTS (subquery)` | Additional `WHERE` on relation query |
|
|
950
1024
|
|
|
951
1025
|
**Example showing the difference:**
|
|
952
1026
|
|
|
@@ -971,6 +1045,7 @@ POST /api/User/crud
|
|
|
971
1045
|
```
|
|
972
1046
|
|
|
973
1047
|
**withWhere supports all operators:**
|
|
1048
|
+
|
|
974
1049
|
- Comparison: `">=18"`, `"<=100"`, `">50"`, `"<10"`, `"!deleted"`
|
|
975
1050
|
- LIKE: `"%pattern%"`
|
|
976
1051
|
- IN: `"[]val1,val2,val3"`
|
|
@@ -1044,6 +1119,7 @@ Example complete relationship structure:
|
|
|
1044
1119
|
### Custom Relation Hooks
|
|
1045
1120
|
|
|
1046
1121
|
For complex relationships that can't be defined in the schema, you can create custom relation hooks in your model class. This is useful for:
|
|
1122
|
+
|
|
1047
1123
|
- Virtual/computed relations
|
|
1048
1124
|
- Cross-database relations
|
|
1049
1125
|
- Complex aggregations
|
|
@@ -1059,35 +1135,33 @@ class Users {
|
|
|
1059
1135
|
// rows = parent rows to attach relation data to
|
|
1060
1136
|
// db = Knex database instance
|
|
1061
1137
|
// qb = QueryBuilder instance
|
|
1062
|
-
|
|
1138
|
+
|
|
1063
1139
|
for (const row of rows) {
|
|
1064
1140
|
// Fetch custom data for each row
|
|
1065
|
-
const stats = await db('user_statistics')
|
|
1066
|
-
|
|
1067
|
-
.first();
|
|
1068
|
-
|
|
1141
|
+
const stats = await db('user_statistics').where('user_id', row.id).first();
|
|
1142
|
+
|
|
1069
1143
|
// Attach to row
|
|
1070
1144
|
row.Statistics = stats || { posts: 0, comments: 0, likes: 0 };
|
|
1071
1145
|
}
|
|
1072
1146
|
}
|
|
1073
|
-
|
|
1147
|
+
|
|
1074
1148
|
// Custom relation with aggregation
|
|
1075
1149
|
async getPostCountRelation({ rows, db }) {
|
|
1076
|
-
const userIds = rows.map(r => r.id);
|
|
1077
|
-
|
|
1150
|
+
const userIds = rows.map((r) => r.id);
|
|
1151
|
+
|
|
1078
1152
|
const counts = await db('posts')
|
|
1079
1153
|
.select('user_id')
|
|
1080
1154
|
.count('* as count')
|
|
1081
1155
|
.whereIn('user_id', userIds)
|
|
1082
1156
|
.groupBy('user_id');
|
|
1083
|
-
|
|
1084
|
-
const countMap = new Map(counts.map(c => [c.user_id, c.count]));
|
|
1085
|
-
|
|
1157
|
+
|
|
1158
|
+
const countMap = new Map(counts.map((c) => [c.user_id, c.count]));
|
|
1159
|
+
|
|
1086
1160
|
for (const row of rows) {
|
|
1087
1161
|
row.PostCount = countMap.get(row.id) || 0;
|
|
1088
1162
|
}
|
|
1089
1163
|
}
|
|
1090
|
-
|
|
1164
|
+
|
|
1091
1165
|
// Custom relation from external API or different database
|
|
1092
1166
|
async getExternalProfileRelation({ rows }) {
|
|
1093
1167
|
for (const row of rows) {
|
|
@@ -1114,16 +1188,16 @@ POST /api/Users/crud
|
|
|
1114
1188
|
|
|
1115
1189
|
**Custom Relation Hook Arguments:**
|
|
1116
1190
|
|
|
1117
|
-
| Argument
|
|
1118
|
-
|
|
1119
|
-
| `rows`
|
|
1120
|
-
| `relName`
|
|
1121
|
-
| `model`
|
|
1122
|
-
| `withTree`
|
|
1123
|
-
| `controller` | ControllerWrapper instance
|
|
1124
|
-
| `relation`
|
|
1125
|
-
| `qb`
|
|
1126
|
-
| `db`
|
|
1191
|
+
| Argument | Description |
|
|
1192
|
+
| ------------ | ----------------------------------------------------------------------- |
|
|
1193
|
+
| `rows` | Parent rows to attach relation data to (modify in place) |
|
|
1194
|
+
| `relName` | The relation name being fetched |
|
|
1195
|
+
| `model` | Model definition object |
|
|
1196
|
+
| `withTree` | Nested relations tree for further loading |
|
|
1197
|
+
| `controller` | ControllerWrapper instance |
|
|
1198
|
+
| `relation` | Relation definition from schema (may be undefined for custom relations) |
|
|
1199
|
+
| `qb` | QueryBuilder instance for building queries |
|
|
1200
|
+
| `db` | Knex database instance for direct queries |
|
|
1127
1201
|
|
|
1128
1202
|
### Important Notes
|
|
1129
1203
|
|
|
@@ -1146,13 +1220,13 @@ Create a model file at `models/Users.model.js`:
|
|
|
1146
1220
|
class Users {
|
|
1147
1221
|
// Enable soft delete for this model
|
|
1148
1222
|
hasSoftDelete = true;
|
|
1149
|
-
|
|
1223
|
+
|
|
1150
1224
|
// Optional: Custom soft delete hook
|
|
1151
1225
|
beforeDelete({ model, action, request, context, db, utils, controller }) {
|
|
1152
1226
|
// Custom logic before soft delete
|
|
1153
1227
|
console.log('Soft deleting user:', request.where);
|
|
1154
1228
|
}
|
|
1155
|
-
|
|
1229
|
+
|
|
1156
1230
|
// Optional: Custom hook after soft delete
|
|
1157
1231
|
afterDelete({ model, action, data, request, context, db, utils, controller }) {
|
|
1158
1232
|
// Custom logic after soft delete
|
|
@@ -1203,15 +1277,13 @@ class Users {
|
|
|
1203
1277
|
throw new Error('Email is required');
|
|
1204
1278
|
}
|
|
1205
1279
|
// Check if email already exists
|
|
1206
|
-
const existing = await db('users')
|
|
1207
|
-
.where('email', request.data.email)
|
|
1208
|
-
.first();
|
|
1280
|
+
const existing = await db('users').where('email', request.data.email).first();
|
|
1209
1281
|
if (existing && existing.id !== request.where?.id) {
|
|
1210
1282
|
throw new Error('Email already exists');
|
|
1211
1283
|
}
|
|
1212
1284
|
}
|
|
1213
1285
|
}
|
|
1214
|
-
|
|
1286
|
+
|
|
1215
1287
|
// Before hooks - run before the action executes
|
|
1216
1288
|
// Method naming: before{Action} (e.g., beforeCreate, beforeUpdate, beforeList, beforeDelete)
|
|
1217
1289
|
async beforeCreate({ model, action, request, context, db, utils, controller }) {
|
|
@@ -1220,18 +1292,18 @@ class Users {
|
|
|
1220
1292
|
request.data.updated_at = new Date();
|
|
1221
1293
|
return request.data;
|
|
1222
1294
|
}
|
|
1223
|
-
|
|
1295
|
+
|
|
1224
1296
|
async beforeUpdate({ model, action, request, context, db, utils, controller }) {
|
|
1225
1297
|
// Modify request data before update
|
|
1226
1298
|
request.data.updated_at = new Date();
|
|
1227
1299
|
return request.data;
|
|
1228
1300
|
}
|
|
1229
|
-
|
|
1301
|
+
|
|
1230
1302
|
async beforeDelete({ model, action, request, context, db, utils, controller }) {
|
|
1231
1303
|
// Logic before delete (works with both hard and soft delete)
|
|
1232
1304
|
console.log('Deleting user:', request.where);
|
|
1233
1305
|
}
|
|
1234
|
-
|
|
1306
|
+
|
|
1235
1307
|
// After hooks - run after the action executes
|
|
1236
1308
|
// Method naming: after{Action} (e.g., afterCreate, afterUpdate, afterList, afterDelete)
|
|
1237
1309
|
async afterCreate({ model, action, data, request, context, db, utils, controller }) {
|
|
@@ -1240,17 +1312,17 @@ class Users {
|
|
|
1240
1312
|
// Send welcome email, trigger notifications, etc.
|
|
1241
1313
|
return data;
|
|
1242
1314
|
}
|
|
1243
|
-
|
|
1315
|
+
|
|
1244
1316
|
async afterUpdate({ model, action, data, request, context, db, utils, controller }) {
|
|
1245
1317
|
console.log('User updated:', data);
|
|
1246
1318
|
return data;
|
|
1247
1319
|
}
|
|
1248
|
-
|
|
1320
|
+
|
|
1249
1321
|
async afterList({ model, action, data, request, context, db, utils, controller }) {
|
|
1250
1322
|
// Modify list results before returning
|
|
1251
1323
|
return data;
|
|
1252
1324
|
}
|
|
1253
|
-
|
|
1325
|
+
|
|
1254
1326
|
// Custom action hooks
|
|
1255
1327
|
// Method naming: on{Action}Action (e.g., onActivateAction, onDeactivateAction)
|
|
1256
1328
|
async onActivateAction({ model, action, request, context, db, utils, controller }) {
|
|
@@ -1258,7 +1330,7 @@ class Users {
|
|
|
1258
1330
|
.where(request.where)
|
|
1259
1331
|
.update({ is_active: true, updated_at: new Date() });
|
|
1260
1332
|
}
|
|
1261
|
-
|
|
1333
|
+
|
|
1262
1334
|
async onDeactivateAction({ model, action, request, context, db, utils, controller }) {
|
|
1263
1335
|
return await db('users')
|
|
1264
1336
|
.where(request.where)
|
|
@@ -1271,29 +1343,30 @@ module.exports = Users;
|
|
|
1271
1343
|
|
|
1272
1344
|
### Hook Arguments Reference
|
|
1273
1345
|
|
|
1274
|
-
| Argument
|
|
1275
|
-
|
|
1276
|
-
| `model`
|
|
1277
|
-
| `action`
|
|
1278
|
-
| `request`
|
|
1279
|
-
| `context`
|
|
1280
|
-
| `db`
|
|
1281
|
-
| `utils`
|
|
1282
|
-
| `controller` | ControllerWrapper instance
|
|
1283
|
-
| `data`
|
|
1346
|
+
| Argument | Description |
|
|
1347
|
+
| ------------ | ------------------------------------------------------------- |
|
|
1348
|
+
| `model` | Model definition object with table, columns, relations |
|
|
1349
|
+
| `action` | Current action being performed (create, update, delete, etc.) |
|
|
1350
|
+
| `request` | The request object containing where, data, etc. |
|
|
1351
|
+
| `context` | Custom context passed from the controller |
|
|
1352
|
+
| `db` | Knex database instance for direct queries |
|
|
1353
|
+
| `utils` | Utility functions |
|
|
1354
|
+
| `controller` | ControllerWrapper instance |
|
|
1355
|
+
| `data` | (After hooks only) Result of the action |
|
|
1284
1356
|
|
|
1285
1357
|
### Complete Hook Types Reference
|
|
1286
1358
|
|
|
1287
|
-
| Hook Type
|
|
1288
|
-
|
|
1289
|
-
| Validate
|
|
1290
|
-
| Before
|
|
1291
|
-
| After
|
|
1292
|
-
| Custom Action
|
|
1293
|
-
| Soft Delete
|
|
1294
|
-
| Custom Relation | `get{RelName}Relation` | During eager loading
|
|
1359
|
+
| Hook Type | Method Naming | When Called | Use Case |
|
|
1360
|
+
| --------------- | ---------------------- | ---------------------- | --------------------------------------- |
|
|
1361
|
+
| Validate | `validate` | Before any action | Input validation, authorization |
|
|
1362
|
+
| Before | `before{Action}` | Before action executes | Modify request data, add timestamps |
|
|
1363
|
+
| After | `after{Action}` | After action executes | Transform results, trigger side effects |
|
|
1364
|
+
| Custom Action | `on{Action}Action` | For custom actions | Implement business logic |
|
|
1365
|
+
| Soft Delete | `hasSoftDelete = true` | During delete/list | Enable soft delete |
|
|
1366
|
+
| Custom Relation | `get{RelName}Relation` | During eager loading | Custom data fetching |
|
|
1295
1367
|
|
|
1296
1368
|
**Available Before/After hooks:**
|
|
1369
|
+
|
|
1297
1370
|
- `beforeCreate` / `afterCreate`
|
|
1298
1371
|
- `beforeUpdate` / `afterUpdate`
|
|
1299
1372
|
- `beforeDelete` / `afterDelete`
|
|
@@ -1316,7 +1389,7 @@ POST /api/Users/crud
|
|
|
1316
1389
|
|
|
1317
1390
|
POST /api/Users/crud
|
|
1318
1391
|
{
|
|
1319
|
-
"action": "deactivate",
|
|
1392
|
+
"action": "deactivate",
|
|
1320
1393
|
"where": { "id": 1 }
|
|
1321
1394
|
}
|
|
1322
1395
|
|
|
@@ -1343,7 +1416,7 @@ const userValidationRules = {
|
|
|
1343
1416
|
first_name: 'required|type:string|maxLen:100',
|
|
1344
1417
|
last_name: 'required|type:string|maxLen:100',
|
|
1345
1418
|
age: 'type:number|min:0|max:150',
|
|
1346
|
-
is_active: 'type:boolean'
|
|
1419
|
+
is_active: 'type:boolean',
|
|
1347
1420
|
};
|
|
1348
1421
|
|
|
1349
1422
|
// Validate and create user
|
|
@@ -1351,30 +1424,33 @@ app.post('/api/users/validate', async (req, res) => {
|
|
|
1351
1424
|
try {
|
|
1352
1425
|
// Validate request data
|
|
1353
1426
|
const validatedData = await validate(req.body, userValidationRules);
|
|
1354
|
-
|
|
1355
|
-
const result = await korm.processRequest(
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1427
|
+
|
|
1428
|
+
const result = await korm.processRequest(
|
|
1429
|
+
{
|
|
1430
|
+
action: 'create',
|
|
1431
|
+
data: validatedData,
|
|
1432
|
+
},
|
|
1433
|
+
'Users'
|
|
1434
|
+
);
|
|
1435
|
+
|
|
1360
1436
|
res.status(201).json({
|
|
1361
1437
|
success: true,
|
|
1362
1438
|
message: 'User created successfully',
|
|
1363
|
-
data: result
|
|
1439
|
+
data: result,
|
|
1364
1440
|
});
|
|
1365
1441
|
} catch (error) {
|
|
1366
1442
|
if (error.name === 'ValidationError') {
|
|
1367
1443
|
return res.status(400).json({
|
|
1368
1444
|
success: false,
|
|
1369
1445
|
message: 'Validation failed',
|
|
1370
|
-
errors: error.message
|
|
1446
|
+
errors: error.message,
|
|
1371
1447
|
});
|
|
1372
1448
|
}
|
|
1373
|
-
|
|
1449
|
+
|
|
1374
1450
|
res.status(500).json({
|
|
1375
1451
|
success: false,
|
|
1376
1452
|
message: 'Error creating user',
|
|
1377
|
-
error: error.message
|
|
1453
|
+
error: error.message,
|
|
1378
1454
|
});
|
|
1379
1455
|
}
|
|
1380
1456
|
});
|
|
@@ -1392,13 +1468,13 @@ const advancedRules = {
|
|
|
1392
1468
|
phone: 'regex:phone',
|
|
1393
1469
|
password: 'required|type:string|minLen:8',
|
|
1394
1470
|
status: 'in:active,inactive,pending',
|
|
1395
|
-
user_id: 'exists:users,id'
|
|
1471
|
+
user_id: 'exists:users,id',
|
|
1396
1472
|
};
|
|
1397
1473
|
|
|
1398
1474
|
const customRegex = {
|
|
1399
1475
|
username: /^[a-zA-Z0-9_]+$/,
|
|
1400
1476
|
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
1401
|
-
phone: /^\+?[\d\s-()]{10,15}
|
|
1477
|
+
phone: /^\+?[\d\s-()]{10,15}$/,
|
|
1402
1478
|
};
|
|
1403
1479
|
|
|
1404
1480
|
const validatedData = await validate(data, advancedRules, { customRegex });
|
|
@@ -1489,20 +1565,21 @@ type|modifier1|modifier2|...
|
|
|
1489
1565
|
|
|
1490
1566
|
**Column Modifiers:**
|
|
1491
1567
|
|
|
1492
|
-
| Modifier
|
|
1493
|
-
|
|
1494
|
-
| `size:n`
|
|
1495
|
-
| `unsigned`
|
|
1496
|
-
| `primaryKey`
|
|
1497
|
-
| `autoIncrement`
|
|
1498
|
-
| `notNull`
|
|
1499
|
-
| `unique`
|
|
1500
|
-
| `default:value`
|
|
1501
|
-
| `onUpdate:value`
|
|
1502
|
-
| `comment:text`
|
|
1503
|
-
| `foreignKey:table:column` | Foreign key
|
|
1568
|
+
| Modifier | Description | Example |
|
|
1569
|
+
| ------------------------- | ------------------ | ---------- | --------------------------- | -------------- |
|
|
1570
|
+
| `size:n` | Column size | `varchar | size:255` |
|
|
1571
|
+
| `unsigned` | Unsigned integer | `int | unsigned` |
|
|
1572
|
+
| `primaryKey` | Primary key column | `bigint | primaryKey` |
|
|
1573
|
+
| `autoIncrement` | Auto increment | `bigint | primaryKey | autoIncrement` |
|
|
1574
|
+
| `notNull` | Not nullable | `varchar | size:255 | notNull` |
|
|
1575
|
+
| `unique` | Unique constraint | `varchar | unique` |
|
|
1576
|
+
| `default:value` | Default value | `tinyint | default:1` |
|
|
1577
|
+
| `onUpdate:value` | On update value | `timestamp | onUpdate:CURRENT_TIMESTAMP` |
|
|
1578
|
+
| `comment:text` | Column comment | `varchar | comment:User email address` |
|
|
1579
|
+
| `foreignKey:table:column` | Foreign key | `int | foreignKey:users:id` |
|
|
1504
1580
|
|
|
1505
1581
|
**Special Default Values:**
|
|
1582
|
+
|
|
1506
1583
|
- `now` or `now()` → `CURRENT_TIMESTAMP`
|
|
1507
1584
|
|
|
1508
1585
|
**Example Column Definitions:**
|
|
@@ -1550,11 +1627,11 @@ Seed data is automatically inserted when `syncDatabase()` is called and the tabl
|
|
|
1550
1627
|
const { initializeKORM } = require('@dreamtree-org/korm-js');
|
|
1551
1628
|
|
|
1552
1629
|
const korm = initializeKORM({
|
|
1553
|
-
db: db,
|
|
1554
|
-
dbClient: 'mysql',
|
|
1555
|
-
schema: null,
|
|
1556
|
-
resolverPath: null,
|
|
1557
|
-
debug: false
|
|
1630
|
+
db: db, // Knex database instance
|
|
1631
|
+
dbClient: 'mysql', // 'mysql', 'mysql2', 'pg', 'postgresql', 'sqlite', 'sqlite3'
|
|
1632
|
+
schema: null, // Optional: initial schema object
|
|
1633
|
+
resolverPath: null, // Optional: path to models directory (default: process.cwd())
|
|
1634
|
+
debug: false, // Optional: enable SQL debugging (default: false)
|
|
1558
1635
|
});
|
|
1559
1636
|
|
|
1560
1637
|
// Process any CRUD request (automatically handles other_requests if present)
|
|
@@ -1581,64 +1658,66 @@ const modelInstance = korm.getModelInstance(modelDef);
|
|
|
1581
1658
|
|
|
1582
1659
|
### ProcessRequest Options
|
|
1583
1660
|
|
|
1584
|
-
| Parameter
|
|
1585
|
-
|
|
1586
|
-
| `action`
|
|
1587
|
-
| `where`
|
|
1588
|
-
| `data`
|
|
1589
|
-
| `select`
|
|
1590
|
-
| `orderBy`
|
|
1591
|
-
| `limit`
|
|
1592
|
-
| `offset`
|
|
1593
|
-
| `page`
|
|
1594
|
-
| `with`
|
|
1595
|
-
| `withWhere`
|
|
1596
|
-
| `groupBy`
|
|
1597
|
-
| `having`
|
|
1598
|
-
| `distinct`
|
|
1599
|
-
| `join`
|
|
1600
|
-
| `leftJoin`
|
|
1601
|
-
| `rightJoin`
|
|
1602
|
-
| `innerJoin`
|
|
1603
|
-
| `conflict`
|
|
1604
|
-
| `other_requests` | object
|
|
1661
|
+
| Parameter | Type | Description |
|
|
1662
|
+
| ---------------- | -------------------- | ------------------------------------------------------------------------------------ |
|
|
1663
|
+
| `action` | string | Action to perform (list, show, create, update, delete, count, replace, upsert, sync) |
|
|
1664
|
+
| `where` | object/array | Filter conditions |
|
|
1665
|
+
| `data` | object/array | Data for create/update operations |
|
|
1666
|
+
| `select` | array/string | Columns to select |
|
|
1667
|
+
| `orderBy` | object/array/string | Sorting configuration |
|
|
1668
|
+
| `limit` | number | Maximum records to return |
|
|
1669
|
+
| `offset` | number | Records to skip |
|
|
1670
|
+
| `page` | number | Page number (alternative to offset) |
|
|
1671
|
+
| `with` | array | Related models to eager load |
|
|
1672
|
+
| `withWhere` | object | Filter conditions for eager loaded relations |
|
|
1673
|
+
| `groupBy` | array/string | Group by columns |
|
|
1674
|
+
| `having` | object | Having conditions |
|
|
1675
|
+
| `distinct` | boolean/array/string | Distinct results |
|
|
1676
|
+
| `join` | object/array | Join configuration |
|
|
1677
|
+
| `leftJoin` | object/array | Left join configuration |
|
|
1678
|
+
| `rightJoin` | object/array | Right join configuration |
|
|
1679
|
+
| `innerJoin` | object/array | Inner join configuration |
|
|
1680
|
+
| `conflict` | array | Conflict columns for upsert |
|
|
1681
|
+
| `other_requests` | object | Nested requests for related models |
|
|
1605
1682
|
|
|
1606
1683
|
### ProcessRequest Actions Summary
|
|
1607
1684
|
|
|
1608
|
-
| Action
|
|
1609
|
-
|
|
1610
|
-
| `list`
|
|
1611
|
-
| `show`
|
|
1612
|
-
| `create`
|
|
1613
|
-
| `update`
|
|
1614
|
-
| `delete`
|
|
1615
|
-
| `count`
|
|
1616
|
-
| `
|
|
1617
|
-
| `
|
|
1618
|
-
| `
|
|
1685
|
+
| Action | Description | Required Fields |
|
|
1686
|
+
| --------- | ------------------------ | ---------------------------------------------------------------- |
|
|
1687
|
+
| `list` | Get multiple records | None (optional: `where`, `select`, `orderBy`, `limit`, `offset`) |
|
|
1688
|
+
| `show` | Get single record | `where` |
|
|
1689
|
+
| `create` | Create new record | `data` |
|
|
1690
|
+
| `update` | Update record(s) | `where`, `data` |
|
|
1691
|
+
| `delete` | Delete record(s) | `where` |
|
|
1692
|
+
| `count` | Count records | None (optional: `where`) |
|
|
1693
|
+
| `sum` | Sum column or expression | `data.sumColumn` or `data.sumFormula` (optional: `where`) |
|
|
1694
|
+
| `replace` | Replace record (MySQL) | `data` |
|
|
1695
|
+
| `upsert` | Insert or update | `data`, `conflict` |
|
|
1696
|
+
| `sync` | Upsert + delete | `data`, `conflict`, `where` |
|
|
1619
1697
|
|
|
1620
1698
|
### Validation Rules
|
|
1621
1699
|
|
|
1622
|
-
| Rule
|
|
1623
|
-
|
|
1624
|
-
| `required`
|
|
1625
|
-
| `type:string`
|
|
1626
|
-
| `type:number`
|
|
1627
|
-
| `type:boolean`
|
|
1628
|
-
| `type:array`
|
|
1629
|
-
| `type:object`
|
|
1630
|
-
| `type:longText`
|
|
1631
|
-
| `minLen:n`
|
|
1632
|
-
| `maxLen:n`
|
|
1633
|
-
| `min:n`
|
|
1634
|
-
| `max:n`
|
|
1635
|
-
| `in:val1,val2`
|
|
1636
|
-
| `regex:name`
|
|
1637
|
-
| `call:name`
|
|
1638
|
-
| `exists:table,column` | Value must exist in database table
|
|
1639
|
-
| `default:value`
|
|
1700
|
+
| Rule | Description | Example |
|
|
1701
|
+
| --------------------- | -------------------------------------------- | ------------------------------ |
|
|
1702
|
+
| `required` | Field is required | `'required'` |
|
|
1703
|
+
| `type:string` | Field must be string | `'type:string'` |
|
|
1704
|
+
| `type:number` | Field must be number | `'type:number'` |
|
|
1705
|
+
| `type:boolean` | Field must be boolean | `'type:boolean'` |
|
|
1706
|
+
| `type:array` | Field must be array | `'type:array'` |
|
|
1707
|
+
| `type:object` | Field must be object | `'type:object'` |
|
|
1708
|
+
| `type:longText` | Field must be string > 255 chars | `'type:longText'` |
|
|
1709
|
+
| `minLen:n` | Minimum string/array length | `'minLen:3'` |
|
|
1710
|
+
| `maxLen:n` | Maximum string/array length | `'maxLen:255'` |
|
|
1711
|
+
| `min:n` | Minimum numeric value | `'min:0'` |
|
|
1712
|
+
| `max:n` | Maximum numeric value | `'max:150'` |
|
|
1713
|
+
| `in:val1,val2` | Value must be in list | `'in:active,inactive,pending'` |
|
|
1714
|
+
| `regex:name` | Custom regex pattern (define in options) | `'regex:email'` |
|
|
1715
|
+
| `call:name` | Custom callback function (define in options) | `'call:myValidator'` |
|
|
1716
|
+
| `exists:table,column` | Value must exist in database table | `'exists:users,id'` |
|
|
1717
|
+
| `default:value` | Default value if not provided | `'default:active'` |
|
|
1640
1718
|
|
|
1641
1719
|
**Rule Chaining:** Combine multiple rules with `|` pipe character:
|
|
1720
|
+
|
|
1642
1721
|
```javascript
|
|
1643
1722
|
{
|
|
1644
1723
|
username: 'required|type:string|minLen:3|maxLen:50',
|
|
@@ -1651,14 +1730,14 @@ const modelInstance = korm.getModelInstance(modelDef);
|
|
|
1651
1730
|
### Library Exports
|
|
1652
1731
|
|
|
1653
1732
|
```javascript
|
|
1654
|
-
const {
|
|
1655
|
-
initializeKORM,
|
|
1656
|
-
helperUtility,
|
|
1657
|
-
emitter,
|
|
1658
|
-
validate,
|
|
1659
|
-
logger,
|
|
1660
|
-
lib,
|
|
1661
|
-
LibClasses
|
|
1733
|
+
const {
|
|
1734
|
+
initializeKORM, // Initialize KORM with database connection
|
|
1735
|
+
helperUtility, // Utility functions (file operations, string manipulation)
|
|
1736
|
+
emitter, // Event emitter instance
|
|
1737
|
+
validate, // Validation function
|
|
1738
|
+
logger, // Logger utility instance
|
|
1739
|
+
lib, // Additional utilities
|
|
1740
|
+
LibClasses, // Library classes (Emitter)
|
|
1662
1741
|
} = require('@dreamtree-org/korm-js');
|
|
1663
1742
|
|
|
1664
1743
|
// lib contains:
|
|
@@ -1694,14 +1773,14 @@ logger.error('Error message');
|
|
|
1694
1773
|
|
|
1695
1774
|
### Log Levels
|
|
1696
1775
|
|
|
1697
|
-
| Level
|
|
1698
|
-
|
|
1699
|
-
| `NONE`
|
|
1700
|
-
| `ERROR` | 1
|
|
1701
|
-
| `WARN`
|
|
1702
|
-
| `INFO`
|
|
1703
|
-
| `LOG`
|
|
1704
|
-
| `DEBUG` | 5
|
|
1776
|
+
| Level | Value | Description |
|
|
1777
|
+
| ------- | ----- | ----------------------------- |
|
|
1778
|
+
| `NONE` | 0 | Disable all logging |
|
|
1779
|
+
| `ERROR` | 1 | Only errors |
|
|
1780
|
+
| `WARN` | 2 | Warnings and errors (default) |
|
|
1781
|
+
| `INFO` | 3 | Info, warnings, and errors |
|
|
1782
|
+
| `LOG` | 4 | General logs and above |
|
|
1783
|
+
| `DEBUG` | 5 | All messages (most verbose) |
|
|
1705
1784
|
|
|
1706
1785
|
### Configuration
|
|
1707
1786
|
|
|
@@ -1709,14 +1788,14 @@ logger.error('Error message');
|
|
|
1709
1788
|
const { logger } = require('@dreamtree-org/korm-js');
|
|
1710
1789
|
|
|
1711
1790
|
// Set log level programmatically
|
|
1712
|
-
logger.setLevel('debug');
|
|
1713
|
-
logger.setLevel('warn');
|
|
1714
|
-
logger.setLevel('error');
|
|
1715
|
-
logger.setLevel('none');
|
|
1791
|
+
logger.setLevel('debug'); // Show all logs
|
|
1792
|
+
logger.setLevel('warn'); // Only warnings and errors (default)
|
|
1793
|
+
logger.setLevel('error'); // Only errors
|
|
1794
|
+
logger.setLevel('none'); // Disable all logging
|
|
1716
1795
|
|
|
1717
1796
|
// Enable/disable logging
|
|
1718
|
-
logger.disable();
|
|
1719
|
-
logger.enable();
|
|
1797
|
+
logger.disable(); // Temporarily disable all logging
|
|
1798
|
+
logger.enable(); // Re-enable logging
|
|
1720
1799
|
```
|
|
1721
1800
|
|
|
1722
1801
|
### Environment Variable
|
|
@@ -1745,10 +1824,10 @@ const { logger } = require('@dreamtree-org/korm-js');
|
|
|
1745
1824
|
|
|
1746
1825
|
// Create a child logger for a specific module
|
|
1747
1826
|
const authLogger = logger.child('[Auth]');
|
|
1748
|
-
authLogger.info('User logged in');
|
|
1827
|
+
authLogger.info('User logged in'); // Output: [KORM][Auth] [INFO] User logged in
|
|
1749
1828
|
|
|
1750
1829
|
const dbLogger = logger.child('[DB]');
|
|
1751
|
-
dbLogger.debug('Query executed');
|
|
1830
|
+
dbLogger.debug('Query executed'); // Output: [KORM][DB] [DEBUG] Query executed
|
|
1752
1831
|
```
|
|
1753
1832
|
|
|
1754
1833
|
### Logger Options
|
|
@@ -1758,10 +1837,10 @@ const { Logger } = require('@dreamtree-org/korm-js').logger;
|
|
|
1758
1837
|
|
|
1759
1838
|
// Create a custom logger instance
|
|
1760
1839
|
const customLogger = new Logger({
|
|
1761
|
-
prefix: '[MyApp]',
|
|
1762
|
-
enabled: true,
|
|
1763
|
-
level: 'debug',
|
|
1764
|
-
timestamps: true
|
|
1840
|
+
prefix: '[MyApp]', // Custom prefix (default: '[KORM]')
|
|
1841
|
+
enabled: true, // Enable/disable logging (default: true)
|
|
1842
|
+
level: 'debug', // Log level (default: 'warn' or KORM_LOG_LEVEL)
|
|
1843
|
+
timestamps: true, // Include timestamps (default: false)
|
|
1765
1844
|
});
|
|
1766
1845
|
|
|
1767
1846
|
customLogger.info('Application started');
|
|
@@ -1799,7 +1878,7 @@ const { initializeKORM } = require('@dreamtree-org/korm-js');
|
|
|
1799
1878
|
const korm = initializeKORM({
|
|
1800
1879
|
db: db,
|
|
1801
1880
|
dbClient: 'mysql',
|
|
1802
|
-
debug: true
|
|
1881
|
+
debug: true, // Enable SQL debugging
|
|
1803
1882
|
});
|
|
1804
1883
|
```
|
|
1805
1884
|
|
|
@@ -1841,10 +1920,10 @@ POST /api/Post/crud
|
|
|
1841
1920
|
|
|
1842
1921
|
### What's Included in sqlDebug
|
|
1843
1922
|
|
|
1844
|
-
| Index | Query Type
|
|
1845
|
-
|
|
1846
|
-
| 0
|
|
1847
|
-
| 1
|
|
1923
|
+
| Index | Query Type | Description |
|
|
1924
|
+
| ----- | ----------- | ---------------------------------------------------------------- |
|
|
1925
|
+
| 0 | Main Query | The primary SELECT query with all WHERE, ORDER BY, LIMIT clauses |
|
|
1926
|
+
| 1 | Count Query | The COUNT query used for pagination (only when limit > 0) |
|
|
1848
1927
|
|
|
1849
1928
|
### Debug Mode Best Practices
|
|
1850
1929
|
|
|
@@ -1853,14 +1932,14 @@ POST /api/Post/crud
|
|
|
1853
1932
|
const korm = initializeKORM({
|
|
1854
1933
|
db: db,
|
|
1855
1934
|
dbClient: 'mysql',
|
|
1856
|
-
debug: process.env.NODE_ENV === 'development'
|
|
1935
|
+
debug: process.env.NODE_ENV === 'development',
|
|
1857
1936
|
});
|
|
1858
1937
|
|
|
1859
1938
|
// Or use environment variable
|
|
1860
1939
|
const korm = initializeKORM({
|
|
1861
1940
|
db: db,
|
|
1862
1941
|
dbClient: 'mysql',
|
|
1863
|
-
debug: process.env.KORM_DEBUG === 'true'
|
|
1942
|
+
debug: process.env.KORM_DEBUG === 'true',
|
|
1864
1943
|
});
|
|
1865
1944
|
```
|
|
1866
1945
|
|
|
@@ -1880,14 +1959,14 @@ const db = knex({
|
|
|
1880
1959
|
user: process.env.DB_USER || 'root',
|
|
1881
1960
|
password: process.env.DB_PASS || 'password',
|
|
1882
1961
|
database: process.env.DB_NAME || 'database_name',
|
|
1883
|
-
port: process.env.DB_PORT || 3306
|
|
1884
|
-
}
|
|
1962
|
+
port: process.env.DB_PORT || 3306,
|
|
1963
|
+
},
|
|
1885
1964
|
});
|
|
1886
1965
|
|
|
1887
1966
|
const korm = initializeKORM({
|
|
1888
1967
|
db: db,
|
|
1889
1968
|
dbClient: 'mysql',
|
|
1890
|
-
debug: process.env.NODE_ENV === 'development'
|
|
1969
|
+
debug: process.env.NODE_ENV === 'development', // Enable SQL debugging in development
|
|
1891
1970
|
});
|
|
1892
1971
|
```
|
|
1893
1972
|
|
|
@@ -1903,14 +1982,14 @@ const db = knex({
|
|
|
1903
1982
|
user: process.env.DB_USER || 'username',
|
|
1904
1983
|
password: process.env.DB_PASS || 'password',
|
|
1905
1984
|
database: process.env.DB_NAME || 'database_name',
|
|
1906
|
-
port: process.env.DB_PORT || 5432
|
|
1907
|
-
}
|
|
1985
|
+
port: process.env.DB_PORT || 5432,
|
|
1986
|
+
},
|
|
1908
1987
|
});
|
|
1909
1988
|
|
|
1910
1989
|
const korm = initializeKORM({
|
|
1911
1990
|
db: db,
|
|
1912
1991
|
dbClient: 'pg',
|
|
1913
|
-
debug: process.env.NODE_ENV === 'development'
|
|
1992
|
+
debug: process.env.NODE_ENV === 'development',
|
|
1914
1993
|
});
|
|
1915
1994
|
```
|
|
1916
1995
|
|
|
@@ -1922,14 +2001,14 @@ const knex = require('knex');
|
|
|
1922
2001
|
const db = knex({
|
|
1923
2002
|
client: 'sqlite3',
|
|
1924
2003
|
connection: {
|
|
1925
|
-
filename: process.env.DB_FILE || './database.sqlite'
|
|
1926
|
-
}
|
|
2004
|
+
filename: process.env.DB_FILE || './database.sqlite',
|
|
2005
|
+
},
|
|
1927
2006
|
});
|
|
1928
2007
|
|
|
1929
2008
|
const korm = initializeKORM({
|
|
1930
2009
|
db: db,
|
|
1931
2010
|
dbClient: 'sqlite',
|
|
1932
|
-
debug: process.env.NODE_ENV === 'development'
|
|
2011
|
+
debug: process.env.NODE_ENV === 'development',
|
|
1933
2012
|
});
|
|
1934
2013
|
```
|
|
1935
2014
|
|
|
@@ -1939,12 +2018,12 @@ const korm = initializeKORM({
|
|
|
1939
2018
|
// Global error handler
|
|
1940
2019
|
app.use((error, req, res, next) => {
|
|
1941
2020
|
console.error('KORM Error:', error);
|
|
1942
|
-
|
|
2021
|
+
|
|
1943
2022
|
res.status(error.status || 500).json({
|
|
1944
2023
|
success: false,
|
|
1945
2024
|
message: 'Internal server error',
|
|
1946
2025
|
error: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong',
|
|
1947
|
-
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
|
|
2026
|
+
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined,
|
|
1948
2027
|
});
|
|
1949
2028
|
});
|
|
1950
2029
|
|
|
@@ -1960,22 +2039,22 @@ app.post('/api/:model/crud', async (req, res) => {
|
|
|
1960
2039
|
return res.status(400).json({
|
|
1961
2040
|
success: false,
|
|
1962
2041
|
message: 'Validation failed',
|
|
1963
|
-
errors: error.message
|
|
2042
|
+
errors: error.message,
|
|
1964
2043
|
});
|
|
1965
2044
|
}
|
|
1966
|
-
|
|
2045
|
+
|
|
1967
2046
|
// Handle not found errors
|
|
1968
2047
|
if (error.message.includes('not found')) {
|
|
1969
2048
|
return res.status(404).json({
|
|
1970
2049
|
success: false,
|
|
1971
|
-
message: error.message
|
|
2050
|
+
message: error.message,
|
|
1972
2051
|
});
|
|
1973
2052
|
}
|
|
1974
|
-
|
|
2053
|
+
|
|
1975
2054
|
// Handle other errors
|
|
1976
2055
|
res.status(400).json({
|
|
1977
2056
|
success: false,
|
|
1978
|
-
message: error.message
|
|
2057
|
+
message: error.message,
|
|
1979
2058
|
});
|
|
1980
2059
|
}
|
|
1981
2060
|
});
|
|
@@ -2000,15 +2079,15 @@ const db = knex({
|
|
|
2000
2079
|
user: process.env.DB_USER || 'root',
|
|
2001
2080
|
password: process.env.DB_PASS || 'password',
|
|
2002
2081
|
database: process.env.DB_NAME || 'my_database',
|
|
2003
|
-
port: process.env.DB_PORT || 3306
|
|
2004
|
-
}
|
|
2082
|
+
port: process.env.DB_PORT || 3306,
|
|
2083
|
+
},
|
|
2005
2084
|
});
|
|
2006
2085
|
|
|
2007
2086
|
// Initialize KORM
|
|
2008
2087
|
const korm = initializeKORM({
|
|
2009
2088
|
db: db,
|
|
2010
2089
|
dbClient: 'mysql',
|
|
2011
|
-
debug: process.env.NODE_ENV === 'development'
|
|
2090
|
+
debug: process.env.NODE_ENV === 'development', // SQL debugging in dev mode
|
|
2012
2091
|
});
|
|
2013
2092
|
|
|
2014
2093
|
// Initialize app
|
|
@@ -2023,7 +2102,7 @@ async function initApp() {
|
|
|
2023
2102
|
helperUtility.file.createDirectory('schema');
|
|
2024
2103
|
helperUtility.file.writeJSON('schema/schema.json', schema);
|
|
2025
2104
|
}
|
|
2026
|
-
|
|
2105
|
+
|
|
2027
2106
|
// Sync database
|
|
2028
2107
|
await korm.syncDatabase();
|
|
2029
2108
|
console.log('✅ Database synced');
|