@dreamtree-org/korm-js 1.0.53 → 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);
@@ -367,6 +396,7 @@ POST /api/Employee/crud
367
396
  ```
368
397
 
369
398
  **Notes:**
399
+
370
400
  - Use **either** `sumColumn` or `sumFormula`, not both. If both are provided, `sumColumn` is used.
371
401
  - In `sumFormula`, column names are written as `{columnName}` and are safely quoted; only letters, numbers, and underscore are allowed in names.
372
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.
@@ -599,21 +629,21 @@ POST /api/Users/crud
599
629
 
600
630
  ### Where Operators Reference
601
631
 
602
- | Operator | Description | Syntax | Example |
603
- |----------|-------------|--------|---------|
604
- | `=` (default) | Equals | `value` | `"status": "active"` |
605
- | `>=` | Greater than or equal | `>=value` | `"age": ">=18"` |
606
- | `<=` | Less than or equal | `<=value` | `"age": "<=65"` |
607
- | `>` | Greater than | `>value` | `"price": ">100"` |
608
- | `<` | Less than | `<value` | `"price": "<500"` |
609
- | `!=` | Not equal | `!value` | `"status": "!deleted"` |
610
- | `like` | Pattern matching (auto) | `%value%` | `"name": "%john%"` |
611
- | `in` | Value in list | `[]val1,val2` | `"status": "[]active,pending"` |
612
- | `notIn` | Value not in list | `![]val1,val2` | `"role": "![]banned,suspended"` |
613
- | `between` | Range (inclusive) | `><min,max` | `"age": "><18,65"` |
614
- | `notBetween` | Outside range | `<>min,max` | `"score": "<>0,50"` |
615
- | `null` | IS NULL check | `null` | `"deleted_at": null` |
616
- | `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"` |
617
647
 
618
648
  **Examples:**
619
649
 
@@ -630,7 +660,7 @@ POST /api/Users/crud
630
660
  "score": "><50,100"
631
661
  }
632
662
  }
633
- // SQL: WHERE age >= 18 AND status != 'deleted' AND name LIKE '%john%'
663
+ // SQL: WHERE age >= 18 AND status != 'deleted' AND name LIKE '%john%'
634
664
  // AND role IN ('admin', 'moderator', 'editor') AND score BETWEEN 50 AND 100
635
665
  ```
636
666
 
@@ -721,6 +751,7 @@ POST /api/Users/crud
721
751
  ```
722
752
 
723
753
  **Available join types:**
754
+
724
755
  - `join` - Regular join
725
756
  - `innerJoin` - Inner join
726
757
  - `leftJoin` - Left join
@@ -795,6 +826,7 @@ For many-to-many relationships, use the `through` key to specify the join/pivot
795
826
  ```
796
827
 
797
828
  **Many-to-Many Structure:**
829
+
798
830
  - `type`: `"many"` (always use "many" for many-to-many)
799
831
  - `table`: The related table name (e.g., `"roles"`)
800
832
  - `localKey`: The primary key in the current model (e.g., `"id"`)
@@ -865,7 +897,7 @@ POST /api/Post/crud
865
897
  },
866
898
  "with": ["User", "User.UserRole", "User.UserRole.Role"]
867
899
  }
868
- // 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
869
901
  // AND EXISTS (SELECT * FROM user_roles WHERE user_roles.user_id = users.id
870
902
  // AND EXISTS (SELECT * FROM roles WHERE roles.id = user_roles.role_id AND name = 'admin')))
871
903
  ```
@@ -884,7 +916,7 @@ POST /api/Post/crud
884
916
  },
885
917
  "with": ["User"]
886
918
  }
887
- // 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
888
920
  // AND NOT EXISTS (SELECT * FROM user_roles WHERE user_roles.user_id = users.id))
889
921
 
890
922
  // Get users with no posts
@@ -912,12 +944,13 @@ POST /api/Post/crud
912
944
 
913
945
  **NOT EXISTS Syntax Summary:**
914
946
 
