@dreamtree-org/korm-js 1.0.39 → 1.0.41

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 +945 -179
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,55 +1,58 @@
1
1
  # KORM-JS
2
2
 
3
- **Knowledge Object-Relational Mapping** - A powerful, modular ORM system for Node.js with dynamic database operations, complex queries, relationships, and nested requests.
3
+ **Knowledge Object-Relational Mapping** - A powerful, modular ORM system for Node.js with dynamic database operations, complex queries, relationships, nested requests, and soft delete support.
4
4
 
5
- [![npm version](https://badge.fury.io/js/korm-js.svg)](https://badge.fury.io/js/korm-js)
5
+ [![npm version](https://badge.fury.io/js/@dreamtree-org%2Fkorm-js.svg)](https://badge.fury.io/js/@dreamtree-org%2Fkorm-js)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
8
  ## Features
9
9
 
10
10
  - 🚀 **Multi-Database Support**: MySQL, PostgreSQL, SQLite
11
- - 🔧 **Dynamic Operations**: Create, Read, Update, Delete with ease
12
- - 🔍 **Advanced Querying**: Complex queries with relationships
11
+ - 🔧 **Dynamic Operations**: Create, Read, Update, Delete, Replace, Upsert, Sync
12
+ - 🔍 **Advanced Querying**: Complex queries with relationships, joins, and filtering
13
13
  - 🛡️ **Data Validation**: Built-in request validation with custom rules
14
14
  - 🔄 **Auto-Sync**: Automatic table schema synchronization
15
15
  - 📦 **Modular Design**: Use only what you need
16
16
  - 🎯 **Express.js Ready**: Perfect integration with Express applications
17
17
  - 🔄 **Schema Generation**: Auto-generate schemas from existing databases
18
+ - 🗑️ **Soft Delete**: Built-in soft delete support with automatic filtering
19
+ - 🎣 **Model Hooks**: Before, After, Validate, and Custom action hooks
18
20
 
19
21
  ## Installation
20
22
 
21
23
  ```bash
22
- npm install korm-js
24
+ npm install @dreamtree-org/korm-js
23
25
  ```
24
26
 
25
- ## Quick Start with Express.js and PostgreSQL
27
+ ## Quick Start with Express.js and MySQL
26
28
 
27
29
  ### 1. Basic Setup
28
30
 
29
31
  ```javascript
30
32
  require('dotenv').config();
31
33
  const express = require('express');
32
- const { initializeKORM, validate, helperUtility } = require('korm-js');
34
+ const { initializeKORM, validate, helperUtility } = require('@dreamtree-org/korm-js');
33
35
  const knex = require('knex');
34
36
 
35
37
  const app = express();
36
38
  app.use(express.json());
37
39
 
38
- // Database configuration
40
+ // MySQL database configuration
39
41
  const db = knex({
40
- client: 'pg',
42
+ client: 'mysql2',
41
43
  connection: {
42
44
  host: process.env.DB_HOST || 'localhost',
43
- user: process.env.DB_USER || 'your_user',
44
- password: process.env.DB_PASS || 'your_pass',
45
- database: process.env.DB_NAME || 'your_db'
45
+ user: process.env.DB_USER || 'root',
46
+ password: process.env.DB_PASS || 'password',
47
+ database: process.env.DB_NAME || 'my_database',
48
+ port: process.env.DB_PORT || 3306
46
49
  }
47
50
  });
48
51
 
49
- // Initialize KORM
52
+ // Initialize KORM with MySQL
50
53
  const korm = initializeKORM({
51
54
  db: db,
52
- dbClient: 'pg'
55
+ dbClient: 'mysql'
53
56
  });
54
57
 
55
58
  app.listen(3000, () => {
@@ -63,9 +66,11 @@ app.listen(3000, () => {
63
66
  async function initApp() {
64
67
  // Load existing schema if available
65
68
  let syncSchema = helperUtility.file.readJSON('schema/sync.json');
66
- korm.setSchema(syncSchema);
69
+ if (syncSchema) {
70
+ korm.setSchema(syncSchema);
71
+ }
67
72
 
68
- // Sync database with schema
73
+ // Sync database with schema (creates tables if they don't exist)
69
74
  await korm.syncDatabase().then(() => {
70
75
  console.log('✅ Database synced');
71
76
  });
@@ -103,7 +108,9 @@ app.get('/health', (req, res) => {
103
108
  });
104
109
  ```
105
110
 
106
- ### 4. Example API Usage
111
+ ## Complete CRUD Operations Guide
112
+
113
+ ### 1. Create Operation
107
114
 
108
115
  ```javascript
109
116
  // Create a new record
@@ -114,11 +121,36 @@ POST /api/Users/crud
114
121
  "username": "john_doe",
115
122
  "email": "john@example.com",
116
123
  "first_name": "John",
117
- "last_name": "Doe"
124
+ "last_name": "Doe",
125
+ "age": 30,
126
+ "is_active": true
118
127
  }
119
128
  }
120
129
 
121
- // Get all records
130
+ // Response
131
+ {
132
+ "message": "Record created successfully",
133
+ "data": [
134
+ {
135
+ "id": 1,
136
+ "username": "john_doe",
137
+ "email": "john@example.com",
138
+ "first_name": "John",
139
+ "last_name": "Doe",
140
+ "age": 30,
141
+ "is_active": true,
142
+ "created_at": "2024-01-01T00:00:00.000Z",
143
+ "updated_at": "2024-01-01T00:00:00.000Z"
144
+ }
145
+ ],
146
+ "success": true
147
+ }
148
+ ```
149
+
150
+ ### 2. List Operation (Read Multiple Records)
151
+
152
+ ```javascript
153
+ // Get all records with basic filtering
122
154
  POST /api/Users/crud
123
155
  {
124
156
  "action": "list",
@@ -128,103 +160,695 @@ POST /api/Users/crud
128
160
  "limit": 10
129
161
  }
130
162
 
131
- // Get single record
163
+ // List with pagination
164
+ POST /api/Users/crud
165
+ {
166
+ "action": "list",
167
+ "select": ["id", "username", "email"],
168
+ "sort": [["created_at", "desc"]],
169
+ "limit": 10,
170
+ "offset": 20
171
+ }
172
+
173
+ // List with complex conditions
174
+ POST /api/Users/crud
175
+ {
176
+ "action": "list",
177
+ "where": {
178
+ "first_name": { "like": "%John%" },
179
+ "email": { "like": "%@example.com" },
180
+ "age": { ">=": 18, "<=": 65 },
181
+ "is_active": true
182
+ },
183
+ "select": ["id", "username", "email", "first_name", "last_name"],
184
+ "sort": [["created_at", "desc"], ["last_name", "asc"]],
185
+ "limit": 10
186
+ }
187
+
188
+ // Response
189
+ {
190
+ "data": [
191
+ {
192
+ "id": 1,
193
+ "username": "john_doe",
194
+ "email": "john@example.com",
195
+ "first_name": "John",
196
+ "last_name": "Doe"
197
+ }
198
+ ],
199
+ "total": 1,
200
+ "page": 1,
201
+ "limit": 10
202
+ }
203
+ ```
204
+
205
+ ### 3. Show Operation (Read Single Record)
206
+
207
+ ```javascript
208
+ // Get single record by ID
132
209
  POST /api/Users/crud
133
210
  {
134
211
  "action": "show",
135
212
  "where": { "id": 1 }
136
213
  }
137
214
 
138
- // Update record
215
+ // Get single record with multiple conditions
216
+ POST /api/Users/crud
217
+ {
218
+ "action": "show",
219
+ "where": {
220
+ "username": "john_doe",
221
+ "is_active": true
222
+ }
223
+ }
224
+
225
+ // Response
226
+ {
227
+ "id": 1,
228
+ "username": "john_doe",
229
+ "email": "john@example.com",
230
+ "first_name": "John",
231
+ "last_name": "Doe",
232
+ "age": 30,
233
+ "is_active": true,
234
+ "created_at": "2024-01-01T00:00:00.000Z",
235
+ "updated_at": "2024-01-01T00:00:00.000Z"
236
+ }
237
+ ```
238
+
239
+ ### 4. Update Operation
240
+
241
+ ```javascript
242
+ // Update record by ID
139
243
  POST /api/Users/crud
140
244
  {
141
245
  "action": "update",
142
246
  "where": { "id": 1 },
143
247
  "data": {
144
248
  "first_name": "Jane",
145
- "updated_at": "2024-01-01T00:00:00Z"
249
+ "last_name": "Smith",
250
+ "updated_at": new Date().toISOString()
251
+ }
252
+ }
253
+
254
+ // Update multiple records
255
+ POST /api/Users/crud
256
+ {
257
+ "action": "update",
258
+ "where": { "is_active": false },
259
+ "data": {
260
+ "is_active": true,
261
+ "updated_at": new Date().toISOString()
262
+ }
263
+ }
264
+
265
+ // Response
266
+ {
267
+ "message": "Record updated successfully",
268
+ "data": [
269
+ {
270
+ "id": 1,
271
+ "first_name": "Jane",
272
+ "last_name": "Smith",
273
+ "updated_at": "2024-01-01T00:00:00.000Z"
274
+ }
275
+ ],
276
+ "success": true
277
+ }
278
+ ```
279
+
280
+ ### 5. Delete Operation
281
+
282
+ ```javascript
283
+ // Hard delete (permanent deletion)
284
+ POST /api/Users/crud
285
+ {
286
+ "action": "delete",
287
+ "where": { "id": 1 }
288
+ }
289
+
290
+ // Delete multiple records
291
+ POST /api/Users/crud
292
+ {
293
+ "action": "delete",
294
+ "where": { "is_active": false }
295
+ }
296
+
297
+ // Response
298
+ {
299
+ "message": "Record deleted successfully",
300
+ "data": [/* deleted records */],
301
+ "success": true
302
+ }
303
+ ```
304
+
305
+ ### 6. Count Operation
306
+
307
+ ```javascript
308
+ // Count all records
309
+ POST /api/Users/crud
310
+ {
311
+ "action": "count"
312
+ }
313
+
314
+ // Count with conditions
315
+ POST /api/Users/crud
316
+ {
317
+ "action": "count",
318
+ "where": {
319
+ "is_active": true,
320
+ "created_at": { ">=": "2024-01-01" }
321
+ }
322
+ }
323
+
324
+ // Response
325
+ 42 // Number of matching records
326
+ ```
327
+
328
+ ### 7. Replace Operation
329
+
330
+ ```javascript
331
+ // Replace record (MySQL specific - replaces entire row)
332
+ POST /api/Users/crud
333
+ {
334
+ "action": "replace",
335
+ "data": {
336
+ "id": 1,
337
+ "username": "john_doe_updated",
338
+ "email": "john.updated@example.com",
339
+ "first_name": "John",
340
+ "last_name": "Doe Updated"
341
+ }
342
+ }
343
+
344
+ // Response
345
+ {
346
+ "message": "Record replaced successfully",
347
+ "data": [/* replaced record */],
348
+ "success": true
349
+ }
350
+ ```
351
+
352
+ ### 8. Upsert Operation (Insert or Update)
353
+
354
+ ```javascript
355
+ // Upsert record (insert if not exists, update if exists)
356
+ POST /api/Users/crud
357
+ {
358
+ "action": "upsert",
359
+ "data": {
360
+ "username": "john_doe",
361
+ "email": "john@example.com",
362
+ "first_name": "John",
363
+ "last_name": "Doe"
364
+ },
365
+ "conflict": ["username"] // Conflict columns for MySQL
366
+ }
367
+
368
+ // Response
369
+ {
370
+ "message": "Record upserted successfully",
371
+ "data": [/* upserted record */],
372
+ "success": true
373
+ }
374
+ ```
375
+
376
+ ### 9. Sync Operation (Upsert + Delete)
377
+
378
+ ```javascript
379
+ // Sync operation: upsert records and delete others matching where clause
380
+ POST /api/Users/crud
381
+ {
382
+ "action": "sync",
383
+ "data": {
384
+ "username": "john_doe",
385
+ "email": "john@example.com",
386
+ "first_name": "John",
387
+ "last_name": "Doe"
388
+ },
389
+ "conflict": ["username"],
390
+ "where": {
391
+ "created_at": { "<": "2024-01-01" }
392
+ }
393
+ }
394
+
395
+ // Response
396
+ {
397
+ "message": "Record synced successfully",
398
+ "data": {
399
+ "insertOrUpdateQuery": [/* upserted records */],
400
+ "deleteQuery": [/* deleted records */]
401
+ },
402
+ "success": true
403
+ }
404
+ ```
405
+
406
+ ## Advanced Querying
407
+
408
+ ### Complex Where Conditions
409
+
410
+ ```javascript
411
+ // Multiple operators
412
+ POST /api/Users/crud
413
+ {
414
+ "action": "list",
415
+ "where": {
416
+ "age": { ">=": 18, "<=": 65 },
417
+ "email": { "like": "%@example.com" },
418
+ "first_name": { "in": ["John", "Jane", "Bob"] },
419
+ "is_active": true,
420
+ "created_at": { ">=": "2024-01-01", "<=": "2024-12-31" }
421
+ }
422
+ }
423
+
424
+ // OR conditions (using arrays)
425
+ POST /api/Users/crud
426
+ {
427
+ "action": "list",
428
+ "where": {
429
+ "or": [
430
+ { "first_name": "John" },
431
+ { "last_name": "Doe" }
432
+ ]
433
+ }
434
+ }
435
+
436
+ // NULL checks
437
+ POST /api/Users/crud
438
+ {
439
+ "action": "list",
440
+ "where": {
441
+ "deleted_at": null
442
+ }
443
+ }
444
+ ```
445
+
446
+ ### Sorting
447
+
448
+ ```javascript
449
+ // Single sort
450
+ POST /api/Users/crud
451
+ {
452
+ "action": "list",
453
+ "sort": [["created_at", "desc"]]
454
+ }
455
+
456
+ // Multiple sorts
457
+ POST /api/Users/crud
458
+ {
459
+ "action": "list",
460
+ "sort": [
461
+ ["is_active", "desc"],
462
+ ["created_at", "desc"],
463
+ ["last_name", "asc"]
464
+ ]
465
+ }
466
+ ```
467
+
468
+ ### Joins
469
+
470
+ ```javascript
471
+ // Inner join
472
+ POST /api/Users/crud
473
+ {
474
+ "action": "list",
475
+ "select": ["users.id", "users.username", "profiles.bio"],
476
+ "join": [
477
+ {
478
+ "table": "user_profiles",
479
+ "as": "profiles",
480
+ "on": "users.id = profiles.user_id",
481
+ "type": "inner"
482
+ }
483
+ ],
484
+ "where": { "users.is_active": true }
485
+ }
486
+
487
+ // Left join
488
+ POST /api/Users/crud
489
+ {
490
+ "action": "list",
491
+ "select": ["users.*", "profiles.bio"],
492
+ "join": [
493
+ {
494
+ "table": "user_profiles",
495
+ "as": "profiles",
496
+ "on": "users.id = profiles.user_id",
497
+ "type": "left"
498
+ }
499
+ ]
500
+ }
501
+ ```
502
+
503
+ ## Relational Support with hasRelations
504
+
505
+ ### Understanding hasRelations Structure
506
+
507
+ The `hasRelations` object in your schema defines relationships between models. Based on your actual schema file (`../borebell.in/schema/sync.json`), relationships use the following structure:
508
+
509
+ #### One-to-One or Belongs-To Relationship (`type: "one"`)
510
+
511
+ ```json
512
+ {
513
+ "UserDetail": {
514
+ "hasRelations": {
515
+ "User": {
516
+ "type": "one",
517
+ "table": "users",
518
+ "localKey": "user_id",
519
+ "foreignKey": "id"
520
+ }
521
+ }
522
+ }
523
+ }
524
+ ```
525
+
526
+ #### One-to-Many Relationship (`type: "many"`)
527
+
528
+ ```json
529
+ {
530
+ "User": {
531
+ "hasRelations": {
532
+ "Post": {
533
+ "type": "many",
534
+ "table": "posts",
535
+ "localKey": "id",
536
+ "foreignKey": "user_id"
537
+ },
538
+ "Comment": {
539
+ "type": "many",
540
+ "table": "comments",
541
+ "localKey": "id",
542
+ "foreignKey": "user_id"
543
+ }
544
+ }
545
+ }
546
+ }
547
+ ```
548
+
549
+ #### Many-to-Many Relationship (using `through`)
550
+
551
+ For many-to-many relationships, use the `through` key to specify the join/pivot table:
552
+
553
+ ```json
554
+ {
555
+ "User": {
556
+ "hasRelations": {
557
+ "Role": {
558
+ "type": "many",
559
+ "table": "roles",
560
+ "localKey": "id",
561
+ "foreignKey": "id",
562
+ "through": "user_roles",
563
+ "throughLocalKey": "user_id",
564
+ "throughForeignKey": "role_id"
565
+ }
566
+ }
567
+ }
568
+ }
569
+ ```
570
+
571
+ **Many-to-Many Structure:**
572
+ - `type`: `"many"` (always use "many" for many-to-many)
573
+ - `table`: The related table name (e.g., `"roles"`)
574
+ - `localKey`: The primary key in the current model (e.g., `"id"`)
575
+ - `foreignKey`: The primary key in the related table (e.g., `"id"`)
576
+ - `through`: **Required** - The join/pivot table name (e.g., `"user_roles"`)
577
+ - `throughLocalKey`: The foreign key in the join table pointing to the current model (e.g., `"user_id"`)
578
+ - `throughForeignKey`: The foreign key in the join table pointing to the related model (e.g., `"role_id"`)
579
+
580
+ ### Using Relationships in Queries
581
+
582
+ #### Eager Loading with `with`
583
+
584
+ ```javascript
585
+ // Load User with related Posts and Comments
586
+ POST /api/User/crud
587
+ {
588
+ "action": "list",
589
+ "where": { "id": 1 },
590
+ "with": ["Post", "Comment"]
591
+ }
592
+
593
+ // Nested relationships
594
+ POST /api/User/crud
595
+ {
596
+ "action": "list",
597
+ "where": { "id": 1 },
598
+ "with": ["Post.Comment", "Post.Like"]
599
+ }
600
+
601
+ // Multiple relationships
602
+ POST /api/User/crud
603
+ {
604
+ "action": "list",
605
+ "where": { "id": 1 },
606
+ "with": ["UserDetail", "Post", "Comment", "Like"]
607
+ }
608
+ ```
609
+
610
+ #### Filtering Related Data with Nested Where
611
+
612
+ ```javascript
613
+ // Get users who have posts with specific hashtag
614
+ POST /api/User/crud
615
+ {
616
+ "action": "list",
617
+ "where": {
618
+ "Post.hashtag": "#OrganicFarming"
619
+ },
620
+ "with": ["Post"]
621
+ }
622
+
623
+ // Get posts with comments from specific user
624
+ POST /api/Post/crud
625
+ {
626
+ "action": "list",
627
+ "where": {
628
+ "Comment.user_id": 1
629
+ },
630
+ "with": ["Comment"]
631
+ }
632
+ ```
633
+
634
+ ### Example: Complete Relationship Structure
635
+
636
+ Based on your actual schema (`../borebell.in/schema/sync.json`):
637
+
638
+ ```json
639
+ {
640
+ "User": {
641
+ "table": "users",
642
+ "hasRelations": {
643
+ "UserDetail": {
644
+ "type": "one",
645
+ "table": "user_details",
646
+ "localKey": "id",
647
+ "foreignKey": "user_id"
648
+ },
649
+ "Post": {
650
+ "type": "many",
651
+ "table": "posts",
652
+ "localKey": "id",
653
+ "foreignKey": "user_id"
654
+ },
655
+ "Comment": {
656
+ "type": "many",
657
+ "table": "comments",
658
+ "localKey": "id",
659
+ "foreignKey": "user_id"
660
+ },
661
+ "Like": {
662
+ "type": "many",
663
+ "table": "likes",
664
+ "localKey": "id",
665
+ "foreignKey": "user_id"
666
+ }
667
+ }
668
+ },
669
+ "Post": {
670
+ "table": "posts",
671
+ "hasRelations": {
672
+ "User": {
673
+ "type": "one",
674
+ "table": "users",
675
+ "localKey": "user_id",
676
+ "foreignKey": "id"
677
+ },
678
+ "Comment": {
679
+ "type": "many",
680
+ "table": "comments",
681
+ "localKey": "id",
682
+ "foreignKey": "post_id"
683
+ },
684
+ "Like": {
685
+ "type": "many",
686
+ "table": "likes",
687
+ "localKey": "id",
688
+ "foreignKey": "post_id"
689
+ }
690
+ }
691
+ }
692
+ }
693
+ ```
694
+
695
+ ### Important Notes
696
+
697
+ > **Always refer to your actual schema file** (`../borebell.in/schema/sync.json`) for the exact relationship definitions in your project. The structure shown above matches the format used in your schema.
698
+
699
+ - `type: "one"` = One-to-One or Belongs-To relationship
700
+ - `type: "many"` = One-to-Many relationship
701
+ - `through` = Required for Many-to-Many relationships (specifies the join table)
702
+ - `localKey` = The key in the current model
703
+ - `foreignKey` = The key in the related table
704
+ - `throughLocalKey` = The key in the join table pointing to current model (for many-to-many)
705
+ - `throughForeignKey` = The key in the join table pointing to related model (for many-to-many)
706
+
707
+ ## Soft Delete Support
708
+
709
+ ### Enabling Soft Delete
710
+
711
+ Create a model file at `models/Users.model.js`:
712
+
713
+ ```javascript
714
+ class Users {
715
+ // Enable soft delete for this model
716
+ hasSoftDelete = true;
717
+
718
+ // Optional: Custom soft delete hook
719
+ beforeDelete({ model, action, request, context, db, utils, controller }) {
720
+ // Custom logic before soft delete
721
+ console.log('Soft deleting user:', request.where);
722
+ }
723
+
724
+ // Optional: Custom hook after soft delete
725
+ afterDelete({ model, action, data, request, context, db, utils, controller }) {
726
+ // Custom logic after soft delete
727
+ console.log('User soft deleted:', data);
146
728
  }
147
729
  }
148
730
 
149
- // Delete record
731
+ module.exports = Users;
732
+ ```
733
+
734
+ ### Using Soft Delete
735
+
736
+ ```javascript
737
+ // When soft delete is enabled, delete action automatically sets deleted_at
150
738
  POST /api/Users/crud
151
739
  {
152
740
  "action": "delete",
153
741
  "where": { "id": 1 }
154
742
  }
155
743
 
156
- // Count records
744
+ // List operation automatically filters out soft-deleted records
157
745
  POST /api/Users/crud
158
746
  {
159
- "action": "count",
747
+ "action": "list",
160
748
  "where": { "is_active": true }
749
+ // Automatically adds: deleted_at IS NULL
161
750
  }
751
+
752
+ // To see soft-deleted records, you need to query directly
753
+ // (soft delete only affects list and delete operations)
162
754
  ```
163
755
 
164
- ### 5. Advanced Querying Examples
756
+ ## Model Hooks
165
757
 
166
- ```javascript
167
- // Complex search with multiple conditions
168
- POST /api/Users/crud
169
- {
170
- "action": "list",
171
- "where": {
172
- "first_name": { "like": "%John%" },
173
- "email": { "like": "%@example.com" },
174
- "is_active": true
175
- },
176
- "select": ["id", "username", "email", "first_name", "last_name"],
177
- "sort": [["created_at", "desc"]],
178
- "limit": 10
179
- }
758
+ ### Creating Model Hooks
180
759
 
181
- // Pagination with offset
182
- POST /api/Users/crud
183
- {
184
- "action": "list",
185
- "select": ["id", "username", "email", "first_name", "last_name"],
186
- "sort": [["created_at", "desc"]],
187
- "limit": 10,
188
- "offset": 20
189
- }
760
+ Create a model file at `models/Users.model.js`:
190
761
 
191
- // Count with conditions
192
- POST /api/Users/crud
193
- {
194
- "action": "count",
195
- "where": {
196
- "is_active": true,
197
- "created_at": { ">=": "2024-01-01" }
762
+ ```javascript
763
+ class Users {
764
+ // Validation hook - runs before any action
765
+ async validate({ model, action, request, context, db, utils, controller }) {
766
+ if (action === 'create' || action === 'update') {
767
+ if (!request.data.email) {
768
+ throw new Error('Email is required');
769
+ }
770
+ // Check if email already exists
771
+ const existing = await db('users')
772
+ .where('email', request.data.email)
773
+ .first();
774
+ if (existing && existing.id !== request.where?.id) {
775
+ throw new Error('Email already exists');
776
+ }
777
+ }
778
+ }
779
+
780
+ // Before hook - runs before the action executes
781
+ async beforeCreate({ model, action, request, context, db, utils, controller }) {
782
+ // Add timestamps
783
+ request.data.created_at = new Date();
784
+ request.data.updated_at = new Date();
785
+ return request.data;
198
786
  }
787
+
788
+ async beforeUpdate({ model, action, request, context, db, utils, controller }) {
789
+ // Update timestamp
790
+ request.data.updated_at = new Date();
791
+ return request.data;
792
+ }
793
+
794
+ // After hook - runs after the action executes
795
+ async afterCreate({ model, action, data, request, context, db, utils, controller }) {
796
+ // Log creation
797
+ console.log('User created:', data);
798
+ // Send welcome email, etc.
799
+ return data;
800
+ }
801
+
802
+ async afterUpdate({ model, action, data, request, context, db, utils, controller }) {
803
+ // Log update
804
+ console.log('User updated:', data);
805
+ return data;
806
+ }
807
+
808
+ // Custom action hook
809
+ async onCustomAction({ model, action, request, context, db, utils, controller }) {
810
+ // Handle custom actions like 'activate', 'deactivate', etc.
811
+ if (action === 'activate') {
812
+ return await db('users')
813
+ .where(request.where)
814
+ .update({ is_active: true, updated_at: new Date() });
815
+ }
816
+ throw new Error(`Unknown custom action: ${action}`);
817
+ }
818
+
819
+ // Soft delete support
820
+ hasSoftDelete = true;
199
821
  }
200
822
 
201
- // Join queries (if relationships are defined)
823
+ module.exports = Users;
824
+ ```
825
+
826
+ ### Using Custom Actions
827
+
828
+ ```javascript
829
+ // Call custom action
202
830
  POST /api/Users/crud
203
831
  {
204
- "action": "list",
205
- "select": ["id", "username", "email"],
206
- "join": [
207
- {
208
- "table": "user_profiles",
209
- "on": "users.id = user_profiles.user_id"
210
- }
211
- ],
212
- "where": { "users.is_active": true }
832
+ "action": "activate",
833
+ "where": { "id": 1 }
213
834
  }
214
835
  ```
215
836
 
216
- ### 6. Data Validation Example
837
+ ## Data Validation
838
+
839
+ ### Basic Validation
217
840
 
218
841
  ```javascript
219
- const { validate } = require('korm-js');
842
+ const { validate } = require('@dreamtree-org/korm-js');
220
843
 
221
844
  // Define validation rules
222
845
  const userValidationRules = {
223
- username: 'required|type:string|maxLen:50',
846
+ username: 'required|type:string|minLen:3|maxLen:50',
224
847
  email: 'required|type:string|maxLen:255',
225
848
  first_name: 'required|type:string|maxLen:100',
226
849
  last_name: 'required|type:string|maxLen:100',
227
- age: 'type:number|min:0|max:150'
850
+ age: 'type:number|min:0|max:150',
851
+ is_active: 'type:boolean'
228
852
  };
229
853
 
230
854
  // Validate and create user
@@ -235,24 +859,20 @@ app.post('/api/users/validate', async (req, res) => {
235
859
 
236
860
  const result = await korm.processRequest({
237
861
  action: 'create',
238
- data: {
239
- ...validatedData,
240
- created_at: new Date(),
241
- updated_at: new Date()
242
- }
862
+ data: validatedData
243
863
  }, 'Users');
244
864
 
245
865
  res.status(201).json({
246
866
  success: true,
247
867
  message: 'User created successfully',
248
- data: result.data
868
+ data: result
249
869
  });
250
870
  } catch (error) {
251
871
  if (error.name === 'ValidationError') {
252
872
  return res.status(400).json({
253
873
  success: false,
254
874
  message: 'Validation failed',
255
- errors: error.message.details
875
+ errors: error.message
256
876
  });
257
877
  }
258
878
 
@@ -265,28 +885,102 @@ app.post('/api/users/validate', async (req, res) => {
265
885
  });
266
886
  ```
267
887
 
268
- ### 7. Schema Structure Example
888
+ ### Advanced Validation
889
+
890
+ ```javascript
891
+ const { validate } = require('@dreamtree-org/korm-js');
892
+
893
+ // Advanced validation rules
894
+ const advancedRules = {
895
+ username: 'required|type:string|regex:username',
896
+ email: 'required|type:string|regex:email',
897
+ phone: 'regex:phone',
898
+ password: 'required|type:string|minLen:8',
899
+ confirm_password: 'required|type:string|match:password',
900
+ status: 'in:active,inactive,pending',
901
+ user_id: 'exists:users,id'
902
+ };
903
+
904
+ const customRegex = {
905
+ username: /^[a-zA-Z0-9_]+$/,
906
+ email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
907
+ phone: /^\+?[\d\s-()]{10,15}$/
908
+ };
909
+
910
+ const validatedData = await validate(data, advancedRules, { customRegex });
911
+ ```
912
+
913
+ ## Nested Requests
914
+
915
+ ```javascript
916
+ // Process multiple related requests in one call
917
+ POST /api/Users/crud
918
+ {
919
+ "action": "show",
920
+ "where": { "id": 1 },
921
+ "other_requests": {
922
+ "Posts": {
923
+ "action": "list",
924
+ "where": { "user_id": 1 },
925
+ "limit": 10
926
+ },
927
+ "Comments": {
928
+ "action": "list",
929
+ "where": { "user_id": 1 },
930
+ "limit": 5
931
+ }
932
+ }
933
+ }
934
+
935
+ // Response includes nested data
936
+ {
937
+ "id": 1,
938
+ "username": "john_doe",
939
+ "email": "john@example.com",
940
+ "other_responses": {
941
+ "Posts": {
942
+ "data": [/* posts */],
943
+ "total": 10
944
+ },
945
+ "Comments": {
946
+ "data": [/* comments */],
947
+ "total": 5
948
+ }
949
+ }
950
+ }
951
+ ```
952
+
953
+ ## Schema Structure
954
+
955
+ ### Example Schema (Auto-generated from MySQL)
269
956
 
270
957
  ```javascript
271
- // Example schema structure (auto-generated)
272
958
  {
273
959
  "Users": {
274
960
  "table": "users",
275
961
  "alias": "Users",
962
+ "modelName": "Users",
276
963
  "columns": {
277
- "id": "bigint|size:8|default:nextval('users_id_seq'::regclass)",
278
- "username": "character varying|size:255",
279
- "email": "character varying|size:255",
280
- "first_name": "character varying|size:255|nullable",
281
- "last_name": "character varying|size:255|nullable",
282
- "is_active": "boolean|default:true",
283
- "created_at": "timestamp with time zone|default:now()",
284
- "updated_at": "timestamp with time zone|default:now()"
964
+ "id": "bigint|size:8|unsigned|auto_increment",
965
+ "username": "varchar|size:255",
966
+ "email": "varchar|size:255",
967
+ "first_name": "varchar|size:255|nullable",
968
+ "last_name": "varchar|size:255|nullable",
969
+ "age": "int|size:4|nullable",
970
+ "is_active": "tinyint|size:1|default:1",
971
+ "created_at": "timestamp|default:CURRENT_TIMESTAMP",
972
+ "updated_at": "timestamp|default:CURRENT_TIMESTAMP|onUpdate:CURRENT_TIMESTAMP",
973
+ "deleted_at": "timestamp|nullable"
285
974
  },
286
- "modelName": "Users",
287
975
  "seed": [],
288
976
  "hasRelations": {},
289
- "indexes": []
977
+ "indexes": [
978
+ {
979
+ "name": "idx_users_email",
980
+ "columns": ["email"],
981
+ "unique": true
982
+ }
983
+ ]
290
984
  }
291
985
  }
292
986
  ```
@@ -298,12 +992,15 @@ app.post('/api/users/validate', async (req, res) => {
298
992
  ```javascript
299
993
  const korm = initializeKORM({
300
994
  db: db,
301
- dbClient: 'pg' // or 'mysql', 'sqlite'
995
+ dbClient: 'mysql' // or 'pg', 'sqlite'
302
996
  });
303
997
 
304
998
  // Process any CRUD request
305
999
  const result = await korm.processRequest(requestBody, modelName);
306
1000
 
1001
+ // Process request with nested requests
1002
+ const result = await korm.processRequestWithOthers(requestBody, modelName);
1003
+
307
1004
  // Set schema manually
308
1005
  korm.setSchema(schemaObject);
309
1006
 
@@ -314,139 +1011,100 @@ await korm.syncDatabase();
314
1011
  const schema = await korm.generateSchema();
315
1012
  ```
316
1013
 
317
- ### ProcessRequest Actions
318
-
319
- ```javascript
320
- // List records
321
- {
322
- "action": "list",
323
- "where": { "is_active": true },
324
- "select": ["id", "name", "email"],
325
- "sort": [["created_at", "desc"]],
326
- "limit": 10,
327
- "offset": 0
328
- }
329
-
330
- // Show single record
331
- {
332
- "action": "show",
333
- "where": { "id": 1 }
334
- }
335
-
336
- // Create record
337
- {
338
- "action": "create",
339
- "data": { "name": "John", "email": "john@example.com" }
340
- }
341
-
342
- // Update record
343
- {
344
- "action": "update",
345
- "where": { "id": 1 },
346
- "data": { "name": "Jane" }
347
- }
348
-
349
- // Delete record
350
- {
351
- "action": "delete",
352
- "where": { "id": 1 }
353
- }
354
-
355
- // Count records
356
- {
357
- "action": "count",
358
- "where": { "is_active": true }
359
- }
360
- ```
361
-
362
- ### Validation Rules
1014
+ ### ControllerWrapper Static Methods
363
1015
 
364
1016
  ```javascript
365
- const { validate } = require('korm-js');
366
-
367
- // Basic validation rules
368
- const rules = {
369
- username: 'required|type:string|minLen:3|maxLen:30',
370
- email: 'required|type:string|maxLen:255',
371
- age: 'type:number|min:0|max:150',
372
- status: 'in:active,inactive,pending'
373
- };
1017
+ const { ControllerWrapper } = require('@dreamtree-org/korm-js');
374
1018
 
375
- // Advanced validation with custom patterns
376
- const advancedRules = {
377
- username: 'required|type:string|regex:username',
378
- email: 'required|type:string|regex:email',
379
- phone: 'regex:phone',
380
- user_id: 'exists:users,id'
381
- };
1019
+ // Load model class
1020
+ const ModelClass = ControllerWrapper.loadModelClass('Users');
382
1021
 
383
- const customRegex = {
384
- username: /^[a-zA-Z0-9_]+$/,
385
- email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
386
- phone: /^\+?[\d\s-()]{10,15}$/
387
- };
1022
+ // Get model instance
1023
+ const modelInstance = ControllerWrapper.getModelInstance(model);
388
1024
 
389
- const validatedData = await validate(data, rules, { customRegex });
1025
+ // Generate schema
1026
+ const schema = await ControllerWrapper.generateSchema();
390
1027
  ```
391
1028
 
392
- ### Helper Utilities
1029
+ ### ProcessRequest Actions Summary
393
1030
 
394
- ```javascript
395
- const { helperUtility } = require('korm-js');
1031
+ | Action | Description | Required Fields |
1032
+ |--------|-------------|----------------|
1033
+ | `create` | Create new record | `data` |
1034
+ | `list` | Get multiple records | None (optional: `where`, `select`, `sort`, `limit`, `offset`) |
1035
+ | `show` | Get single record | `where` |
1036
+ | `update` | Update record(s) | `where`, `data` |
1037
+ | `delete` | Delete record(s) | `where` |
1038
+ | `count` | Count records | None (optional: `where`) |
1039
+ | `replace` | Replace record (MySQL) | `data` |
1040
+ | `upsert` | Insert or update | `data`, `conflict` |
1041
+ | `sync` | Upsert + delete | `data`, `conflict`, `where` |
396
1042
 
397
- // File operations
398
- helperUtility.file.readJSON('path/to/file.json');
399
- helperUtility.file.writeJSON('path/to/file.json', data);
400
- helperUtility.file.createDirectory('path/to/directory');
1043
+ ### Validation Rules
401
1044
 
402
- // Other utilities available through helperUtility
403
- ```
1045
+ | Rule | Description | Example |
1046
+ |------|-------------|---------|
1047
+ | `required` | Field is required | `username: 'required'` |
1048
+ | `type:string` | Field must be string | `name: 'type:string'` |
1049
+ | `type:number` | Field must be number | `age: 'type:number'` |
1050
+ | `type:boolean` | Field must be boolean | `is_active: 'type:boolean'` |
1051
+ | `minLen:n` | Minimum length | `username: 'minLen:3'` |
1052
+ | `maxLen:n` | Maximum length | `email: 'maxLen:255'` |
1053
+ | `min:n` | Minimum value | `age: 'min:0'` |
1054
+ | `max:n` | Maximum value | `age: 'max:150'` |
1055
+ | `in:val1,val2` | Value must be in list | `status: 'in:active,inactive'` |
1056
+ | `regex:name` | Custom regex pattern | `email: 'regex:email'` |
1057
+ | `match:field` | Must match another field | `confirm_password: 'match:password'` |
1058
+ | `exists:table,column` | Value must exist in table | `user_id: 'exists:users,id'` |
404
1059
 
405
1060
  ## Database Support
406
1061
 
407
- ### PostgreSQL (Recommended)
1062
+ ### MySQL (Current Guide)
1063
+
408
1064
  ```javascript
409
1065
  const knex = require('knex');
410
1066
 
411
1067
  const db = knex({
412
- client: 'pg',
1068
+ client: 'mysql2',
413
1069
  connection: {
414
1070
  host: process.env.DB_HOST || 'localhost',
415
- user: process.env.DB_USER || 'username',
1071
+ user: process.env.DB_USER || 'root',
416
1072
  password: process.env.DB_PASS || 'password',
417
1073
  database: process.env.DB_NAME || 'database_name',
418
- port: process.env.DB_PORT || 5432
1074
+ port: process.env.DB_PORT || 3306
419
1075
  }
420
1076
  });
421
1077
 
422
1078
  const korm = initializeKORM({
423
1079
  db: db,
424
- dbClient: 'pg'
1080
+ dbClient: 'mysql'
425
1081
  });
426
1082
  ```
427
1083
 
428
- ### MySQL
1084
+ ### PostgreSQL
1085
+
429
1086
  ```javascript
430
1087
  const knex = require('knex');
431
1088
 
432
1089
  const db = knex({
433
- client: 'mysql2',
1090
+ client: 'pg',
434
1091
  connection: {
435
1092
  host: process.env.DB_HOST || 'localhost',
436
1093
  user: process.env.DB_USER || 'username',
437
1094
  password: process.env.DB_PASS || 'password',
438
1095
  database: process.env.DB_NAME || 'database_name',
439
- port: process.env.DB_PORT || 3306
1096
+ port: process.env.DB_PORT || 5432
440
1097
  }
441
1098
  });
442
1099
 
443
1100
  const korm = initializeKORM({
444
1101
  db: db,
445
- dbClient: 'mysql'
1102
+ dbClient: 'pg'
446
1103
  });
447
1104
  ```
448
1105
 
449
1106
  ### SQLite
1107
+
450
1108
  ```javascript
451
1109
  const knex = require('knex');
452
1110
 
@@ -466,15 +1124,123 @@ const korm = initializeKORM({
466
1124
  ## Error Handling
467
1125
 
468
1126
  ```javascript
1127
+ // Global error handler
469
1128
  app.use((error, req, res, next) => {
470
1129
  console.error('KORM Error:', error);
471
1130
 
472
- res.status(500).json({
1131
+ res.status(error.status || 500).json({
473
1132
  success: false,
474
1133
  message: 'Internal server error',
475
- error: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong'
1134
+ error: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong',
1135
+ stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
476
1136
  });
477
1137
  });
1138
+
1139
+ // Route-specific error handling
1140
+ app.post('/api/:model/crud', async (req, res) => {
1141
+ try {
1142
+ const { model } = req.params;
1143
+ const result = await korm.processRequest(req.body, model);
1144
+ res.json(result);
1145
+ } catch (error) {
1146
+ // Handle validation errors
1147
+ if (error.name === 'ValidationError') {
1148
+ return res.status(400).json({
1149
+ success: false,
1150
+ message: 'Validation failed',
1151
+ errors: error.message
1152
+ });
1153
+ }
1154
+
1155
+ // Handle not found errors
1156
+ if (error.message.includes('not found')) {
1157
+ return res.status(404).json({
1158
+ success: false,
1159
+ message: error.message
1160
+ });
1161
+ }
1162
+
1163
+ // Handle other errors
1164
+ res.status(400).json({
1165
+ success: false,
1166
+ message: error.message
1167
+ });
1168
+ }
1169
+ });
1170
+ ```
1171
+
1172
+ ## Complete Example Application
1173
+
1174
+ ```javascript
1175
+ require('dotenv').config();
1176
+ const express = require('express');
1177
+ const { initializeKORM, validate, helperUtility } = require('@dreamtree-org/korm-js');
1178
+ const knex = require('knex');
1179
+
1180
+ const app = express();
1181
+ app.use(express.json());
1182
+
1183
+ // MySQL configuration
1184
+ const db = knex({
1185
+ client: 'mysql2',
1186
+ connection: {
1187
+ host: process.env.DB_HOST || 'localhost',
1188
+ user: process.env.DB_USER || 'root',
1189
+ password: process.env.DB_PASS || 'password',
1190
+ database: process.env.DB_NAME || 'my_database',
1191
+ port: process.env.DB_PORT || 3306
1192
+ }
1193
+ });
1194
+
1195
+ // Initialize KORM
1196
+ const korm = initializeKORM({
1197
+ db: db,
1198
+ dbClient: 'mysql'
1199
+ });
1200
+
1201
+ // Initialize app
1202
+ async function initApp() {
1203
+ try {
1204
+ // Load or generate schema
1205
+ let schema = helperUtility.file.readJSON('schema/schema.json');
1206
+ if (schema) {
1207
+ korm.setSchema(schema);
1208
+ } else {
1209
+ schema = await korm.generateSchema();
1210
+ helperUtility.file.createDirectory('schema');
1211
+ helperUtility.file.writeJSON('schema/schema.json', schema);
1212
+ }
1213
+
1214
+ // Sync database
1215
+ await korm.syncDatabase();
1216
+ console.log('✅ Database synced');
1217
+ } catch (error) {
1218
+ console.error('❌ Initialization error:', error);
1219
+ }
1220
+ }
1221
+
1222
+ // Generic CRUD endpoint
1223
+ app.post('/api/:model/crud', async (req, res) => {
1224
+ try {
1225
+ const { model } = req.params;
1226
+ const result = await korm.processRequest(req.body, model);
1227
+ res.json(result);
1228
+ } catch (error) {
1229
+ res.status(400).json({ error: error.message });
1230
+ }
1231
+ });
1232
+
1233
+ // Health check
1234
+ app.get('/health', (req, res) => {
1235
+ res.send('OK');
1236
+ });
1237
+
1238
+ // Start server
1239
+ const PORT = process.env.PORT || 3000;
1240
+ app.listen(PORT, async () => {
1241
+ console.log(`🚀 Server running on http://localhost:${PORT}`);
1242
+ await initApp();
1243
+ });
478
1244
  ```
479
1245
 
480
1246
  ## Contributing
@@ -491,7 +1257,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
491
1257
 
492
1258
  ## Support
493
1259
 
494
- If you have any questions or need help, please open an issue on GitHub or contact us at [partha.preetham.krishna@gmail.com](mailto:partha.preetham.krishna@gmail.com).
1260
+ If you have any questions or need help, please open an issue on GitHub or contact us at [partha.preetham.krishna@gmail.com](mailto:partha-preetham.krishna@gmail.com).
495
1261
 
496
1262
  ---
497
1263
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamtree-org/korm-js",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
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",