@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/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 // Set to true for SQL debugging
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. Replace Operation
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
- ### 8. Upsert Operation (Insert or Update)
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
- ### 9. Sync Operation (Upsert + Delete)
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 | Description | Syntax | Example |
562
- |----------|-------------|--------|---------|
563
- | `=` (default) | Equals | `value` | `"status": "active"` |
564
- | `>=` | Greater than or equal | `>=value` | `"age": ">=18"` |
565
- | `<=` | Less than or equal | `<=value` | `"age": "<=65"` |
566
- | `>` | Greater than | `>value` | `"price": ">100"` |
567
- | `<` | Less than | `<value` | `"price": "<500"` |
568
- | `!=` | Not equal | `!value` | `"status": "!deleted"` |
569
- | `like` | Pattern matching (auto) | `%value%` | `"name": "%john%"` |
570
- | `in` | Value in list | `[]val1,val2` | `"status": "[]active,pending"` |
571
- | `notIn` | Value not in list | `![]val1,val2` | `"role": "![]banned,suspended"` |
572
- | `between` | Range (inclusive) | `><min,max` | `"age": "><18,65"` |
573
- | `notBetween` | Outside range | `<>min,max` | `"score": "<>0,50"` |
574
- | `null` | IS NULL check | `null` | `"deleted_at": null` |
575
- | `Or:column` | OR condition prefix | `Or:column` | `"Or:name": "John"` |
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 | Meaning | Use Case |
875
- |---------|---------|----------|
876
- | `"!RelName": true` | NOT EXISTS on direct relation | Users with no posts |
877
- | `"Parent.!Child": true` | NOT EXISTS on nested relation | Posts where user has no roles |
878
- | `"Parent.!Child.column": value` | NOT EXISTS with condition | Posts where user has no admin role |
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 | `where` (Nested) | `withWhere` |
946
- |---------|-----------------|-------------|
947
- | Affects parent query | Yes (filters parent rows) | No (only filters related rows) |
948
- | Purpose | Filter parents that have matching relations | Filter which related rows are loaded |
949
- | SQL generated | `WHERE EXISTS (subquery)` | Additional `WHERE` on relation query |
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
- .where('user_id', row.id)
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 | Description |
1118
- |----------|-------------|
1119
- | `rows` | Parent rows to attach relation data to (modify in place) |
1120
- | `relName` | The relation name being fetched |
1121
- | `model` | Model definition object |
1122
- | `withTree` | Nested relations tree for further loading |
1123
- | `controller` | ControllerWrapper instance |
1124
- | `relation` | Relation definition from schema (may be undefined for custom relations) |
1125
- | `qb` | QueryBuilder instance for building queries |
1126
- | `db` | Knex database instance for direct queries |
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 | Description |
1275
- |----------|-------------|
1276
- | `model` | Model definition object with table, columns, relations |
1277
- | `action` | Current action being performed (create, update, delete, etc.) |
1278
- | `request` | The request object containing where, data, etc. |
1279
- | `context` | Custom context passed from the controller |
1280
- | `db` | Knex database instance for direct queries |
1281
- | `utils` | Utility functions |
1282
- | `controller` | ControllerWrapper instance |
1283
- | `data` | (After hooks only) Result of the action |
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 | Method Naming | When Called | Use Case |
1288
- |-----------|---------------|-------------|----------|
1289
- | Validate | `validate` | Before any action | Input validation, authorization |
1290
- | Before | `before{Action}` | Before action executes | Modify request data, add timestamps |
1291
- | After | `after{Action}` | After action executes | Transform results, trigger side effects |
1292
- | Custom Action | `on{Action}Action` | For custom actions | Implement business logic |
1293
- | Soft Delete | `hasSoftDelete = true` | During delete/list | Enable soft delete |
1294
- | Custom Relation | `get{RelName}Relation` | During eager loading | Custom data fetching |
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
- action: 'create',
1357
- data: validatedData
1358
- }, 'Users');
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 | Description | Example |
1493
- |----------|-------------|---------|
1494
- | `size:n` | Column size | `varchar|size:255` |
1495
- | `unsigned` | Unsigned integer | `int|unsigned` |
1496
- | `primaryKey` | Primary key column | `bigint|primaryKey` |
1497
- | `autoIncrement` | Auto increment | `bigint|primaryKey|autoIncrement` |
1498
- | `notNull` | Not nullable | `varchar|size:255|notNull` |
1499
- | `unique` | Unique constraint | `varchar|unique` |
1500
- | `default:value` | Default value | `tinyint|default:1` |
1501
- | `onUpdate:value` | On update value | `timestamp|onUpdate:CURRENT_TIMESTAMP` |
1502
- | `comment:text` | Column comment | `varchar|comment:User email address` |
1503
- | `foreignKey:table:column` | Foreign key | `int|foreignKey:users:id` |
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, // Knex database instance
1554
- dbClient: 'mysql', // 'mysql', 'mysql2', 'pg', 'postgresql', 'sqlite', 'sqlite3'
1555
- schema: null, // Optional: initial schema object
1556
- resolverPath: null, // Optional: path to models directory (default: process.cwd())
1557
- debug: false // Optional: enable SQL debugging (default: 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 | Type | Description |
1585
- |-----------|------|-------------|
1586
- | `action` | string | Action to perform (list, show, create, update, delete, count, replace, upsert, sync) |
1587
- | `where` | object/array | Filter conditions |
1588
- | `data` | object/array | Data for create/update operations |
1589
- | `select` | array/string | Columns to select |
1590
- | `orderBy` | object/array/string | Sorting configuration |
1591
- | `limit` | number | Maximum records to return |
1592
- | `offset` | number | Records to skip |
1593
- | `page` | number | Page number (alternative to offset) |
1594
- | `with` | array | Related models to eager load |
1595
- | `withWhere` | object | Filter conditions for eager loaded relations |
1596
- | `groupBy` | array/string | Group by columns |
1597
- | `having` | object | Having conditions |
1598
- | `distinct` | boolean/array/string | Distinct results |
1599
- | `join` | object/array | Join configuration |
1600
- | `leftJoin` | object/array | Left join configuration |
1601
- | `rightJoin` | object/array | Right join configuration |
1602
- | `innerJoin` | object/array | Inner join configuration |
1603
- | `conflict` | array | Conflict columns for upsert |
1604
- | `other_requests` | object | Nested requests for related models |
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 | Description | Required Fields |
1609
- |--------|-------------|----------------|
1610
- | `list` | Get multiple records | None (optional: `where`, `select`, `orderBy`, `limit`, `offset`) |
1611
- | `show` | Get single record | `where` |
1612
- | `create` | Create new record | `data` |
1613
- | `update` | Update record(s) | `where`, `data` |
1614
- | `delete` | Delete record(s) | `where` |
1615
- | `count` | Count records | None (optional: `where`) |
1616
- | `replace` | Replace record (MySQL) | `data` |
1617
- | `upsert` | Insert or update | `data`, `conflict` |
1618
- | `sync` | Upsert + delete | `data`, `conflict`, `where` |
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 | Description | Example |
1623
- |------|-------------|---------|
1624
- | `required` | Field is required | `'required'` |
1625
- | `type:string` | Field must be string | `'type:string'` |
1626
- | `type:number` | Field must be number | `'type:number'` |
1627
- | `type:boolean` | Field must be boolean | `'type:boolean'` |
1628
- | `type:array` | Field must be array | `'type:array'` |
1629
- | `type:object` | Field must be object | `'type:object'` |
1630
- | `type:longText` | Field must be string > 255 chars | `'type:longText'` |
1631
- | `minLen:n` | Minimum string/array length | `'minLen:3'` |
1632
- | `maxLen:n` | Maximum string/array length | `'maxLen:255'` |
1633
- | `min:n` | Minimum numeric value | `'min:0'` |
1634
- | `max:n` | Maximum numeric value | `'max:150'` |
1635
- | `in:val1,val2` | Value must be in list | `'in:active,inactive,pending'` |
1636
- | `regex:name` | Custom regex pattern (define in options) | `'regex:email'` |
1637
- | `call:name` | Custom callback function (define in options) | `'call:myValidator'` |
1638
- | `exists:table,column` | Value must exist in database table | `'exists:users,id'` |
1639
- | `default:value` | Default value if not provided | `'default:active'` |
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, // Initialize KORM with database connection
1656
- helperUtility, // Utility functions (file operations, string manipulation)
1657
- emitter, // Event emitter instance
1658
- validate, // Validation function
1659
- logger, // Logger utility instance
1660
- lib, // Additional utilities
1661
- LibClasses // Library classes (Emitter)
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 | Value | Description |
1698
- |-------|-------|-------------|
1699
- | `NONE` | 0 | Disable all logging |
1700
- | `ERROR` | 1 | Only errors |
1701
- | `WARN` | 2 | Warnings and errors (default) |
1702
- | `INFO` | 3 | Info, warnings, and errors |
1703
- | `LOG` | 4 | General logs and above |
1704
- | `DEBUG` | 5 | All messages (most verbose) |
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'); // Show all logs
1713
- logger.setLevel('warn'); // Only warnings and errors (default)
1714
- logger.setLevel('error'); // Only errors
1715
- logger.setLevel('none'); // Disable all logging
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(); // Temporarily disable all logging
1719
- logger.enable(); // Re-enable logging
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'); // Output: [KORM][Auth] [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'); // Output: [KORM][DB] [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]', // Custom prefix (default: '[KORM]')
1762
- enabled: true, // Enable/disable logging (default: true)
1763
- level: 'debug', // Log level (default: 'warn' or KORM_LOG_LEVEL)
1764
- timestamps: true // Include timestamps (default: false)
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 // Enable SQL debugging
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 | Description |
1845
- |-------|------------|-------------|
1846
- | 0 | Main Query | The primary SELECT query with all WHERE, ORDER BY, LIMIT clauses |
1847
- | 1 | Count Query | The COUNT query used for pagination (only when limit > 0) |
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' // Enable SQL debugging in 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' // SQL debugging in dev mode
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');