915
- | Pattern | Meaning | Use Case |
916
- |---------|---------|----------|
917
- | `"!RelName": true` | NOT EXISTS on direct relation | Users with no posts |
918
- | `"Parent.!Child": true` | NOT EXISTS on nested relation | Posts where user has no roles |
919
- | `"Parent.!Child.column": value` | NOT EXISTS with condition | Posts where user has no admin role |
920
- ```
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
+ ````
921
954
 
922
955
  #### Filtering Eager Loaded Relations with `withWhere`
923
956
 
@@ -979,15 +1012,15 @@ POST /api/User/crud
979
1012
  "Or:Post.is_featured": true
980
1013
  }
981
1014
  }
982
- ```
1015
+ ````
983
1016
 
984
1017
  **Key differences between `where` and `withWhere`:**
985
1018
 
986
- | Feature | `where` (Nested) | `withWhere` |
987
- |---------|-----------------|-------------|
988
- | Affects parent query | Yes (filters parent rows) | No (only filters related rows) |
989
- | Purpose | Filter parents that have matching relations | Filter which related rows are loaded |
990
- | 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 |
991
1024
 
992
1025
  **Example showing the difference:**
993
1026
 
@@ -1012,6 +1045,7 @@ POST /api/User/crud
1012
1045
  ```
1013
1046
 
1014
1047
  **withWhere supports all operators:**
1048
+
1015
1049
  - Comparison: `">=18"`, `"<=100"`, `">50"`, `"<10"`, `"!deleted"`
1016
1050
  - LIKE: `"%pattern%"`
1017
1051
  - IN: `"[]val1,val2,val3"`
@@ -1085,6 +1119,7 @@ Example complete relationship structure:
1085
1119
  ### Custom Relation Hooks
1086
1120
 
1087
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
+
1088
1123
  - Virtual/computed relations
1089
1124
  - Cross-database relations
1090
1125
  - Complex aggregations
@@ -1100,35 +1135,33 @@ class Users {
1100
1135
  // rows = parent rows to attach relation data to
1101
1136
  // db = Knex database instance
1102
1137
  // qb = QueryBuilder instance
1103
-
1138
+
1104
1139
  for (const row of rows) {
1105
1140
  // Fetch custom data for each row
1106
- const stats = await db('user_statistics')
1107
- .where('user_id', row.id)
1108
- .first();
1109
-
1141
+ const stats = await db('user_statistics').where('user_id', row.id).first();
1142
+
1110
1143
  // Attach to row
1111
1144
  row.Statistics = stats || { posts: 0, comments: 0, likes: 0 };
1112
1145
  }
1113
1146
  }
1114
-
1147
+
1115
1148
  // Custom relation with aggregation
1116
1149
  async getPostCountRelation({ rows, db }) {
1117
- const userIds = rows.map(r => r.id);
1118
-
1150
+ const userIds = rows.map((r) => r.id);
1151
+
1119
1152
  const counts = await db('posts')
1120
1153
  .select('user_id')
1121
1154
  .count('* as count')
1122
1155
  .whereIn('user_id', userIds)
1123
1156
  .groupBy('user_id');
1124
-
1125
- const countMap = new Map(counts.map(c => [c.user_id, c.count]));
1126
-
1157
+
1158
+ const countMap = new Map(counts.map((c) => [c.user_id, c.count]));
1159
+
1127
1160
  for (const row of rows) {
1128
1161
  row.PostCount = countMap.get(row.id) || 0;
1129
1162
  }
1130
1163
  }
1131
-
1164
+
1132
1165
  // Custom relation from external API or different database
1133
1166
  async getExternalProfileRelation({ rows }) {
1134
1167
  for (const row of rows) {
@@ -1155,16 +1188,16 @@ POST /api/Users/crud
1155
1188
 
1156
1189
  **Custom Relation Hook Arguments:**
1157
1190
 
1158
- | Argument | Description |
1159
- |----------|-------------|
1160
- | `rows` | Parent rows to attach relation data to (modify in place) |
1161
- | `relName` | The relation name being fetched |
1162
- | `model` | Model definition object |
1163
- | `withTree` | Nested relations tree for further loading |
1164
- | `controller` | ControllerWrapper instance |
1165
- | `relation` | Relation definition from schema (may be undefined for custom relations) |
1166
- | `qb` | QueryBuilder instance for building queries |
1167
- | `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 |
1168
1201
 
