@dreamtree-org/korm-js 1.0.46 → 1.0.47
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 +187 -7
- package/clients/mysql/HookService.js +1 -1
- package/clients/mysql/QueryBuilder.js +1 -1
- package/clients/pg/HookService.js +1 -1
- package/clients/pg/QueryBuilder.js +1 -1
- package/clients/sqlite/CurdTable.js +1 -1
- package/clients/sqlite/HookService.js +1 -1
- package/clients/sqlite/QueryBuilder.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
- 🔄 **Schema Generation**: Auto-generate schemas from existing databases
|
|
18
18
|
- 🗑️ **Soft Delete**: Built-in soft delete support with automatic filtering
|
|
19
19
|
- 🎣 **Model Hooks**: Before, After, Validate, and Custom action hooks
|
|
20
|
+
- 🔗 **Custom Relations**: Define custom relation hooks for complex data fetching
|
|
21
|
+
- 📦 **Nested Requests**: Process multiple related requests in a single call
|
|
20
22
|
|
|
21
23
|
## Installation
|
|
22
24
|
|
|
@@ -871,6 +873,90 @@ Example complete relationship structure:
|
|
|
871
873
|
}
|
|
872
874
|
```
|
|
873
875
|
|
|
876
|
+
### Custom Relation Hooks
|
|
877
|
+
|
|
878
|
+
For complex relationships that can't be defined in the schema, you can create custom relation hooks in your model class. This is useful for:
|
|
879
|
+
- Virtual/computed relations
|
|
880
|
+
- Cross-database relations
|
|
881
|
+
- Complex aggregations
|
|
882
|
+
- Custom data transformations
|
|
883
|
+
|
|
884
|
+
Create a method named `get{RelationName}Relation` in your model file:
|
|
885
|
+
|
|
886
|
+
```javascript
|
|
887
|
+
// models/Users.model.js
|
|
888
|
+
class Users {
|
|
889
|
+
// Custom relation hook for "Statistics" relation
|
|
890
|
+
async getStatisticsRelation({ rows, relName, model, withTree, controller, relation, qb, db }) {
|
|
891
|
+
// rows = parent rows to attach relation data to
|
|
892
|
+
// db = Knex database instance
|
|
893
|
+
// qb = QueryBuilder instance
|
|
894
|
+
|
|
895
|
+
for (const row of rows) {
|
|
896
|
+
// Fetch custom data for each row
|
|
897
|
+
const stats = await db('user_statistics')
|
|
898
|
+
.where('user_id', row.id)
|
|
899
|
+
.first();
|
|
900
|
+
|
|
901
|
+
// Attach to row
|
|
902
|
+
row.Statistics = stats || { posts: 0, comments: 0, likes: 0 };
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Custom relation with aggregation
|
|
907
|
+
async getPostCountRelation({ rows, db }) {
|
|
908
|
+
const userIds = rows.map(r => r.id);
|
|
909
|
+
|
|
910
|
+
const counts = await db('posts')
|
|
911
|
+
.select('user_id')
|
|
912
|
+
.count('* as count')
|
|
913
|
+
.whereIn('user_id', userIds)
|
|
914
|
+
.groupBy('user_id');
|
|
915
|
+
|
|
916
|
+
const countMap = new Map(counts.map(c => [c.user_id, c.count]));
|
|
917
|
+
|
|
918
|
+
for (const row of rows) {
|
|
919
|
+
row.PostCount = countMap.get(row.id) || 0;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Custom relation from external API or different database
|
|
924
|
+
async getExternalProfileRelation({ rows }) {
|
|
925
|
+
for (const row of rows) {
|
|
926
|
+
// Fetch from external source
|
|
927
|
+
row.ExternalProfile = await fetchFromExternalAPI(row.external_id);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
module.exports = Users;
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
**Using Custom Relations:**
|
|
936
|
+
|
|
937
|
+
```javascript
|
|
938
|
+
// Use custom relation just like schema-defined relations
|
|
939
|
+
POST /api/Users/crud
|
|
940
|
+
{
|
|
941
|
+
"action": "list",
|
|
942
|
+
"where": { "is_active": true },
|
|
943
|
+
"with": ["Statistics", "PostCount", "ExternalProfile"]
|
|
944
|
+
}
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
**Custom Relation Hook Arguments:**
|
|
948
|
+
|
|
949
|
+
| Argument | Description |
|
|
950
|
+
|----------|-------------|
|
|
951
|
+
| `rows` | Parent rows to attach relation data to (modify in place) |
|
|
952
|
+
| `relName` | The relation name being fetched |
|
|
953
|
+
| `model` | Model definition object |
|
|
954
|
+
| `withTree` | Nested relations tree for further loading |
|
|
955
|
+
| `controller` | ControllerWrapper instance |
|
|
956
|
+
| `relation` | Relation definition from schema (may be undefined for custom relations) |
|
|
957
|
+
| `qb` | QueryBuilder instance for building queries |
|
|
958
|
+
| `db` | Knex database instance for direct queries |
|
|
959
|
+
|
|
874
960
|
### Important Notes
|
|
875
961
|
|
|
876
962
|
- `type: "one"` = One-to-One or Belongs-To relationship
|
|
@@ -880,6 +966,7 @@ Example complete relationship structure:
|
|
|
880
966
|
- `foreignKey` = The key in the related table
|
|
881
967
|
- `throughLocalKey` = The key in the join table pointing to current model (for many-to-many)
|
|
882
968
|
- `throughForeignKey` = The key in the join table pointing to related model (for many-to-many)
|
|
969
|
+
- Custom relation hooks take precedence when relation is not defined in schema
|
|
883
970
|
|
|
884
971
|
## Soft Delete Support
|
|
885
972
|
|
|
@@ -1027,6 +1114,28 @@ module.exports = Users;
|
|
|
1027
1114
|
| `controller` | ControllerWrapper instance |
|
|
1028
1115
|
| `data` | (After hooks only) Result of the action |
|
|
1029
1116
|
|
|
1117
|
+
### Complete Hook Types Reference
|
|
1118
|
+
|
|
1119
|
+
| Hook Type | Method Naming | When Called | Use Case |
|
|
1120
|
+
|-----------|---------------|-------------|----------|
|
|
1121
|
+
| Validate | `validate` | Before any action | Input validation, authorization |
|
|
1122
|
+
| Before | `before{Action}` | Before action executes | Modify request data, add timestamps |
|
|
1123
|
+
| After | `after{Action}` | After action executes | Transform results, trigger side effects |
|
|
1124
|
+
| Custom Action | `on{Action}Action` | For custom actions | Implement business logic |
|
|
1125
|
+
| Soft Delete | `hasSoftDelete = true` | During delete/list | Enable soft delete |
|
|
1126
|
+
| Custom Relation | `get{RelName}Relation` | During eager loading | Custom data fetching |
|
|
1127
|
+
|
|
1128
|
+
**Available Before/After hooks:**
|
|
1129
|
+
- `beforeCreate` / `afterCreate`
|
|
1130
|
+
- `beforeUpdate` / `afterUpdate`
|
|
1131
|
+
- `beforeDelete` / `afterDelete`
|
|
1132
|
+
- `beforeList` / `afterList`
|
|
1133
|
+
- `beforeShow` / `afterShow`
|
|
1134
|
+
- `beforeCount` / `afterCount`
|
|
1135
|
+
- `beforeReplace` / `afterReplace`
|
|
1136
|
+
- `beforeUpsert` / `afterUpsert`
|
|
1137
|
+
- `beforeSync` / `afterSync`
|
|
1138
|
+
|
|
1030
1139
|
### Using Custom Actions
|
|
1031
1140
|
|
|
1032
1141
|
```javascript
|
|
@@ -1042,6 +1151,14 @@ POST /api/Users/crud
|
|
|
1042
1151
|
"action": "deactivate",
|
|
1043
1152
|
"where": { "id": 1 }
|
|
1044
1153
|
}
|
|
1154
|
+
|
|
1155
|
+
// You can create any custom action
|
|
1156
|
+
POST /api/Users/crud
|
|
1157
|
+
{
|
|
1158
|
+
"action": "sendWelcomeEmail",
|
|
1159
|
+
"where": { "id": 1 }
|
|
1160
|
+
}
|
|
1161
|
+
// Triggers: onSendWelcomeEmailAction({ model, action, request, context, db, utils, controller })
|
|
1045
1162
|
```
|
|
1046
1163
|
|
|
1047
1164
|
## Data Validation
|
|
@@ -1170,16 +1287,16 @@ POST /api/Users/crud
|
|
|
1170
1287
|
"alias": "Users",
|
|
1171
1288
|
"modelName": "Users",
|
|
1172
1289
|
"columns": {
|
|
1173
|
-
"id": "bigint|size:8|unsigned|
|
|
1174
|
-
"username": "varchar|size:255",
|
|
1175
|
-
"email": "varchar|size:255",
|
|
1176
|
-
"first_name": "varchar|size:255
|
|
1177
|
-
"last_name": "varchar|size:255
|
|
1178
|
-
"age": "int|size:4
|
|
1290
|
+
"id": "bigint|size:8|unsigned|primaryKey|autoIncrement",
|
|
1291
|
+
"username": "varchar|size:255|notNull|unique",
|
|
1292
|
+
"email": "varchar|size:255|notNull",
|
|
1293
|
+
"first_name": "varchar|size:255",
|
|
1294
|
+
"last_name": "varchar|size:255",
|
|
1295
|
+
"age": "int|size:4",
|
|
1179
1296
|
"is_active": "tinyint|size:1|default:1",
|
|
1180
1297
|
"created_at": "timestamp|default:CURRENT_TIMESTAMP",
|
|
1181
1298
|
"updated_at": "timestamp|default:CURRENT_TIMESTAMP|onUpdate:CURRENT_TIMESTAMP",
|
|
1182
|
-
"deleted_at": "timestamp
|
|
1299
|
+
"deleted_at": "timestamp"
|
|
1183
1300
|
},
|
|
1184
1301
|
"seed": [],
|
|
1185
1302
|
"hasRelations": {},
|
|
@@ -1194,6 +1311,69 @@ POST /api/Users/crud
|
|
|
1194
1311
|
}
|
|
1195
1312
|
```
|
|
1196
1313
|
|
|
1314
|
+
### Column Definition String Format
|
|
1315
|
+
|
|
1316
|
+
Column definitions use a pipe-separated string format:
|
|
1317
|
+
|
|
1318
|
+
```
|
|
1319
|
+
type|modifier1|modifier2|...
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
**Column Modifiers:**
|
|
1323
|
+
|
|
1324
|
+
| Modifier | Description | Example |
|
|
1325
|
+
|----------|-------------|---------|
|
|
1326
|
+
| `size:n` | Column size | `varchar|size:255` |
|
|
1327
|
+
| `unsigned` | Unsigned integer | `int|unsigned` |
|
|
1328
|
+
| `primaryKey` | Primary key column | `bigint|primaryKey` |
|
|
1329
|
+
| `autoIncrement` | Auto increment | `bigint|primaryKey|autoIncrement` |
|
|
1330
|
+
| `notNull` | Not nullable | `varchar|size:255|notNull` |
|
|
1331
|
+
| `unique` | Unique constraint | `varchar|unique` |
|
|
1332
|
+
| `default:value` | Default value | `tinyint|default:1` |
|
|
1333
|
+
| `onUpdate:value` | On update value | `timestamp|onUpdate:CURRENT_TIMESTAMP` |
|
|
1334
|
+
| `comment:text` | Column comment | `varchar|comment:User email address` |
|
|
1335
|
+
| `foreignKey:table:column` | Foreign key | `int|foreignKey:users:id` |
|
|
1336
|
+
|
|
1337
|
+
**Special Default Values:**
|
|
1338
|
+
- `now` or `now()` → `CURRENT_TIMESTAMP`
|
|
1339
|
+
|
|
1340
|
+
**Example Column Definitions:**
|
|
1341
|
+
|
|
1342
|
+
```javascript
|
|
1343
|
+
{
|
|
1344
|
+
"id": "bigint|size:8|unsigned|primaryKey|autoIncrement",
|
|
1345
|
+
"user_id": "int|unsigned|notNull|foreignKey:users:id",
|
|
1346
|
+
"status": "enum|in:active,inactive,pending|default:pending",
|
|
1347
|
+
"created_at": "timestamp|default:now",
|
|
1348
|
+
"updated_at": "timestamp|default:now|onUpdate:now",
|
|
1349
|
+
"bio": "text|comment:User biography"
|
|
1350
|
+
}
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
### Seed Data
|
|
1354
|
+
|
|
1355
|
+
You can define seed data in the schema to auto-populate tables:
|
|
1356
|
+
|
|
1357
|
+
```javascript
|
|
1358
|
+
{
|
|
1359
|
+
"Roles": {
|
|
1360
|
+
"table": "roles",
|
|
1361
|
+
"columns": {
|
|
1362
|
+
"id": "int|primaryKey|autoIncrement",
|
|
1363
|
+
"name": "varchar|size:50|notNull|unique",
|
|
1364
|
+
"description": "text"
|
|
1365
|
+
},
|
|
1366
|
+
"seed": [
|
|
1367
|
+
{ "name": "admin", "description": "Administrator role" },
|
|
1368
|
+
{ "name": "user", "description": "Regular user role" },
|
|
1369
|
+
{ "name": "moderator", "description": "Moderator role" }
|
|
1370
|
+
]
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
```
|
|
1374
|
+
|
|
1375
|
+
Seed data is automatically inserted when `syncDatabase()` is called and the table is empty.
|
|
1376
|
+
|
|
1197
1377
|
## API Reference
|
|
1198
1378
|
|
|
1199
1379
|
### KORM Instance Methods
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const path=require("path");class HookService{constructor(e,t,o=null){this.db=e,this.utils=t,this.controllerWrapper=o,this.appRoot=o&&o.resolverPath?o.resolverPath:process.cwd()}loadModelClass(e){try{const t=path.join(this.appRoot,"models",`${e}.model.js`);delete require.cache[require.resolve(t)];return require(t)}catch(e){return void console.log("loadModelClass error",{err:e})}}getModelInstance(e){
|
|
1
|
+
const path=require("path");class HookService{constructor(e,t,o=null){this.db=e,this.utils=t,this.controllerWrapper=o,this.controllerWrapper.hookService=this,this.appRoot=o&&o.resolverPath?o.resolverPath:process.cwd()}loadModelClass(e){try{const t=path.join(this.appRoot,"models",`${e}.model.js`);delete require.cache[require.resolve(t)];return require(t)}catch(e){return void console.log("loadModelClass error",{err:e})}}getModelInstance(e){let t="string"==typeof e?e:e.modelName;const o=this.loadModelClass(t);if(o)return"function"==typeof o?new o:o}resolveModelHook(e,t,o){const r=e.modelName,s=this.loadModelClass(r);if(!s)return;const l=this.getModelInstance(e);let i;if("validate"===t)i="validate";else if("on"===t)i=`on${o.charAt(0).toUpperCase()+o.slice(1)}`;else if("before"===t)i=`before${o.charAt(0).toUpperCase()+o.slice(1)}`;else if("after"===t)i=`after${o.charAt(0).toUpperCase()+o.slice(1)}`;else{if("custom"!==t)return;i=`on${o.charAt(0).toUpperCase()+o.slice(1)}Action`}return"function"==typeof l[i]?l[i].bind(l):"function"==typeof s[i]?s[i].bind(s):void 0}async executeValidatorHook({model:e,action:t,request:o,ctx:r,controller:s}){const l=this.resolveModelHook(e,"validate",t);if(l)return await l({model:e,action:t,request:o,context:r,db:this.db,utils:this.utils,controller:s})}async executeBeforeHook({model:e,action:t,request:o,ctx:r,controller:s}){const l=this.resolveModelHook(e,"before",t);if(l)return await l({model:e,action:t,request:o,context:r,db:this.db,utils:this.utils,controller:s})}async executeAfterHook({model:e,action:t,data:o,request:r,ctx:s,controller:l}){const i=this.resolveModelHook(e,"after",t);return i?await i({model:e,action:t,data:o,request:r,context:s,db:this.db,utils:this.utils,controller:l}):o}async executeCustomAction({model:e,action:t,request:o,ctx:r,controller:s}){const l=this.resolveModelHook(e,"custom",t);if(l)return await l({model:e,action:t,request:o,context:r,db:this.db,utils:this.utils,controller:s});throw new Error(`No custom action hook found for ${e.modelName}.${t}`)}async executeHasSoftDeleteHook(e){const t=this.getModelInstance(e);if(!t)return!1;return t.hasOwnProperty("hasSoftDelete")&&!0===t.hasSoftDelete}}module.exports=HookService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const HelperUtility=require("./HelperUtility");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getQueryBuilder(e,t=null){let r=this.db(e.table);return t&&(r=t),r._getMyModel=()=>e,r}parseValue(e){return this.helperUtility.parseValue(e)}parseWhereValue(e){return this.helperUtility.parseWhereValue(e)}parseWhereColumn(e){return this.helperUtility.parseWhereColumn(e)}_applyOrWhereCondition(e,t,r,i){if(null!==i)if(Array.isArray(i))e.orWhereIn(t,i);else switch(r){case"between":e.orWhereBetween(t,i);break;case"notBetween":e.orWhereNotBetween(t,i);break;case"in":e.orWhereIn(t,i);break;case"notIn":e.orWhereNotIn(t,i);break;case"like":e.orWhereRaw(`\`${t}\` LIKE ?`,[i]);break;default:e.orWhere(t,r,i)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,i){if(null!==i)if(Array.isArray(i))e.whereIn(t,i);else switch(r){case"between":e.whereBetween(t,i);break;case"notBetween":e.whereNotBetween(t,i);break;case"in":e.whereIn(t,i);break;case"notIn":e.whereNotIn(t,i);break;case"like":e.whereRaw(`\`${t}\` LIKE ?`,[i]);break;default:e.where(t,r,i)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:i})=>t?t({current:e,part:r,source:i}):{}})}async fetchRelatedRows(e,t){if(e.through){let r=await this.db(e.through).whereIn(e.throughLocalKey,t);return await this.db(e.table).whereIn(e.foreignKey,r.map(t=>t[e.throughForeignKey]))}return this.db(e.table).whereIn(e.foreignKey,t)}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:i,withTree:
|
|
1
|
+
const HelperUtility=require("./HelperUtility");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getHookService(){return this.controllerWrapper.hookService}getQueryBuilder(e,t=null){let r=this.db(e.table);return t&&(r=t),r._getMyModel=()=>e,r}parseValue(e){return this.helperUtility.parseValue(e)}parseWhereValue(e){return this.helperUtility.parseWhereValue(e)}parseWhereColumn(e){return this.helperUtility.parseWhereColumn(e)}_applyOrWhereCondition(e,t,r,i){if(null!==i)if(Array.isArray(i))e.orWhereIn(t,i);else switch(r){case"between":e.orWhereBetween(t,i);break;case"notBetween":e.orWhereNotBetween(t,i);break;case"in":e.orWhereIn(t,i);break;case"notIn":e.orWhereNotIn(t,i);break;case"like":e.orWhereRaw(`\`${t}\` LIKE ?`,[i]);break;default:e.orWhere(t,r,i)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,i){if(null!==i)if(Array.isArray(i))e.whereIn(t,i);else switch(r){case"between":e.whereBetween(t,i);break;case"notBetween":e.whereNotBetween(t,i);break;case"in":e.whereIn(t,i);break;case"notIn":e.whereNotIn(t,i);break;case"like":e.whereRaw(`\`${t}\` LIKE ?`,[i]);break;default:e.where(t,r,i)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:i})=>t?t({current:e,part:r,source:i}):{}})}async fetchRelatedRows(e,t){if(e.through){let r=await this.db(e.through).whereIn(e.throughLocalKey,t);return await this.db(e.table).whereIn(e.foreignKey,r.map(t=>t[e.throughForeignKey]))}return this.db(e.table).whereIn(e.foreignKey,t)}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:i,withTree:o,relation:l}=e;if(!l){const e=this.getHookService().getModelInstance(i),l=`get${r.charAt(0).toUpperCase()+r.slice(1)}Relation`;if("function"==typeof e[l]){const a={rows:t,relName:r,model:i,withTree:o,controller:this.controllerWrapper,relation:i.hasRelations[r],qb:this,db:this.db};await e[l](a)}return t}let a=t.map(e=>e[l.localKey]),s=[];const n="one"===l?.type;s=await this.fetchRelatedRows(l,a);let h=new Map;for(const e of s){let t=e[l.foreignKey];h.has(t)||h.set(t,[]),h.get(t).push(e)}for(const e of t){let t=e[l.localKey];n&&1==h.get(t)?.length?e[r]=h.get(t)[0]:e[r]=h.get(t)||[]}let c=Object.keys(o);for(const e of c){let i=o[e],l=t.filter(e=>n?e[r]:e[r].length>0).map(e=>e[r]).reduce((e,t)=>e.concat(t),[]),a=this.utils.getModel(this.controllerWrapper,r);await this.fetchAndAttachRelated({parentRows:l,relName:e,model:a,withTree:i,relation:a.hasRelations[e]})}return t}filterWhere(e,t=""){return t?Object.keys(e).filter(e=>e.includes(t)).reduce((r,i)=>(r[i.replace(t,"")]=e[i],r),{}):Object.keys(e).filter(e=>!e.includes(".")).reduce((t,r)=>(t[r]=e[r],t),{})}async getQuery(e,t){try{const{where:r={},with:i,withWhere:o,select:l,orderBy:a={column:"id",direction:"asc"},limit:s=10,offset:n=0,page:h,groupBy:c,having:p,distinct:y,join:u,leftJoin:f,rightJoin:d,innerJoin:g,count:w=!1}=t;let b=this.getQueryBuilder(e);l&&(Array.isArray(l)||"string"==typeof l)?b.select(l):b.select("*"),y&&(Array.isArray(y)||"string"==typeof y?b.distinct(y):b.distinct()),u&&this._applyJoins(b,u,"join"),f&&this._applyJoins(b,f,"leftJoin"),d&&this._applyJoins(b,d,"rightJoin"),g&&this._applyJoins(b,g,"innerJoin"),this._applyWhereClause(b,r,i),c&&(Array.isArray(c),b.groupBy(c)),p&&this._applyHavingClause(b,p),a&&this._applyOrderBy(b,a);let W=!1,A=s,m=n,_=1,k=0;h&&s>0&&(_=Math.max(1,parseInt(h)),m=(_-1)*s),s>0&&(b.limit(A),m>0&&b.offset(m));const j=await b;if(i&&i.length>0){const t=this.buildWithTree(i);for(const r of Object.keys(t))await this.fetchAndAttachRelated({parentRows:j,relName:r,model:e,withTree:t[r],relation:e.hasRelations[r]})}let B=null;if(s>0)try{let t=this.getQueryBuilder(e);r&&Object.keys(r).length>0&&this._applyWhereClause(t,r),u&&this._applyJoins(t,u,"join"),f&&this._applyJoins(t,f,"leftJoin"),d&&this._applyJoins(t,d,"rightJoin"),g&&this._applyJoins(t,g,"innerJoin");B=(await t.count("* as cnt").first()).cnt}catch(e){console.warn("Failed to get total count:",e.message),B=j.length}s>0&&null!==B&&(k=Math.ceil(B/s),W=_<k);return{data:j,totalCount:B,...s>0?{pagination:{page:_,limit:A,offset:m,totalPages:k,hasNext:W,hasPrev:_>1,nextPage:W?_+1:null,prevPage:_>1?_-1:null}}:{}}}catch(e){throw console.error("QueryService.getQuery error:",e),new Error(`Failed to execute query: ${e.message}`)}}_applyWhereWithArray(e,t,r){for(const i of t)this._applyWhereClause(e,i,r)}_applyWhereClause(e,t,r=[]){if(Array.isArray(t))this._applyWhereWithArray(e,t,r);else{if(t&&Object.keys(t).length>0){let r=this.helperUtility.getDotWalkQuery(t);console.log({filteredWhere:r}),r=this.helperUtility.objectFilter(r,(e,t)=>"object"!=typeof t||null===t);for(const[t,i]of Object.entries(r)){const{joinType:r="AND",column:o}=this.parseWhereColumn(t),{operator:l,value:a}=this.parseWhereValue(i);"AND"===r?this._applyAndWhereCondition(e,o,l,a):this._applyOrWhereCondition(e,o,l,a)}}r&&r.length>0&&this._applyNestedWhere(e,t,r)}}_applyNestedWhere(e,t,r){let i=this,o=e._getMyModel();if(r&&r.length>0)for(const l of r){let a=this.helperUtility.getDotWalkQuery(t,l);if(a&&Object.keys(a).length>0){let t=this.utils.getModel(this.controllerWrapper,l),s=o.hasRelations[l],n=r.map(e=>this.helperUtility.pluckDotWalkKey(e,1));e.whereExists(function(){let e=i.getQueryBuilder(t,this.select("*").from(t.table));e.whereRaw(`${s.foreignKey} = ${o.table}.${s.localKey}`),i._applyWhereClause(e,a,n)})}}}_applyJoins(e,t,r){const i=Array.isArray(t)?t:[t];for(const t of i)"string"==typeof t?e[r](t):"object"==typeof t&&(t.table&&t.on?e[r](t.table,t.on):t.table&&t.first&&t.operator&&t.second&&e[r](t.table,t.first,t.operator,t.second))}_applyWithWhere(e,t){try{if(Array.isArray(t))for(const r of t)"string"==typeof r?e.withWhere(r):"object"==typeof r&&e.withWhere(r.column,r.operator,r.value);else if("object"==typeof t)for(const[r,i]of Object.entries(t))e.withWhere(r,i)}catch(e){console.warn("Failed to apply withWhere:",e.message)}}_applyHavingClause(e,t){for(const[r,i]of Object.entries(t))"object"==typeof i&&i.operator?e.having(r,i.operator,i.value):e.having(r,i)}_applyOrderBy(e,t){if(Array.isArray(t))for(const r of t)"string"==typeof r?e.orderBy(r):"object"==typeof r&&e.orderBy(r.column,r.direction||"asc");else"string"==typeof t?e.orderBy(t):"object"==typeof t&&e.orderBy(t.column,t.direction||"asc")}}module.exports=QueryBuilder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const path=require("path");class HookService{constructor(e,t,o=null){this.db=e,this.utils=t,this.controllerWrapper=o,this.appRoot=o&&o.resolverPath?o.resolverPath:process.cwd()}loadModelClass(e){try{const t=path.join(this.appRoot,"models",`${e}.model.js`);delete require.cache[require.resolve(t)];return require(t)}catch(e){return void console.log("loadModelClass error",{err:e})}}getModelInstance(e){
|
|
1
|
+
const path=require("path");class HookService{constructor(e,t,o=null){this.db=e,this.utils=t,this.controllerWrapper=o,this.controllerWrapper.hookService=this,this.appRoot=o&&o.resolverPath?o.resolverPath:process.cwd()}loadModelClass(e){try{const t=path.join(this.appRoot,"models",`${e}.model.js`);delete require.cache[require.resolve(t)];return require(t)}catch(e){return void console.log("loadModelClass error",{err:e})}}getModelInstance(e){let t="string"==typeof e?e:e.modelName;const o=this.loadModelClass(t);if(o)return"function"==typeof o?new o:o}resolveModelHook(e,t,o){const r=e.modelName,s=this.loadModelClass(r);if(!s)return;const l=this.getModelInstance(e);let i;if("validate"===t)i="validate";else if("on"===t)i=`on${o.charAt(0).toUpperCase()+o.slice(1)}`;else if("before"===t)i=`before${o.charAt(0).toUpperCase()+o.slice(1)}`;else if("after"===t)i=`after${o.charAt(0).toUpperCase()+o.slice(1)}`;else{if("custom"!==t)return;i=`on${o.charAt(0).toUpperCase()+o.slice(1)}Action`}return"function"==typeof l[i]?l[i].bind(l):"function"==typeof s[i]?s[i].bind(s):void 0}async executeValidatorHook({model:e,action:t,request:o,ctx:r,controller:s}){const l=this.resolveModelHook(e,"validate",t);if(l)return await l({model:e,action:t,request:o,context:r,db:this.db,utils:this.utils,controller:s})}async executeBeforeHook({model:e,action:t,request:o,ctx:r,controller:s}){const l=this.resolveModelHook(e,"before",t);if(l)return await l({model:e,action:t,request:o,context:r,db:this.db,utils:this.utils,controller:s})}async executeAfterHook({model:e,action:t,data:o,request:r,ctx:s,controller:l}){const i=this.resolveModelHook(e,"after",t);return i?await i({model:e,action:t,data:o,request:r,context:s,db:this.db,utils:this.utils,controller:l}):o}async executeCustomAction({model:e,action:t,request:o,ctx:r,controller:s}){const l=this.resolveModelHook(e,"custom",t);if(l)return await l({model:e,action:t,request:o,context:r,db:this.db,utils:this.utils,controller:s});throw new Error(`No custom action hook found for ${e.modelName}.${t}`)}async executeHasSoftDeleteHook(e){const t=this.getModelInstance(e);if(!t)return!1;return t.hasOwnProperty("hasSoftDelete")&&!0===t.hasSoftDelete}}module.exports=HookService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const HelperUtility=require("./HelperUtility");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getQueryBuilder(e,t=null){let r=this.db(e.table);return t&&(r=t),r._getMyModel=()=>e,r}parseValue(e){return this.helperUtility.parseValue(e)}parseWhereValue(e){return this.helperUtility.parseWhereValue(e)}parseWhereColumn(e){return this.helperUtility.parseWhereColumn(e)}_applyOrWhereCondition(e,t,r,i){if(null!==i)if(Array.isArray(i))e.orWhereIn(t,i);else switch(r){case"between":e.orWhereBetween(t,i);break;case"notBetween":e.orWhereNotBetween(t,i);break;case"in":e.orWhereIn(t,i);break;case"notIn":e.orWhereNotIn(t,i);break;case"like":e.orWhere(t,"like",i);break;default:e.orWhere(t,r,i)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,i){if(null!==i)if(Array.isArray(i))e.whereIn(t,i);else switch(r){case"between":e.whereBetween(t,i);break;case"notBetween":e.whereNotBetween(t,i);break;case"in":e.whereIn(t,i);break;case"notIn":e.whereNotIn(t,i);break;case"like":e.where(t,"like",i);break;default:e.where(t,r,i)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:i})=>t?t({current:e,part:r,source:i}):{}})}async fetchRelatedRows(e,t){if(e.through){let r=await this.db(e.through).whereIn(e.throughLocalKey,t);return await this.db(e.table).whereIn(e.foreignKey,r.map(t=>t[e.throughForeignKey]))}return this.db(e.table).whereIn(e.foreignKey,t)}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:i,withTree:
|
|
1
|
+
const HelperUtility=require("./HelperUtility");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getHookService(){return this.controllerWrapper.hookService}getQueryBuilder(e,t=null){let r=this.db(e.table);return t&&(r=t),r._getMyModel=()=>e,r}parseValue(e){return this.helperUtility.parseValue(e)}parseWhereValue(e){return this.helperUtility.parseWhereValue(e)}parseWhereColumn(e){return this.helperUtility.parseWhereColumn(e)}_applyOrWhereCondition(e,t,r,i){if(null!==i)if(Array.isArray(i))e.orWhereIn(t,i);else switch(r){case"between":e.orWhereBetween(t,i);break;case"notBetween":e.orWhereNotBetween(t,i);break;case"in":e.orWhereIn(t,i);break;case"notIn":e.orWhereNotIn(t,i);break;case"like":e.orWhere(t,"like",i);break;default:e.orWhere(t,r,i)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,i){if(null!==i)if(Array.isArray(i))e.whereIn(t,i);else switch(r){case"between":e.whereBetween(t,i);break;case"notBetween":e.whereNotBetween(t,i);break;case"in":e.whereIn(t,i);break;case"notIn":e.whereNotIn(t,i);break;case"like":e.where(t,"like",i);break;default:e.where(t,r,i)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:i})=>t?t({current:e,part:r,source:i}):{}})}async fetchRelatedRows(e,t){if(e.through){let r=await this.db(e.through).whereIn(e.throughLocalKey,t);return await this.db(e.table).whereIn(e.foreignKey,r.map(t=>t[e.throughForeignKey]))}return this.db(e.table).whereIn(e.foreignKey,t)}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:i,withTree:o,relation:l}=e;if(!l){const e=this.getHookService().getModelInstance(i),l=`get${r.charAt(0).toUpperCase()+r.slice(1)}Relation`;if("function"==typeof e[l]){const a={rows:t,relName:r,model:i,withTree:o,controller:this.controllerWrapper,relation:i.hasRelations[r],qb:this,db:this.db};await e[l](a)}return t}let a=t.map(e=>e[l.localKey]);const s="one"===l?.type;let n=[];n=await this.fetchRelatedRows(l,a);let h=new Map;for(const e of n){let t=e[l.foreignKey];h.has(t)||h.set(t,[]),h.get(t).push(e)}for(const e of t){let t=e[l.localKey];s&&1==h.get(t)?.length?e[r]=h.get(t)[0]:e[r]=h.get(t)||[]}let c=Object.keys(o);for(const e of c){let i=o[e],l=t.filter(e=>s?e[r]:e[r].length>0).map(e=>e[r]).reduce((e,t)=>e.concat(t),[]),a=this.utils.getModel(this.controllerWrapper,r);await this.fetchAndAttachRelated({parentRows:l,relName:e,model:a,withTree:i,relation:a.hasRelations[e]})}return t}filterWhere(e,t=""){return t?Object.keys(e).filter(e=>e.includes(t)).reduce((r,i)=>(r[i.replace(t,"")]=e[i],r),{}):Object.keys(e).filter(e=>!e.includes(".")).reduce((t,r)=>(t[r]=e[r],t),{})}async getQuery(e,t){try{const{where:r={},with:i,withWhere:o,select:l,orderBy:a={column:"id",direction:"asc"},limit:s=10,offset:n=0,page:h,groupBy:c,having:p,distinct:y,join:u,leftJoin:f,rightJoin:d,innerJoin:g,count:w=!1}=t;let b=this.getQueryBuilder(e);l&&(Array.isArray(l)||"string"==typeof l)?b.select(l):b.select("*"),y&&(Array.isArray(y)||"string"==typeof y?b.distinct(y):b.distinct()),u&&this._applyJoins(b,u,"join"),f&&this._applyJoins(b,f,"leftJoin"),d&&this._applyJoins(b,d,"rightJoin"),g&&this._applyJoins(b,g,"innerJoin"),this._applyWhereClause(b,r,i),c&&(Array.isArray(c),b.groupBy(c)),p&&this._applyHavingClause(b,p),a&&this._applyOrderBy(b,a);let W=!1,A=s,k=n,m=1,_=0;h&&s>0&&(m=Math.max(1,parseInt(h)),k=(m-1)*s),s>0&&(b.limit(A),k>0&&b.offset(k));const j=await b;if(i&&i.length>0){const t=this.buildWithTree(i);for(const r of Object.keys(t))await this.fetchAndAttachRelated({parentRows:j,relName:r,model:e,withTree:t[r],relation:e.hasRelations[r]})}let B=null;if(s>0)try{let t=this.getQueryBuilder(e);r&&Object.keys(r).length>0&&this._applyWhereClause(t,r),u&&this._applyJoins(t,u,"join"),f&&this._applyJoins(t,f,"leftJoin"),d&&this._applyJoins(t,d,"rightJoin"),g&&this._applyJoins(t,g,"innerJoin");B=(await t.count("* as cnt").first()).cnt}catch(e){console.warn("Failed to get total count:",e.message),B=j.length}s>0&&null!==B&&(_=Math.ceil(B/s),W=m<_);return{data:j,totalCount:B,...s>0?{pagination:{page:m,limit:A,offset:k,totalPages:_,hasNext:W,hasPrev:m>1,nextPage:W?m+1:null,prevPage:m>1?m-1:null}}:{}}}catch(e){throw console.error("QueryService.getQuery error:",e),new Error(`Failed to execute query: ${e.message}`)}}_applyWhereWithArray(e,t,r){for(const i of t)this._applyWhereClause(e,i,r)}_applyWhereClause(e,t,r=[]){if(Array.isArray(t))this._applyWhereWithArray(e,t,r);else{if(t&&Object.keys(t).length>0){let r=this.helperUtility.getDotWalkQuery(t);console.log({filteredWhere:r}),r=this.helperUtility.objectFilter(r,(e,t)=>"object"!=typeof t||null===t);for(const[t,i]of Object.entries(r)){const{joinType:r="AND",column:o}=this.parseWhereColumn(t),{operator:l,value:a}=this.parseWhereValue(i);"AND"===r?this._applyAndWhereCondition(e,o,l,a):this._applyOrWhereCondition(e,o,l,a)}}r&&r.length>0&&this._applyNestedWhere(e,t,r)}}_applyNestedWhere(e,t,r){let i=this,o=e._getMyModel();if(r&&r.length>0)for(const l of r){let a=this.helperUtility.getDotWalkQuery(t,l);if(a&&Object.keys(a).length>0){let t=this.utils.getModel(this.controllerWrapper,l),s=o.hasRelations[l],n=r.map(e=>this.helperUtility.pluckDotWalkKey(e,1));e.whereExists(function(){let e=i.getQueryBuilder(t,this.select("*").from(t.table));e.whereRaw(`${s.foreignKey} = ${o.table}.${s.localKey}`),i._applyWhereClause(e,a,n)})}}}_applyJoins(e,t,r){const i=Array.isArray(t)?t:[t];for(const t of i)"string"==typeof t?e[r](t):"object"==typeof t&&(t.table&&t.on?e[r](t.table,t.on):t.table&&t.first&&t.operator&&t.second&&e[r](t.table,t.first,t.operator,t.second))}_applyWithWhere(e,t){try{if(Array.isArray(t))for(const r of t)"string"==typeof r?e.withWhere(r):"object"==typeof r&&e.withWhere(r.column,r.operator,r.value);else if("object"==typeof t)for(const[r,i]of Object.entries(t))e.withWhere(r,i)}catch(e){console.warn("Failed to apply withWhere:",e.message)}}_applyHavingClause(e,t){for(const[r,i]of Object.entries(t))"object"==typeof i&&i.operator?e.having(r,i.operator,i.value):e.having(r,i)}_applyOrderBy(e,t){if(Array.isArray(t))for(const r of t)"string"==typeof r?e.orderBy(r):"object"==typeof r&&e.orderBy(r.column,r.direction||"asc");else"string"==typeof t?e.orderBy(t):"object"==typeof t&&e.orderBy(t.column,t.direction||"asc")}}module.exports=QueryBuilder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const QueryService=require("./QueryService"),HookService=require("./HookService");class CurdTable{constructor(e,r,t=null){if(this.db=e,this.utils=r,this.controllerWrapper=t,this.hookService=new HookService(e,r,t),this.queryService=new QueryService(e,r,t),!this.queryService)throw new Error("CurdTable requires queryService (execute*Query / getQuery).")}async processRequest(e,r=null,t={}){
|
|
1
|
+
const QueryService=require("./QueryService"),HookService=require("./HookService");class CurdTable{constructor(e,r,t=null){if(this.db=e,this.utils=r,this.controllerWrapper=t,this.hookService=new HookService(e,r,t),this.queryService=new QueryService(e,r,t),!this.queryService)throw new Error("CurdTable requires queryService (execute*Query / getQuery).")}async processRequest(e,r=null,t={}){const o=this.controllerWrapper;let s=this.utils.getModel(this.controllerWrapper,r);const c=e?.action||"list";let i=null;const a={model:s,action:c,request:e,ctx:t,controller:o};switch(this.hookService?.executeValidatorHook&&await this.hookService.executeValidatorHook({...a}),this.hookService?.executeBeforeHook&&(e.beforeActionData=await this.hookService.executeBeforeHook({...a})),c){case"count":i=await this.queryService.executeCountQuery(s,e);break;case"list":i=await this.hookService.executeHasSoftDeleteHook(s)?await this.queryService.getSoftDeleteQuery(s,e):await this.queryService.getQuery(s,e);break;case"show":i=await this.queryService.executeShowQuery(s,e);break;case"create":i=await this.queryService.executeCreateQuery(s,e);break;case"update":{const r=await this.queryService.executeUpdateQuery(s,e);if(!r)throw new Error(`Record not found or not updated: ${s.table} returned ${r}`);i={message:"Record updated successfully",data:r,success:!0};break}case"replace":if(i=await this.queryService.executeReplaceQuery(s,e),!i)throw new Error(`Record not found or not replaced: ${r} returned ${i}`);i={message:"Record replaced successfully",data:i,success:!0};break;case"upsert":if(i=await this.queryService.executeUpsertQuery(s,e),!i)throw new Error(`Record not found or not upserted: ${r} returned ${i}`);i={message:"Record upserted successfully",data:i,success:!0};break;case"sync":if(i=await this.queryService.executeSyncQuery(s,e),!i)throw new Error(`Record not found or not synced: ${r} returned ${i}`);i={message:"Record synced successfully",data:i,success:!0};break;case"delete":{let t=null;if(t=await this.hookService.executeHasSoftDeleteHook(s)?await this.queryService.executeSoftDeleteQuery(s,e):await this.queryService.executeDeleteQuery(s,e),!t)throw new Error(`Record not found or not deleted: ${r} returned ${t}`);i={message:"Record deleted successfully",data:t,success:!0};break}default:if(!this.hookService?.executeCustomAction)throw new Error(`Unknown action "${c}" and no custom action hook provided.`);i=await this.hookService.executeCustomAction({...a})}if(this.hookService?.executeAfterHook&&(i=await this.hookService.executeAfterHook({...a,data:i})),e?.other_requests&&"object"==typeof e.other_requests){const r={},o=Object.entries(e.other_requests);for(const[e,s]of o)Array.isArray(s)?r[e]=await Promise.all(s.map(r=>this.processRequest(r,e,t))):r[e]=await this.processRequest(s,e,t);i.other_responses=r}return i}}module.exports=CurdTable;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const path=require("path");class HookService{constructor(e,t,o=null){this.db=e,this.utils=t,this.controllerWrapper=o,this.appRoot=o&&o.resolverPath?o.resolverPath:process.cwd()}loadModelClass(e){try{const t=path.join(this.appRoot,"models",`${e}.model.js`);delete require.cache[require.resolve(t)];return require(t)}catch(e){return}}getModelInstance(e){
|
|
1
|
+
const path=require("path");class HookService{constructor(e,t,o=null){this.db=e,this.utils=t,this.controllerWrapper=o,this.controllerWrapper.hookService=this,this.appRoot=o&&o.resolverPath?o.resolverPath:process.cwd()}loadModelClass(e){try{const t=path.join(this.appRoot,"models",`${e}.model.js`);delete require.cache[require.resolve(t)];return require(t)}catch(e){return}}getModelInstance(e){let t="string"==typeof e?e:e.modelName||e.name;const o=this.loadModelClass(t);if(o)return"function"==typeof o?new o:o}resolveModelHook(e,t,o){const r=e.modelName||e.name,s=this.loadModelClass(r);if(!s)return;const l=this.getModelInstance(e);let i;if("validate"===t)i="validate";else if("on"===t)i=`on${o.charAt(0).toUpperCase()+o.slice(1)}`;else if("before"===t)i=`before${o.charAt(0).toUpperCase()+o.slice(1)}`;else if("after"===t)i=`after${o.charAt(0).toUpperCase()+o.slice(1)}`;else{if("custom"!==t)return;i=`on${o.charAt(0).toUpperCase()+o.slice(1)}Action`}return"function"==typeof l[i]?l[i].bind(l):"function"==typeof s[i]?s[i].bind(s):void 0}async executeValidatorHook({model:e,action:t,request:o,ctx:r,controller:s}){const l=this.resolveModelHook(e,"validate",t);if(l)return await l({model:e,action:t,request:o,context:r,db:this.db,utils:this.utils,controller:s})}async executeBeforeHook({model:e,action:t,request:o,ctx:r,controller:s}){const l=this.resolveModelHook(e,"before",t);if(l)return await l({model:e,action:t,request:o,context:r,db:this.db,utils:this.utils,controller:s})}async executeAfterHook({model:e,action:t,data:o,request:r,ctx:s,controller:l}){const i=this.resolveModelHook(e,"after",t);return i?await i({model:e,action:t,data:o,request:r,context:s,db:this.db,utils:this.utils,controller:l}):o}async executeCustomAction({model:e,action:t,request:o,ctx:r,controller:s}){const l=this.resolveModelHook(e,"custom",t);if(l)return await l({model:e,action:t,request:o,context:r,db:this.db,utils:this.utils,controller:s});throw new Error(`No custom action hook found for ${e.modelName||e.name}.${t}`)}async executeHasSoftDeleteHook(e){const t=this.getModelInstance(e);if(!t)return!1;return t.hasOwnProperty("hasSoftDelete")&&!0===t.hasSoftDelete}}module.exports=HookService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const HelperUtility=require("./HelperUtility");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getQueryBuilder(e){
|
|
1
|
+
const HelperUtility=require("./HelperUtility");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getHookService(){return this.controllerWrapper.hookService}getQueryBuilder(e,t=null){let r=this.db(e.table);return t&&(r=t),r._getMyModel=()=>e,r}parseValue(e){return this.helperUtility.parseValue(e)}parseWhereValue(e){return this.helperUtility.parseWhereValue(e)}parseWhereColumn(e){return this.helperUtility.parseWhereColumn(e)}_applyOrWhereCondition(e,t,r,i){if(null!==i)if(Array.isArray(i))e.orWhereIn(t,i);else switch(r){case"between":e.orWhereBetween(t,i);break;case"notBetween":e.orWhereNotBetween(t,i);break;case"in":e.orWhereIn(t,i);break;case"notIn":e.orWhereNotIn(t,i);break;case"like":e.orWhere(t,"like",i);break;default:e.orWhere(t,r,i)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,i){if(null!==i)if(Array.isArray(i))e.whereIn(t,i);else switch(r){case"between":e.whereBetween(t,i);break;case"notBetween":e.whereNotBetween(t,i);break;case"in":e.whereIn(t,i);break;case"notIn":e.whereNotIn(t,i);break;case"like":e.where(t,"like",i);break;default:e.where(t,r,i)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:i})=>t?t({current:e,part:r,source:i}):{}})}async fetchRelatedRows(e,t){if(e.through){let r=await this.db(e.through).whereIn(e.throughLocalKey,t);return await this.db(e.table).whereIn(e.foreignKey,r.map(t=>t[e.throughForeignKey]))}return this.db(e.table).whereIn(e.foreignKey,t)}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:i,withTree:o,relation:l}=e;if(!l){const e=this.getHookService().getModelInstance(i),l=`get${r.charAt(0).toUpperCase()+r.slice(1)}Relation`;if("function"==typeof e[l]){const a={rows:t,relName:r,model:i,withTree:o,controller:this.controllerWrapper,relation:i.hasRelations[r],qb:this,db:this.db};await e[l](a)}return t}let a=t.map(e=>e[l.localKey]);const s="one"===l?.type;let n=[];n=await this.fetchRelatedRows(l,a);let h=new Map;for(const e of n){let t=e[l.foreignKey];h.has(t)||h.set(t,[]),h.get(t).push(e)}for(const e of t){let t=e[l.localKey];s&&1==h.get(t)?.length?e[r]=h.get(t)[0]:e[r]=h.get(t)||[]}let c=Object.keys(o);for(const e of c){let i=o[e],l=t.filter(e=>s?e[r]:e[r].length>0).map(e=>e[r]).reduce((e,t)=>e.concat(t),[]),a=this.utils.getModel(this.controllerWrapper,r);await this.fetchAndAttachRelated({parentRows:l,relName:e,model:a,withTree:i,relation:a.hasRelations[e]})}return t}filterWhere(e,t=""){return t?Object.keys(e).filter(e=>e.includes(t)).reduce((r,i)=>(r[i.replace(t,"")]=e[i],r),{}):Object.keys(e).filter(e=>!e.includes(".")).reduce((t,r)=>(t[r]=e[r],t),{})}async getQuery(e,t){try{const{where:r={},with:i,withWhere:o,select:l,orderBy:a={column:"id",direction:"asc"},limit:s=10,offset:n=0,page:h,groupBy:c,having:p,distinct:y,join:u,leftJoin:f,rightJoin:d,innerJoin:g,count:w=!1}=t;let b=this.getQueryBuilder(e);l&&(Array.isArray(l)||"string"==typeof l)?b.select(l):b.select("*"),y&&(Array.isArray(y)||"string"==typeof y?b.distinct(y):b.distinct()),u&&this._applyJoins(b,u,"join"),f&&this._applyJoins(b,f,"leftJoin"),d&&this._applyJoins(b,d,"rightJoin"),g&&this._applyJoins(b,g,"innerJoin"),this._applyWhereClause(b,r,i),c&&(Array.isArray(c),b.groupBy(c)),p&&this._applyHavingClause(b,p),a&&this._applyOrderBy(b,a);let W=!1,A=s,k=n,m=1,_=0;h&&s>0&&(m=Math.max(1,parseInt(h)),k=(m-1)*s),s>0&&(b.limit(A),k>0&&b.offset(k));const j=await b;if(i&&i.length>0){const t=this.buildWithTree(i);for(const r of Object.keys(t))await this.fetchAndAttachRelated({parentRows:j,relName:r,model:e,withTree:t[r],relation:e.hasRelations[r]})}let B=null;if(s>0)try{let t=this.getQueryBuilder(e);r&&Object.keys(r).length>0&&this._applyWhereClause(t,r),u&&this._applyJoins(t,u,"join"),f&&this._applyJoins(t,f,"leftJoin"),d&&this._applyJoins(t,d,"rightJoin"),g&&this._applyJoins(t,g,"innerJoin");B=(await t.count("* as cnt").first()).cnt}catch(e){console.warn("Failed to get total count:",e.message),B=j.length}s>0&&null!==B&&(_=Math.ceil(B/s),W=m<_);return{data:j,totalCount:B,...s>0?{pagination:{page:m,limit:A,offset:k,totalPages:_,hasNext:W,hasPrev:m>1,nextPage:W?m+1:null,prevPage:m>1?m-1:null}}:{}}}catch(e){throw console.error("QueryService.getQuery error:",e),new Error(`Failed to execute query: ${e.message}`)}}_applyWhereWithArray(e,t,r){for(const i of t)this._applyWhereClause(e,i,r)}_applyWhereClause(e,t,r=[]){if(Array.isArray(t))this._applyWhereWithArray(e,t,r);else{if(t&&Object.keys(t).length>0){let r=this.helperUtility.getDotWalkQuery(t);r=this.helperUtility.objectFilter(r,(e,t)=>"object"!=typeof t||null===t);for(const[t,i]of Object.entries(r)){const{joinType:r="AND",column:o}=this.parseWhereColumn(t),{operator:l,value:a}=this.parseWhereValue(i);"AND"===r?this._applyAndWhereCondition(e,o,l,a):this._applyOrWhereCondition(e,o,l,a)}}r&&r.length>0&&this._applyNestedWhere(e,t,r)}}_applyNestedWhere(e,t,r){let i=this,o=e._getMyModel();if(r&&r.length>0)for(const l of r){let a=this.helperUtility.getDotWalkQuery(t,l);if(a&&Object.keys(a).length>0){let t=this.utils.getModel(this.controllerWrapper,l),s=o.hasRelations[l],n=r.map(e=>this.helperUtility.pluckDotWalkKey(e,1));e.whereExists(function(){let e=i.getQueryBuilder(t,this.select("*").from(t.table));e.whereRaw(`${s.foreignKey} = ${o.table}.${s.localKey}`),i._applyWhereClause(e,a,n)})}}}_applyJoins(e,t,r){const i=Array.isArray(t)?t:[t];for(const t of i)"string"==typeof t?e[r](t):"object"==typeof t&&(t.table&&t.on?e[r](t.table,t.on):t.table&&t.first&&t.operator&&t.second&&e[r](t.table,t.first,t.operator,t.second))}_applyWithWhere(e,t){try{if(Array.isArray(t))for(const r of t)"string"==typeof r?e.withWhere(r):"object"==typeof r&&e.withWhere(r.column,r.operator,r.value);else if("object"==typeof t)for(const[r,i]of Object.entries(t))e.withWhere(r,i)}catch(e){console.warn("Failed to apply withWhere:",e.message)}}_applyHavingClause(e,t){for(const[r,i]of Object.entries(t))"object"==typeof i&&i.operator?e.having(r,i.operator,i.value):e.having(r,i)}_applyOrderBy(e,t){if(Array.isArray(t))for(const r of t)"string"==typeof r?e.orderBy(r):"object"==typeof r&&e.orderBy(r.column,r.direction||"asc");else"string"==typeof t?e.orderBy(t):"object"==typeof t&&e.orderBy(t.column,t.direction||"asc")}}module.exports=QueryBuilder;
|
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.47",
|
|
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",
|