@dreamtree-org/korm-js 1.0.45 → 1.0.46

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.
Files changed (2) hide show
  1. package/README.md +211 -83
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -156,7 +156,7 @@ POST /api/Users/crud
156
156
  "action": "list",
157
157
  "where": { "is_active": true },
158
158
  "select": ["id", "username", "email", "first_name", "last_name"],
159
- "sort": [["created_at", "desc"]],
159
+ "orderBy": { "column": "created_at", "direction": "desc" },
160
160
  "limit": 10
161
161
  }
162
162
 
@@ -165,7 +165,7 @@ POST /api/Users/crud
165
165
  {
166
166
  "action": "list",
167
167
  "select": ["id", "username", "email"],
168
- "sort": [["created_at", "desc"]],
168
+ "orderBy": { "column": "created_at", "direction": "desc" },
169
169
  "limit": 10,
170
170
  "offset": 20
171
171
  }
@@ -181,7 +181,10 @@ POST /api/Users/crud
181
181
  "is_active": true
182
182
  },
183
183
  "select": ["id", "username", "email", "first_name", "last_name"],
184
- "sort": [["created_at", "desc"], ["last_name", "asc"]],
184
+ "orderBy": [
185
+ { "column": "created_at", "direction": "desc" },
186
+ { "column": "last_name", "direction": "asc" }
187
+ ],
185
188
  "limit": 10
186
189
  }
187
190
 
@@ -584,63 +587,98 @@ POST /api/Users/crud
584
587
  // AND role IN ('admin', 'moderator', 'editor') AND score BETWEEN 50 AND 100
585
588
  ```
586
589
 
587
- ### Sorting
590
+ ### Sorting (orderBy)
588
591
 
589
592
  ```javascript
590
- // Single sort
593
+ // Single sort with object
591
594
  POST /api/Users/crud
592
595
  {
593
596
  "action": "list",
594
- "sort": [["created_at", "desc"]]
597
+ "orderBy": { "column": "created_at", "direction": "desc" }
595
598
  }
596
599
 
597
- // Multiple sorts
600
+ // Single sort with string (default: ascending)
598
601
  POST /api/Users/crud