1169
1202
  ### Important Notes
1170
1203
 
@@ -1187,13 +1220,13 @@ Create a model file at `models/Users.model.js`:
1187
1220
  class Users {
1188
1221
  // Enable soft delete for this model
1189
1222
  hasSoftDelete = true;
1190
-
1223
+
1191
1224
  // Optional: Custom soft delete hook
1192
1225
  beforeDelete({ model, action, request, context, db, utils, controller }) {
1193
1226
  // Custom logic before soft delete
1194
1227
  console.log('Soft deleting user:', request.where);
1195
1228
  }
1196
-
1229
+
1197
1230
  // Optional: Custom hook after soft delete
1198
1231
  afterDelete({ model, action, data, request, context, db, utils, controller }) {
1199
1232
  // Custom logic after soft delete
@@ -1244,15 +1277,13 @@ class Users {
1244
1277
  throw new Error('Email is required');
1245
1278
  }
1246
1279
  // Check if email already exists
1247
- const existing = await db('users')
1248
- .where('email', request.data.email)
1249
- .first();
1280
+ const existing = await db('users').where('email', request.data.email).first();
1250
1281
  if (existing && existing.id !== request.where?.id) {
1251
1282
  throw new Error('Email already exists');
1252
1283
  }
1253
1284
  }
1254
1285
  }
1255
-
1286
+
1256
1287
  // Before hooks - run before the action executes
1257
1288
  // Method naming: before{Action} (e.g., beforeCreate, beforeUpdate, beforeList, beforeDelete)
