@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.
- package/README.md +945 -179
- 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
|
|
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
|
-
[](https://badge.fury.io/js/@dreamtree-org%2Fkorm-js)
|
|
6
6
|
[](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
|
|
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
|
|
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
|
-
//
|
|
40
|
+
// MySQL database configuration
|
|
39
41
|
const db = knex({
|
|
40
|
-
client: '
|
|
42
|
+
client: 'mysql2',
|
|
41
43
|
connection: {
|
|
42
44
|
host: process.env.DB_HOST || 'localhost',
|
|
43
|
-
user: process.env.DB_USER || '
|
|
44
|
-
password: process.env.DB_PASS || '
|
|
45
|
-
database: process.env.DB_NAME || '
|
|
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: '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
//
|
|
744
|
+
// List operation automatically filters out soft-deleted records
|
|
157
745
|
POST /api/Users/crud
|
|
158
746
|
{
|
|
159
|
-
"action": "
|
|
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
|
-
|
|
756
|
+
## Model Hooks
|
|
165
757
|
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
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": "
|
|
205
|
-
"
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
###
|
|
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|
|
|
278
|
-
"username": "
|
|
279
|
-
"email": "
|
|
280
|
-
"first_name": "
|
|
281
|
-
"last_name": "
|
|
282
|
-
"
|
|
283
|
-
"
|
|
284
|
-
"
|
|
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: '
|
|
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
|
-
###
|
|
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 {
|
|
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
|
-
//
|
|
376
|
-
const
|
|
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
|
-
|
|
384
|
-
|
|
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
|
-
|
|
1025
|
+
// Generate schema
|
|
1026
|
+
const schema = await ControllerWrapper.generateSchema();
|
|
390
1027
|
```
|
|
391
1028
|
|
|
392
|
-
###
|
|
1029
|
+
### ProcessRequest Actions Summary
|
|
393
1030
|
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
1062
|
+
### MySQL (Current Guide)
|
|
1063
|
+
|
|
408
1064
|
```javascript
|
|
409
1065
|
const knex = require('knex');
|
|
410
1066
|
|
|
411
1067
|
const db = knex({
|
|
412
|
-
client: '
|
|
1068
|
+
client: 'mysql2',
|
|
413
1069
|
connection: {
|
|
414
1070
|
host: process.env.DB_HOST || 'localhost',
|
|
415
|
-
user: process.env.DB_USER || '
|
|
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 ||
|
|
1074
|
+
port: process.env.DB_PORT || 3306
|
|
419
1075
|
}
|
|
420
1076
|
});
|
|
421
1077
|
|
|
422
1078
|
const korm = initializeKORM({
|
|
423
1079
|
db: db,
|
|
424
|
-
dbClient: '
|
|
1080
|
+
dbClient: 'mysql'
|
|
425
1081
|
});
|
|
426
1082
|
```
|
|
427
1083
|
|
|
428
|
-
###
|
|
1084
|
+
### PostgreSQL
|
|
1085
|
+
|
|
429
1086
|
```javascript
|
|
430
1087
|
const knex = require('knex');
|
|
431
1088
|
|
|
432
1089
|
const db = knex({
|
|
433
|
-
client: '
|
|
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 ||
|
|
1096
|
+
port: process.env.DB_PORT || 5432
|
|
440
1097
|
}
|
|
441
1098
|
});
|
|
442
1099
|
|
|
443
1100
|
const korm = initializeKORM({
|
|
444
1101
|
db: db,
|
|
445
|
-
dbClient: '
|
|
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
|
|
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.
|
|
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",
|