599
602
  {
600
603
  "action": "list",
601
- "sort": [
602
- ["is_active", "desc"],
603
- ["created_at", "desc"],
604
- ["last_name", "asc"]
604
+ "orderBy": "created_at"
605
+ }
606
+
607
+ // Multiple sorts with array of objects
608
+ POST /api/Users/crud
609
+ {
610
+ "action": "list",
611
+ "orderBy": [
612
+ { "column": "is_active", "direction": "desc" },
613
+ { "column": "created_at", "direction": "desc" },
614
+ { "column": "last_name", "direction": "asc" }
605
615
  ]
606
616
  }
617
+
618
+ // Multiple sorts with array of strings (all ascending)
619
+ POST /api/Users/crud
620
+ {
621
+ "action": "list",
622
+ "orderBy": ["is_active", "created_at", "last_name"]
623
+ }
607
624
  ```
608
625
 
609
626
  ### Joins
610
627
 
611
628
  ```javascript
612
- // Inner join
629
+ // Inner join (using innerJoin parameter)
613
630
  POST /api/Users/crud
614
631
  {
615
632
  "action": "list",
616
- "select": ["users.id", "users.username", "profiles.bio"],
617
- "join": [
618
- {
619
- "table": "user_profiles",
620
- "as": "profiles",
621
- "on": "users.id = profiles.user_id",
622
- "type": "inner"
623
- }
624
- ],
633
+ "select": ["users.id", "users.username", "user_profiles.bio"],
634
+ "innerJoin": {
635
+ "table": "user_profiles",
636
+ "on": "users.id = user_profiles.user_id"
637
+ },
625
638
  "where": { "users.is_active": true }
626
639
  }
627
640
 
628
- // Left join
641
+ // Left join (using leftJoin parameter)
629
642
  POST /api/Users/crud
630
643
  {
631
644
  "action": "list",
632
- "select": ["users.*", "profiles.bio"],
633
- "join": [
634
- {
635
- "table": "user_profiles",
636
- "as": "profiles",
637
- "on": "users.id = profiles.user_id",
638
- "type": "left"
639
- }
645
+ "select": ["users.*", "user_profiles.bio"],
646
+ "leftJoin": {
647
+ "table": "user_profiles",
648
+ "on": "users.id = user_profiles.user_id"
649
+ }
650
+ }
651
+
652
+ // Multiple joins (array format)
653
+ POST /api/Users/crud
654
+ {
655
+ "action": "list",
656
+ "select": ["users.*", "profiles.bio", "roles.name"],
657
+ "leftJoin": [
658
+ { "table": "user_profiles", "on": "users.id = user_profiles.user_id" },
659
+ { "table": "roles", "on": "users.role_id = roles.id" }
640
660
  ]
641
661
  }
662
+
663
+ // Join with explicit columns
664
+ POST /api/Users/crud
665
+ {
666
+ "action": "list",
667
+ "innerJoin": {
668
+ "table": "orders",
669
+ "first": "users.id",
670
+ "operator": "=",
671
+ "second": "orders.user_id"
672
+ }
673
+ }
642
674
  ```
643
675
 
676
+ **Available join types:**
677
+ - `join` - Regular join
678
+ - `innerJoin` - Inner join
679
+ - `leftJoin` - Left join
680
+ - `rightJoin` - Right join
681
+
644
682
  ## Relational Support with hasRelations
645
683
 
646
684
  ### Understanding hasRelations Structure
@@ -900,6 +938,9 @@ Create a model file at `models/Users.model.js`:
900
938
 
901
939
  ```javascript
902
940
  class Users {
941
+ // Soft delete support (property, not method)
942
+ hasSoftDelete = true;
943
+
903
944
  // Validation hook - runs before any action
904
945
  async validate({ model, action, request, context, db, utils, controller }) {
905
946
  if (action === 'create' || action === 'update') {
@@ -916,61 +957,91 @@ class Users {
916
957
  }
917
958
  }
918
959
 
919
- // Before hook - runs before the action executes
960
+ // Before hooks - run before the action executes
961
+ // Method naming: before{Action} (e.g., beforeCreate, beforeUpdate, beforeList, beforeDelete)
920
962
  async beforeCreate({ model, action, request, context, db, utils, controller }) {
921
- // Add timestamps
963
+ // Modify request data before insert
922
964
  request.data.created_at = new Date();
923
965
  request.data.updated_at = new Date();
924
966
  return request.data;
925
967
  }
926
968
 
927
969
  async beforeUpdate({ model, action, request, context, db, utils, controller }) {
928
- // Update timestamp
970
+ // Modify request data before update
929
971
  request.data.updated_at = new Date();
930
972
  return request.data;
931
973
  }
932
974
 
933
- // After hook - runs after the action executes
975
+ async beforeDelete({ model, action, request, context, db, utils, controller }) {
976
+ // Logic before delete (works with both hard and soft delete)
977
+ console.log('Deleting user:', request.where);
978
+ }
979
+
980
+ // After hooks - run after the action executes
981
+ // Method naming: after{Action} (e.g., afterCreate, afterUpdate, afterList, afterDelete)
934
982
  async afterCreate({ model, action, data, request, context, db, utils, controller }) {
935
- // Log creation
983
+ // data contains the result of the action
936
984
  console.log('User created:', data);
937
- // Send welcome email, etc.
985
+ // Send welcome email, trigger notifications, etc.
938
986
  return data;
939
987
  }
940
988
 
941
989
  async afterUpdate({ model, action, data, request, context, db, utils, controller }) {
942
- // Log update
943
990
  console.log('User updated:', data);
944
991
  return data;
945
992
  }
946
993
 
947
- // Custom action hook
948
- async onCustomAction({ model, action, request, context, db, utils, controller }) {
949
- // Handle custom actions like 'activate', 'deactivate', etc.
950
- if (action === 'activate') {
951
- return await db('users')
952
- .where(request.where)
953
- .update({ is_active: true, updated_at: new Date() });
954
- }
955
- throw new Error(`Unknown custom action: ${action}`);
994
+ async afterList({ model, action, data, request, context, db, utils, controller }) {
995
+ // Modify list results before returning
996
+ return data;
956
997
  }
957
998
 
958
- // Soft delete support
959
- hasSoftDelete = true;
999
+ // Custom action hooks
1000
+ // Method naming: on{Action}Action (e.g., onActivateAction, onDeactivateAction)
1001
+ async onActivateAction({ model, action, request, context, db, utils, controller }) {
1002
+ return await db('users')
1003
+ .where(request.where)
1004
+ .update({ is_active: true, updated_at: new Date() });
1005
+ }
1006
+
1007
+ async onDeactivateAction({ model, action, request, context, db, utils, controller }) {
1008
+ return await db('users')
1009
+ .where(request.where)
1010
+ .update({ is_active: false, updated_at: new Date() });
1011
+ }
960
1012
  }
961
1013
 
962
1014
  module.exports = Users;
963
1015
  ```
964
1016
 
1017
+ ### Hook Arguments Reference
1018
+
1019
+ | Argument | Description |
1020
+ |----------|-------------|
1021
+ | `model` | Model definition object with table, columns, relations |
1022
+ | `action` | Current action being performed (create, update, delete, etc.) |
1023
+ | `request` | The request object containing where, data, etc. |
1024
+ | `context` | Custom context passed from the controller |
1025
+ | `db` | Knex database instance for direct queries |
1026
+ | `utils` | Utility functions |
1027
+ | `controller` | ControllerWrapper instance |
1028
+ | `data` | (After hooks only) Result of the action |
1029
+
965
1030
  ### Using Custom Actions
966
1031
 
967
1032
  ```javascript
968
- // Call custom action
1033
+ // Call custom action - triggers on{Action}Action hook
969
1034
  POST /api/Users/crud
970
1035
  {
971
1036
  "action": "activate",
972
1037
  "where": { "id": 1 }
973
1038
  }
1039
+
1040
+ POST /api/Users/crud
1041
+ {
1042
+ "action": "deactivate",
1043
+ "where": { "id": 1 }
1044
+ }
974
1045
  ```
975
1046
 
976
1047
  ## Data Validation
@@ -1128,49 +1199,67 @@ POST /api/Users/crud
1128
1199
  ### KORM Instance Methods
1129
1200
 
1130
1201
  ```javascript
1202
+ const { initializeKORM } = require('@dreamtree-org/korm-js');
1203
+
1131
1204
  const korm = initializeKORM({
1132
- db: db,
1133
- dbClient: 'mysql' // or 'pg', 'sqlite'
1205
+ db: db, // Knex database instance
1206
+ dbClient: 'mysql', // 'mysql', 'mysql2', 'pg', 'postgresql', 'sqlite', 'sqlite3'
1207
+ schema: null, // Optional: initial schema object
1208
+ resolverPath: null // Optional: path to models directory (default: process.cwd())
1134
1209
  });
1135
1210
 
1136
- // Process any CRUD request
1137
- const result = await korm.processRequest(requestBody, modelName);
1211
+ // Process any CRUD request (automatically handles other_requests if present)
1212
+ const result = await korm.processRequest(requestBody, modelName, context);
1138
1213
 
1139
- // Process request with nested requests
1140
- const result = await korm.processRequestWithOthers(requestBody, modelName);
1214
+ // Process request with nested requests (legacy - now same as processRequest)
1215
+ const result = await korm.processRequestWithOthers(requestBody, modelName, context);
1141
1216
 
1142
1217
  // Set schema manually
1143
1218
  korm.setSchema(schemaObject);
1144
1219
 
1145
- // Sync database with schema
1220
+ // Sync database with schema (creates/updates tables)
1146
1221
  await korm.syncDatabase();
1147
1222
 
1148
1223
  // Generate schema from existing database
1149
1224
  const schema = await korm.generateSchema();
1150
- ```
1151
-
1152
- ### ControllerWrapper Static Methods
1153
1225
 
1154
- ```javascript
1155
- const { ControllerWrapper } = require('@dreamtree-org/korm-js');
1156
-
1157
- // Load model class
1158
- const ModelClass = ControllerWrapper.loadModelClass('Users');
1226
+ // Load model class from models/{ModelName}.model.js
1227
+ const ModelClass = korm.loadModelClass('Users');
1159
1228
 
1160
1229
  // Get model instance
1161
- const modelInstance = ControllerWrapper.getModelInstance(model);
1162
-
1163
- // Generate schema
1164
- const schema = await ControllerWrapper.generateSchema();
1230
+ const modelInstance = korm.getModelInstance(modelDef);
1165
1231
  ```
1166
1232
 
1233
+ ### ProcessRequest Options
1234
+
1235
+ | Parameter | Type | Description |
1236
+ |-----------|------|-------------|
1237
+ | `action` | string | Action to perform (list, show, create, update, delete, count, replace, upsert, sync) |
1238
+ | `where` | object/array | Filter conditions |
1239
+ | `data` | object/array | Data for create/update operations |
1240
+ | `select` | array/string | Columns to select |
1241
+ | `orderBy` | object/array/string | Sorting configuration |
1242
+ | `limit` | number | Maximum records to return |
1243
+ | `offset` | number | Records to skip |
1244
+ | `page` | number | Page number (alternative to offset) |
1245
+ | `with` | array | Related models to eager load |
1246
+ | `groupBy` | array/string | Group by columns |
1247
+ | `having` | object | Having conditions |
1248
+ | `distinct` | boolean/array/string | Distinct results |
1249
+ | `join` | object/array | Join configuration |
1250
+ | `leftJoin` | object/array | Left join configuration |
1251
+ | `rightJoin` | object/array | Right join configuration |
1252
+ | `innerJoin` | object/array | Inner join configuration |
1253
+ | `conflict` | array | Conflict columns for upsert |
1254
+ | `other_requests` | object | Nested requests for related models |
1255
+
1167
1256
  ### ProcessRequest Actions Summary
1168
1257
 
1169
1258
  | Action | Description | Required Fields |
1170
1259
  |--------|-------------|----------------|
1171
- | `create` | Create new record | `data` |
1172
- | `list` | Get multiple records | None (optional: `where`, `select`, `sort`, `limit`, `offset`) |
1260
+ | `list` | Get multiple records | None (optional: `where`, `select`, `orderBy`, `limit`, `offset`) |
1173
1261
  | `show` | Get single record | `where` |
1262
+ | `create` | Create new record | `data` |
1174
1263
  | `update` | Update record(s) | `where`, `data` |
1175
1264
  | `delete` | Delete record(s) | `where` |
1176
1265
  | `count` | Count records | None (optional: `where`) |
@@ -1182,19 +1271,58 @@ const schema = await ControllerWrapper.generateSchema();
1182
1271
 
1183
1272
  | Rule | Description | Example |
1184
1273
  |------|-------------|---------|
1185
- | `required` | Field is required | `username: 'required'` |
1186
- | `type:string` | Field must be string | `name: 'type:string'` |
1187
- | `type:number` | Field must be number | `age: 'type:number'` |
1188
- | `type:boolean` | Field must be boolean | `is_active: 'type:boolean'` |
1189
- | `minLen:n` | Minimum length | `username: 'minLen:3'` |
1190
- | `maxLen:n` | Maximum length | `email: 'maxLen:255'` |
1191
- | `min:n` | Minimum value | `age: 'min:0'` |
1192
- | `max:n` | Maximum value | `age: 'max:150'` |
1193
- | `in:val1,val2` | Value must be in list | `status: 'in:active,inactive'` |
1194
- | `regex:name` | Custom regex pattern | `email: 'regex:email'` |
1195
- | `call:name` | Custom callback function | `field: 'call:myValidator'` |
1196
- | `exists:table,column` | Value must exist in table | `user_id: 'exists:users,id'` |
1197
- | `default:value` | Default value if not provided | `status: 'default:active'` |
1274
+ | `required` | Field is required | `'required'` |
1275
+ | `type:string` | Field must be string | `'type:string'` |
1276
+ | `type:number` | Field must be number | `'type:number'` |
1277
+ | `type:boolean` | Field must be boolean | `'type:boolean'` |
1278
+ | `type:array` | Field must be array | `'type:array'` |
1279
+ | `type:object` | Field must be object | `'type:object'` |
1280
+ | `type:longText` | Field must be string > 255 chars | `'type:longText'` |
1281
+ | `minLen:n` | Minimum string/array length | `'minLen:3'` |
1282
+ | `maxLen:n` | Maximum string/array length | `'maxLen:255'` |
1283
+ | `min:n` | Minimum numeric value | `'min:0'` |
1284
+ | `max:n` | Maximum numeric value | `'max:150'` |
1285
+ | `in:val1,val2` | Value must be in list | `'in:active,inactive,pending'` |
1286
+ | `regex:name` | Custom regex pattern (define in options) | `'regex:email'` |
1287
+ | `call:name` | Custom callback function (define in options) | `'call:myValidator'` |
1288
+ | `exists:table,column` | Value must exist in database table | `'exists:users,id'` |
1289
+ | `default:value` | Default value if not provided | `'default:active'` |
1290
+
1291
+ **Rule Chaining:** Combine multiple rules with `|` pipe character:
1292
+ ```javascript
1293
+ {
1294
+ username: 'required|type:string|minLen:3|maxLen:50',
1295
+ email: 'required|type:string|regex:email',
1296
+ age: 'type:number|min:0|max:150',
1297
+ status: 'in:active,inactive|default:active'
1298
+ }
1299
+ ```
1300
+
1301
+ ### Library Exports
1302
+
1303
+ ```javascript
1304
+ const {
1305
+ initializeKORM, // Initialize KORM with database connection
1306
+ helperUtility, // Utility functions (file operations, string manipulation)
1307
+ emitter, // Event emitter instance
1308
+ validate, // Validation function
1309
+ lib, // Additional utilities
1310
+ LibClasses // Library classes (Emitter)
1311
+ } = require('@dreamtree-org/korm-js');
1312
+
1313
+ // lib contains:
1314
+ // - createValidationMiddleware(rules, options) - Express middleware
1315
+ // - validateEmail(email) - Email validation
1316
+ // - validatePassword(password) - Password strength validation
1317
+ // - validatePhone(phone) - Phone number validation
1318
+ // - validatePAN(pan) - PAN validation (India)
1319
+ // - validateAadhaar(aadhaar) - Aadhaar validation (India)
1320
+
1321
+ // helperUtility.file contains:
1322
+ // - readJSON(path) - Read JSON file
1323
+ // - writeJSON(path, data) - Write JSON file
1324
+ // - createDirectory(path) - Create directory
1325
+ ```
1198
1326
 
1199
1327
  ## Database Support
1200
1328
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamtree-org/korm-js",
3
- "version": "1.0.45",
3
+ "version": "1.0.46",
4
4
  "description": "Knowledge Object-Relational Mapping - A powerful, modular ORM system for Node.js with dynamic database operations, complex queries, relationships, and nested requests",
5
5
  "author": {
6
6
  "name": "Partha Preetham Krishna",