1258
1289
  async beforeCreate({ model, action, request, context, db, utils, controller }) {
@@ -1261,18 +1292,18 @@ class Users {
1261
1292
  request.data.updated_at = new Date();
1262
1293
  return request.data;
1263
1294
  }
1264
-
1295
+
1265
1296
  async beforeUpdate({ model, action, request, context, db, utils, controller }) {
1266
1297
  // Modify request data before update
1267
1298
  request.data.updated_at = new Date();
1268
1299
  return request.data;
1269
1300
  }
1270
-
1301
+
1271
1302
  async beforeDelete({ model, action, request, context, db, utils, controller }) {
1272
1303
  // Logic before delete (works with both hard and soft delete)
1273
1304
  console.log('Deleting user:', request.where);
1274
1305
  }
1275
-
1306
+
1276
1307
  // After hooks - run after the action executes
1277
1308
  // Method naming: after{Action} (e.g., afterCreate, afterUpdate, afterList, afterDelete)
1278
1309
  async afterCreate({ model, action, data, request, context, db, utils, controller }) {
@@ -1281,17 +1312,17 @@ class Users {
1281
1312
  // Send welcome email, trigger notifications, etc.
1282
1313
  return data;
1283
1314
  }
1284
-
1315
+
1285
1316
  async afterUpdate({ model, action, data, request, context, db, utils, controller }) {
1286
1317
  console.log('User updated:', data);
1287
1318
  return data;
1288
1319
  }
1289
-
1320
+
1290
1321
  async afterList({ model, action, data, request, context, db, utils, controller }) {
1291
1322
  // Modify list results before returning
1292
1323
  return data;
1293
1324
  }
1294
-
1325
+
1295
1326
  // Custom action hooks
1296
1327
  // Method naming: on{Action}Action (e.g., onActivateAction, onDeactivateAction)
1297
1328
  async onActivateAction({ model, action, request, context, db, utils, controller }) {
@@ -1299,7 +1330,7 @@ class Users {
1299
1330
  .where(request.where)
1300
1331
  .update({ is_active: true, updated_at: new Date() });
1301
1332
  }
1302
-
1333
+
1303
1334
  async onDeactivateAction({ model, action, request, context, db, utils, controller }) {
1304
1335
  return await db('users')
1305
1336
  .where(request.where)
@@ -1312,29 +1343,30 @@ module.exports = Users;
1312
1343
 
1313
1344
  ### Hook Arguments Reference
1314
1345
 
1315
- | Argument | Description |
1316
- |----------|-------------|
1317
- | `model` | Model definition object with table, columns, relations |
1318
- | `action` | Current action being performed (create, update, delete, etc.) |
1319
- | `request` | The request object containing where, data, etc. |
1320
- | `context` | Custom context passed from the controller |
1321
- | `db` | Knex database instance for direct queries |
1322
- | `utils` | Utility functions |
1323
- | `controller` | ControllerWrapper instance |
1324
- | `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 |
1325
1356
 
1326
1357
  ### Complete Hook Types Reference
1327
1358
 
1328
- | Hook Type | Method Naming | When Called | Use Case |
1329
- |-----------|---------------|-------------|----------|
1330
- | Validate | `validate` | Before any action | Input validation, authorization |
1331
- | Before | `before{Action}` | Before action executes | Modify request data, add timestamps |
1332
- | After | `after{Action}` | After action executes | Transform results, trigger side effects |
1333
- | Custom Action | `on{Action}Action` | For custom actions | Implement business logic |
1334
- | Soft Delete | `hasSoftDelete = true` | During delete/list | Enable soft delete |
1335
- | 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 |
1336
1367
 
1337
1368
  **Available Before/After hooks:**
1369
+
1338
1370
  - `beforeCreate` / `afterCreate`
1339
1371
  - `beforeUpdate` / `afterUpdate`
1340
1372
  - `beforeDelete` / `afterDelete`
@@ -1357,7 +1389,7 @@ POST /api/Users/crud
1357
1389
 
1358
1390
  POST /api/Users/crud
1359
1391
  {
1360
- "action": "deactivate",
1392
+ "action": "deactivate",
1361
1393
  "where": { "id": 1 }
1362
1394
  }
1363
1395
 
@@ -1384,7 +1416,7 @@ const userValidationRules = {
1384
1416
  first_name: 'required|type:string|maxLen:100',
1385
1417
  last_name: 'required|type:string|maxLen:100',
1386
1418
  age: 'type:number|min:0|max:150',
1387
- is_active: 'type:boolean'
1419
+ is_active: 'type:boolean',
1388
1420
  };
1389
1421
 
1390
1422
  // Validate and create user
@@ -1392,30 +1424,33 @@ app.post('/api/users/validate', async (req, res) => {
1392
1424
  try {
1393
1425
  // Validate request data
1394
1426
  const validatedData = await validate(req.body, userValidationRules);
1395
-
1396
- const result = await korm.processRequest({
1397
- action: 'create',
1398
- data: validatedData
1399
- }, 'Users');
1400
-
1427
+
1428
+ const result = await korm.processRequest(
1429
+ {
1430
+ action: 'create',
1431
+ data: validatedData,
1432
+ },
1433
+ 'Users'
1434
+ );
1435
+
1401
1436
  res.status(201).json({
1402
1437
  success: true,
1403
1438
  message: 'User created successfully',
1404
- data: result
1439
+ data: result,
1405
1440
  });
1406
1441
  } catch (error) {
1407
1442
  if (error.name === 'ValidationError') {
1408
1443
  return res.status(400).json({
1409
1444
  success: false,
1410
1445
  message: 'Validation failed',
1411
- errors: error.message
1446
+ errors: error.message,
1412
1447
  });
1413
1448
  }
1414
-
1449
+
1415
1450
  res.status(500).json({
1416
1451
  success: false,
1417
1452
  message: 'Error creating user',
1418
- error: error.message
1453
+ error: error.message,
1419
1454
  });
1420
1455
  }
1421
1456
  });
@@ -1433,13 +1468,13 @@ const advancedRules = {
1433
1468
  phone: 'regex:phone',
1434
1469
  password: 'required|type:string|minLen:8',
1435
1470
  status: 'in:active,inactive,pending',
1436
- user_id: 'exists:users,id'
1471
+ user_id: 'exists:users,id',
1437
1472
  };
1438
1473
 
1439
1474
  const customRegex = {
1440
1475
  username: /^[a-zA-Z0-9_]+$/,
1441
1476
  email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
1442
- phone: /^\+?[\d\s-()]{10,15}$/
1477
+ phone: /^\+?[\d\s-()]{10,15}$/,
1443
1478
  };
1444
1479
 
1445
1480
  const validatedData = await validate(data, advancedRules, { customRegex });
@@ -1530,20 +1565,21 @@ type|modifier1|modifier2|...
1530
1565
 
1531
1566
  **Column Modifiers:**
1532
1567
 
1533
- | Modifier | Description | Example |
1534
- |----------|-------------|---------|
1535
- | `size:n` | Column size | `varchar|size:255` |
1536
- | `unsigned` | Unsigned integer | `int|unsigned` |
1537
- | `primaryKey` | Primary key column | `bigint|primaryKey` |
1538
- | `autoIncrement` | Auto increment | `bigint|primaryKey|autoIncrement` |
1539
- | `notNull` | Not nullable | `varchar|size:255|notNull` |
1540
- | `unique` | Unique constraint | `varchar|unique` |
1541
- | `default:value` | Default value | `tinyint|default:1` |
1542
- | `onUpdate:value` | On update value | `timestamp|onUpdate:CURRENT_TIMESTAMP` |
1543
- | `comment:text` | Column comment | `varchar|comment:User email address` |
1544
- | `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` |
1545
1580
 
1546
1581
  **Special Default Values:**
1582
+
1547
1583
  - `now` or `now()` → `CURRENT_TIMESTAMP`
1548
1584
 
1549
1585
  **Example Column Definitions:**
@@ -1591,11 +1627,11 @@ Seed data is automatically inserted when `syncDatabase()` is called and the tabl
1591
1627
  const { initializeKORM } = require('@dreamtree-org/korm-js');
1592
1628
 
1593
1629
  const korm = initializeKORM({
1594
- db: db, // Knex database instance
1595
- dbClient: 'mysql', // 'mysql', 'mysql2', 'pg', 'postgresql', 'sqlite', 'sqlite3'
1596
- schema: null, // Optional: initial schema object
1597
- resolverPath: null, // Optional: path to models directory (default: process.cwd())
1598
- 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)
1599
1635
  });
1600
1636
 
1601
1637
  // Process any CRUD request (automatically handles other_requests if present)
@@ -1622,65 +1658,66 @@ const modelInstance = korm.getModelInstance(modelDef);
1622
1658
 
1623
1659
  ### ProcessRequest Options
1624
1660
 
1625
- | Parameter | Type | Description |
1626
- |-----------|------|-------------|
1627
- | `action` | string | Action to perform (list, show, create, update, delete, count, replace, upsert, sync) |
1628
- | `where` | object/array | Filter conditions |
1629
- | `data` | object/array | Data for create/update operations |
1630
- | `select` | array/string | Columns to select |
1631
- | `orderBy` | object/array/string | Sorting configuration |
1632
- | `limit` | number | Maximum records to return |
1633
- | `offset` | number | Records to skip |
1634
- | `page` | number | Page number (alternative to offset) |
1635
- | `with` | array | Related models to eager load |
1636
- | `withWhere` | object | Filter conditions for eager loaded relations |
1637
- | `groupBy` | array/string | Group by columns |
1638
- | `having` | object | Having conditions |
1639
- | `distinct` | boolean/array/string | Distinct results |
1640
- | `join` | object/array | Join configuration |
1641
- | `leftJoin` | object/array | Left join configuration |
1642
- | `rightJoin` | object/array | Right join configuration |
1643
- | `innerJoin` | object/array | Inner join configuration |
1644
- | `conflict` | array | Conflict columns for upsert |
1645
- | `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 |
1646
1682
 
1647
1683
  ### ProcessRequest Actions Summary
1648
1684
 
1649
- | Action | Description | Required Fields |
1650
- |--------|-------------|----------------|
1651
- | `list` | Get multiple records | None (optional: `where`, `select`, `orderBy`, `limit`, `offset`) |
1652
- | `show` | Get single record | `where` |
1653
- | `create` | Create new record | `data` |
1654
- | `update` | Update record(s) | `where`, `data` |
1655
- | `delete` | Delete record(s) | `where` |
1656
- | `count` | Count records | None (optional: `where`) |
1657
- | `sum` | Sum column or expression | `data.sumColumn` or `data.sumFormula` (optional: `where`) |
1658
- | `replace` | Replace record (MySQL) | `data` |
1659
- | `upsert` | Insert or update | `data`, `conflict` |
1660
- | `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` |
1661
1697
 
1662
1698
  ### Validation Rules
1663
1699
 
1664
- | Rule | Description | Example |
1665
- |------|-------------|---------|
1666
- | `required` | Field is required | `'required'` |
1667
- | `type:string` | Field must be string | `'type:string'` |
1668
- | `type:number` | Field must be number | `'type:number'` |
1669
- | `type:boolean` | Field must be boolean | `'type:boolean'` |
1670
- | `type:array` | Field must be array | `'type:array'` |
1671
- | `type:object` | Field must be object | `'type:object'` |
1672
- | `type:longText` | Field must be string > 255 chars | `'type:longText'` |
1673
- | `minLen:n` | Minimum string/array length | `'minLen:3'` |
1674
- | `maxLen:n` | Maximum string/array length | `'maxLen:255'` |
1675
- | `min:n` | Minimum numeric value | `'min:0'` |
1676
- | `max:n` | Maximum numeric value | `'max:150'` |
1677
- | `in:val1,val2` | Value must be in list | `'in:active,inactive,pending'` |
1678
- | `regex:name` | Custom regex pattern (define in options) | `'regex:email'` |
1679
- | `call:name` | Custom callback function (define in options) | `'call:myValidator'` |
1680
- | `exists:table,column` | Value must exist in database table | `'exists:users,id'` |
1681
- | `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'` |
1682
1718
 
1683
1719
  **Rule Chaining:** Combine multiple rules with `|` pipe character:
1720
+
1684
1721
  ```javascript
1685
1722
  {
1686
1723
  username: 'required|type:string|minLen:3|maxLen:50',
@@ -1693,14 +1730,14 @@ const modelInstance = korm.getModelInstance(modelDef);
1693
1730
  ### Library Exports
1694
1731
 
1695
1732
  ```javascript
1696
- const {
1697
- initializeKORM, // Initialize KORM with database connection
1698
- helperUtility, // Utility functions (file operations, string manipulation)
1699
- emitter, // Event emitter instance
1700
- validate, // Validation function
1701
- logger, // Logger utility instance
1702
- lib, // Additional utilities
1703
- 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)
1704
1741
  } = require('@dreamtree-org/korm-js');
1705
1742
 
1706
1743
  // lib contains:
@@ -1736,14 +1773,14 @@ logger.error('Error message');
1736
1773
 
1737
1774
  ### Log Levels
1738
1775
 
1739
- | Level | Value | Description |
1740
- |-------|-------|-------------|
1741
- | `NONE` | 0 | Disable all logging |
1742
- | `ERROR` | 1 | Only errors |
1743
- | `WARN` | 2 | Warnings and errors (default) |
1744
- | `INFO` | 3 | Info, warnings, and errors |
1745
- | `LOG` | 4 | General logs and above |
1746
- | `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) |
1747
1784
 
1748
1785
  ### Configuration
1749
1786
 
@@ -1751,14 +1788,14 @@ logger.error('Error message');
1751
1788
  const { logger } = require('@dreamtree-org/korm-js');
1752
1789
 
1753
1790
  // Set log level programmatically
1754
- logger.setLevel('debug'); // Show all logs
1755
- logger.setLevel('warn'); // Only warnings and errors (default)
1756
- logger.setLevel('error'); // Only errors
1757
- 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
1758
1795
 
1759
1796
  // Enable/disable logging
1760
- logger.disable(); // Temporarily disable all logging
1761
- logger.enable(); // Re-enable logging
1797
+ logger.disable(); // Temporarily disable all logging
1798
+ logger.enable(); // Re-enable logging
1762
1799
  ```
1763
1800
 
1764
1801
  ### Environment Variable
@@ -1787,10 +1824,10 @@ const { logger } = require('@dreamtree-org/korm-js');
1787
1824
 
1788
1825
  // Create a child logger for a specific module
1789
1826
  const authLogger = logger.child('[Auth]');
1790
- 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
1791
1828
 
1792
1829
  const dbLogger = logger.child('[DB]');
1793
- dbLogger.debug('Query executed'); // Output: [KORM][DB] [DEBUG] Query executed
1830
+ dbLogger.debug('Query executed'); // Output: [KORM][DB] [DEBUG] Query executed
1794
1831
  ```
1795
1832
 
1796
1833
  ### Logger Options
@@ -1800,10 +1837,10 @@ const { Logger } = require('@dreamtree-org/korm-js').logger;
1800
1837
 
1801
1838
  // Create a custom logger instance
1802
1839
  const customLogger = new Logger({
1803
- prefix: '[MyApp]', // Custom prefix (default: '[KORM]')
1804
- enabled: true, // Enable/disable logging (default: true)
1805
- level: 'debug', // Log level (default: 'warn' or KORM_LOG_LEVEL)
1806
- 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)
1807
1844
  });
1808
1845
 
1809
1846
  customLogger.info('Application started');
@@ -1841,7 +1878,7 @@ const { initializeKORM } = require('@dreamtree-org/korm-js');
1841
1878
  const korm = initializeKORM({
1842
1879
  db: db,
1843
1880
  dbClient: 'mysql',
1844
- debug: true // Enable SQL debugging
1881
+ debug: true, // Enable SQL debugging
1845
1882
  });
1846
1883
  ```
1847
1884
 
@@ -1883,10 +1920,10 @@ POST /api/Post/crud
1883
1920
 
1884
1921
  ### What's Included in sqlDebug
1885
1922
 
1886
- | Index | Query Type | Description |
1887
- |-------|------------|-------------|
1888
- | 0 | Main Query | The primary SELECT query with all WHERE, ORDER BY, LIMIT clauses |
1889
- | 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) |
1890
1927
 
1891
1928
  ### Debug Mode Best Practices
1892
1929
 
@@ -1895,14 +1932,14 @@ POST /api/Post/crud
1895
1932
  const korm = initializeKORM({
1896
1933
  db: db,
1897
1934
  dbClient: 'mysql',
1898
- debug: process.env.NODE_ENV === 'development'
1935
+ debug: process.env.NODE_ENV === 'development',
1899
1936
  });
1900
1937
 
1901
1938
  // Or use environment variable
1902
1939
  const korm = initializeKORM({
1903
1940
  db: db,
1904
1941
  dbClient: 'mysql',
1905
- debug: process.env.KORM_DEBUG === 'true'
1942
+ debug: process.env.KORM_DEBUG === 'true',
1906
1943
  });
1907
1944
  ```
1908
1945
 
@@ -1922,14 +1959,14 @@ const db = knex({
1922
1959
  user: process.env.DB_USER || 'root',
1923
1960
  password: process.env.DB_PASS || 'password',
1924
1961
  database: process.env.DB_NAME || 'database_name',
1925
- port: process.env.DB_PORT || 3306
1926
- }
1962
+ port: process.env.DB_PORT || 3306,
1963
+ },
1927
1964
  });
1928
1965
 
1929
1966
  const korm = initializeKORM({
1930
1967
  db: db,
1931
1968
  dbClient: 'mysql',
1932
- debug: process.env.NODE_ENV === 'development' // Enable SQL debugging in development
1969
+ debug: process.env.NODE_ENV === 'development', // Enable SQL debugging in development
1933
1970
  });
1934
1971
  ```
1935
1972
 
@@ -1945,14 +1982,14 @@ const db = knex({
1945
1982
  user: process.env.DB_USER || 'username',
1946
1983
  password: process.env.DB_PASS || 'password',
1947
1984
  database: process.env.DB_NAME || 'database_name',
1948
- port: process.env.DB_PORT || 5432
1949
- }
1985
+ port: process.env.DB_PORT || 5432,
1986
+ },
1950
1987
  });
1951
1988
 
1952
1989
  const korm = initializeKORM({
1953
1990
  db: db,
1954
1991
  dbClient: 'pg',
1955
- debug: process.env.NODE_ENV === 'development'
1992
+ debug: process.env.NODE_ENV === 'development',
1956
1993
  });
1957
1994
  ```
1958
1995
 
@@ -1964,14 +2001,14 @@ const knex = require('knex');
1964
2001
  const db = knex({
1965
2002
  client: 'sqlite3',
1966
2003
  connection: {
1967
- filename: process.env.DB_FILE || './database.sqlite'
1968
- }
2004
+ filename: process.env.DB_FILE || './database.sqlite',
2005
+ },
1969
2006
  });
1970
2007
 
1971
2008
  const korm = initializeKORM({
1972
2009
  db: db,
1973
2010
  dbClient: 'sqlite',
1974
- debug: process.env.NODE_ENV === 'development'
2011
+ debug: process.env.NODE_ENV === 'development',
1975
2012
  });
1976
2013
  ```
1977
2014
 
@@ -1981,12 +2018,12 @@ const korm = initializeKORM({
1981
2018
  // Global error handler
1982
2019
  app.use((error, req, res, next) => {
1983
2020
  console.error('KORM Error:', error);
1984
-
2021
+
1985
2022
  res.status(error.status || 500).json({
1986
2023
  success: false,
1987
2024
  message: 'Internal server error',
1988
2025
  error: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong',
1989
- stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
2026
+ stack: process.env.NODE_ENV === 'development' ? error.stack : undefined,
1990
2027
  });
1991
2028
  });
1992
2029
 
@@ -2002,22 +2039,22 @@ app.post('/api/:model/crud', async (req, res) => {
2002
2039
  return res.status(400).json({
2003
2040
  success: false,
2004
2041
  message: 'Validation failed',
2005
- errors: error.message
2042
+ errors: error.message,
2006
2043
  });
2007
2044
  }
2008
-
2045
+
2009
2046
  // Handle not found errors
2010
2047
  if (error.message.includes('not found')) {
2011
2048
  return res.status(404).json({
2012
2049
  success: false,
2013
- message: error.message
2050
+ message: error.message,
2014
2051
  });
2015
2052
  }
2016
-
2053
+
2017
2054
  // Handle other errors
2018
2055
  res.status(400).json({
2019
2056
  success: false,
2020
- message: error.message
2057
+ message: error.message,
2021
2058
  });
2022
2059
  }
2023
2060
  });
@@ -2042,15 +2079,15 @@ const db = knex({
2042
2079
  user: process.env.DB_USER || 'root',
2043
2080
  password: process.env.DB_PASS || 'password',
2044
2081
  database: process.env.DB_NAME || 'my_database',
2045
- port: process.env.DB_PORT || 3306
2046
- }
2082
+ port: process.env.DB_PORT || 3306,
2083
+ },
2047
2084
  });
2048
2085
 
2049
2086
  // Initialize KORM
2050
2087
  const korm = initializeKORM({
2051
2088
  db: db,
2052
2089
  dbClient: 'mysql',
2053
- debug: process.env.NODE_ENV === 'development' // SQL debugging in dev mode
2090
+ debug: process.env.NODE_ENV === 'development', // SQL debugging in dev mode
2054
2091
  });
2055
2092
 
2056
2093
  // Initialize app
@@ -2065,7 +2102,7 @@ async function initApp() {
2065
2102
  helperUtility.file.createDirectory('schema');
2066
2103
  helperUtility.file.writeJSON('schema/schema.json', schema);
2067
2104
  }
2068
-
2105
+
2069
2106
  // Sync database
2070
2107
  await korm.syncDatabase();
2071
2108
  console.log('✅ Database synced');