@dreamtree-org/korm-js 1.0.49 → 1.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- 🎣 **Model Hooks**: Before, After, Validate, and Custom action hooks
|
|
20
20
|
- 🔗 **Custom Relations**: Define custom relation hooks for complex data fetching
|
|
21
21
|
- 📦 **Nested Requests**: Process multiple related requests in a single call
|
|
22
|
+
- 📝 **Logger Utility**: Configurable logging with multiple log levels
|
|
22
23
|
|
|
23
24
|
## Installation
|
|
24
25
|
|
|
@@ -812,6 +813,108 @@ POST /api/Post/crud
|
|
|
812
813
|
}
|
|
813
814
|
```
|
|
814
815
|
|
|
816
|
+
#### Filtering Eager Loaded Relations with `withWhere`
|
|
817
|
+
|
|
818
|
+
The `withWhere` parameter allows you to apply additional where conditions when fetching related data. This filters the related rows without affecting the parent query.
|
|
819
|
+
|
|
820
|
+
```javascript
|
|
821
|
+
// Get all users with their posts, but only posts from user_id = 15
|
|
822
|
+
POST /api/User/crud
|
|
823
|
+
{
|
|
824
|
+
"action": "list",
|
|
825
|
+
"with": ["Post"],
|
|
826
|
+
"withWhere": {
|
|
827
|
+
"Post.user_id": 15
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Get users with active posts only
|
|
832
|
+
POST /api/User/crud
|
|
833
|
+
{
|
|
834
|
+
"action": "list",
|
|
835
|
+
"with": ["Post"],
|
|
836
|
+
"withWhere": {
|
|
837
|
+
"Post.status": "active",
|
|
838
|
+
"Post.is_published": true
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Multiple relations with different filters
|
|
843
|
+
POST /api/User/crud
|
|
844
|
+
{
|
|
845
|
+
"action": "list",
|
|
846
|
+
"with": ["Post", "Comment"],
|
|
847
|
+
"withWhere": {
|
|
848
|
+
"Post.status": "published",
|
|
849
|
+
"Post.created_at": ">=2024-01-01",
|
|
850
|
+
"Comment.is_approved": true
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Using operators in withWhere (same as regular where)
|
|
855
|
+
POST /api/User/crud
|
|
856
|
+
{
|
|
857
|
+
"action": "list",
|
|
858
|
+
"with": ["Post"],
|
|
859
|
+
"withWhere": {
|
|
860
|
+
"Post.title": "%tutorial%",
|
|
861
|
+
"Post.views": ">=100",
|
|
862
|
+
"Post.category": "[]tech,programming"
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Using OR conditions in withWhere
|
|
867
|
+
POST /api/User/crud
|
|
868
|
+
{
|
|
869
|
+
"action": "list",
|
|
870
|
+
"with": ["Post"],
|
|
871
|
+
"withWhere": {
|
|
872
|
+
"Post.status": "published",
|
|
873
|
+
"Or:Post.is_featured": true
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
**Key differences between `where` and `withWhere`:**
|
|
879
|
+
|
|
880
|
+
| Feature | `where` (Nested) | `withWhere` |
|
|
881
|
+
|---------|-----------------|-------------|
|
|
882
|
+
| Affects parent query | Yes (filters parent rows) | No (only filters related rows) |
|
|
883
|
+
| Purpose | Filter parents that have matching relations | Filter which related rows are loaded |
|
|
884
|
+
| SQL generated | `WHERE EXISTS (subquery)` | Additional `WHERE` on relation query |
|
|
885
|
+
|
|
886
|
+
**Example showing the difference:**
|
|
887
|
+
|
|
888
|
+
```javascript
|
|
889
|
+
// This filters users who have published posts (affects which users are returned)
|
|
890
|
+
{
|
|
891
|
+
"action": "list",
|
|
892
|
+
"where": { "Post.status": "published" },
|
|
893
|
+
"with": ["Post"]
|
|
894
|
+
}
|
|
895
|
+
// Returns: Only users who have at least one published post
|
|
896
|
+
// Each user's Post array contains ALL their posts (not filtered)
|
|
897
|
+
|
|
898
|
+
// This loads all users but only their published posts
|
|
899
|
+
{
|
|
900
|
+
"action": "list",
|
|
901
|
+
"with": ["Post"],
|
|
902
|
+
"withWhere": { "Post.status": "published" }
|
|
903
|
+
}
|
|
904
|
+
// Returns: All users
|
|
905
|
+
// Each user's Post array contains ONLY their published posts
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
**withWhere supports all operators:**
|
|
909
|
+
- Comparison: `">=18"`, `"<=100"`, `">50"`, `"<10"`, `"!deleted"`
|
|
910
|
+
- LIKE: `"%pattern%"`
|
|
911
|
+
- IN: `"[]val1,val2,val3"`
|
|
912
|
+
- NOT IN: `"![]val1,val2"`
|
|
913
|
+
- BETWEEN: `"><min,max"`
|
|
914
|
+
- NOT BETWEEN: `"<>min,max"`
|
|
915
|
+
- NULL: `null`
|
|
916
|
+
- OR prefix: `"Or:column"`
|
|
917
|
+
|
|
815
918
|
### Example: Complete Relationship Structure
|
|
816
919
|
|
|
817
920
|
Example complete relationship structure:
|
|
@@ -1423,6 +1526,7 @@ const modelInstance = korm.getModelInstance(modelDef);
|
|
|
1423
1526
|
| `offset` | number | Records to skip |
|
|
1424
1527
|
| `page` | number | Page number (alternative to offset) |
|
|
1425
1528
|
| `with` | array | Related models to eager load |
|
|
1529
|
+
| `withWhere` | object | Filter conditions for eager loaded relations |
|
|
1426
1530
|
| `groupBy` | array/string | Group by columns |
|
|
1427
1531
|
| `having` | object | Having conditions |
|
|
1428
1532
|
| `distinct` | boolean/array/string | Distinct results |
|
|
@@ -1486,6 +1590,7 @@ const {
|
|
|
1486
1590
|
helperUtility, // Utility functions (file operations, string manipulation)
|
|
1487
1591
|
emitter, // Event emitter instance
|
|
1488
1592
|
validate, // Validation function
|
|
1593
|
+
logger, // Logger utility instance
|
|
1489
1594
|
lib, // Additional utilities
|
|
1490
1595
|
LibClasses // Library classes (Emitter)
|
|
1491
1596
|
} = require('@dreamtree-org/korm-js');
|
|
@@ -1504,6 +1609,118 @@ const {
|
|
|
1504
1609
|
// - createDirectory(path) - Create directory
|
|
1505
1610
|
```
|
|
1506
1611
|
|
|
1612
|
+
## Logger Utility
|
|
1613
|
+
|
|
1614
|
+
KORM includes a built-in logger utility with configurable log levels for debugging and monitoring.
|
|
1615
|
+
|
|
1616
|
+
### Basic Usage
|
|
1617
|
+
|
|
1618
|
+
```javascript
|
|
1619
|
+
const { logger } = require('@dreamtree-org/korm-js');
|
|
1620
|
+
|
|
1621
|
+
// Log methods (from most to least verbose)
|
|
1622
|
+
logger.debug('Detailed debugging information');
|
|
1623
|
+
logger.log('General log message');
|
|
1624
|
+
logger.info('Informational message');
|
|
1625
|
+
logger.warn('Warning message');
|
|
1626
|
+
logger.error('Error message');
|
|
1627
|
+
```
|
|
1628
|
+
|
|
1629
|
+
### Log Levels
|
|
1630
|
+
|
|
1631
|
+
| Level | Value | Description |
|
|
1632
|
+
|-------|-------|-------------|
|
|
1633
|
+
| `NONE` | 0 | Disable all logging |
|
|
1634
|
+
| `ERROR` | 1 | Only errors |
|
|
1635
|
+
| `WARN` | 2 | Warnings and errors (default) |
|
|
1636
|
+
| `INFO` | 3 | Info, warnings, and errors |
|
|
1637
|
+
| `LOG` | 4 | General logs and above |
|
|
1638
|
+
| `DEBUG` | 5 | All messages (most verbose) |
|
|
1639
|
+
|
|
1640
|
+
### Configuration
|
|
1641
|
+
|
|
1642
|
+
```javascript
|
|
1643
|
+
const { logger } = require('@dreamtree-org/korm-js');
|
|
1644
|
+
|
|
1645
|
+
// Set log level programmatically
|
|
1646
|
+
logger.setLevel('debug'); // Show all logs
|
|
1647
|
+
logger.setLevel('warn'); // Only warnings and errors (default)
|
|
1648
|
+
logger.setLevel('error'); // Only errors
|
|
1649
|
+
logger.setLevel('none'); // Disable all logging
|
|
1650
|
+
|
|
1651
|
+
// Enable/disable logging
|
|
1652
|
+
logger.disable(); // Temporarily disable all logging
|
|
1653
|
+
logger.enable(); // Re-enable logging
|
|
1654
|
+
```
|
|
1655
|
+
|
|
1656
|
+
### Environment Variable
|
|
1657
|
+
|
|
1658
|
+
Set the log level via environment variable:
|
|
1659
|
+
|
|
1660
|
+
```bash
|
|
1661
|
+
# In your .env file or environment
|
|
1662
|
+
KORM_LOG_LEVEL=debug # Show all logs
|
|
1663
|
+
KORM_LOG_LEVEL=warn # Only warnings and errors (default)
|
|
1664
|
+
KORM_LOG_LEVEL=error # Only errors
|
|
1665
|
+
KORM_LOG_LEVEL=none # Disable logging
|
|
1666
|
+
```
|
|
1667
|
+
|
|
1668
|
+
```javascript
|
|
1669
|
+
// The logger automatically reads KORM_LOG_LEVEL on initialization
|
|
1670
|
+
// KORM_LOG_LEVEL=debug node app.js
|
|
1671
|
+
```
|
|
1672
|
+
|
|
1673
|
+
### Creating Child Loggers
|
|
1674
|
+
|
|
1675
|
+
Create child loggers with custom prefixes for different modules:
|
|
1676
|
+
|
|
1677
|
+
```javascript
|
|
1678
|
+
const { logger } = require('@dreamtree-org/korm-js');
|
|
1679
|
+
|
|
1680
|
+
// Create a child logger for a specific module
|
|
1681
|
+
const authLogger = logger.child('[Auth]');
|
|
1682
|
+
authLogger.info('User logged in'); // Output: [KORM][Auth] [INFO] User logged in
|
|
1683
|
+
|
|
1684
|
+
const dbLogger = logger.child('[DB]');
|
|
1685
|
+
dbLogger.debug('Query executed'); // Output: [KORM][DB] [DEBUG] Query executed
|
|
1686
|
+
```
|
|
1687
|
+
|
|
1688
|
+
### Logger Options
|
|
1689
|
+
|
|
1690
|
+
```javascript
|
|
1691
|
+
const { Logger } = require('@dreamtree-org/korm-js').logger;
|
|
1692
|
+
|
|
1693
|
+
// Create a custom logger instance
|
|
1694
|
+
const customLogger = new Logger({
|
|
1695
|
+
prefix: '[MyApp]', // Custom prefix (default: '[KORM]')
|
|
1696
|
+
enabled: true, // Enable/disable logging (default: true)
|
|
1697
|
+
level: 'debug', // Log level (default: 'warn' or KORM_LOG_LEVEL)
|
|
1698
|
+
timestamps: true // Include timestamps (default: false)
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
customLogger.info('Application started');
|
|
1702
|
+
// Output: [MyApp] [2024-01-15T10:30:00.000Z] [INFO] Application started
|
|
1703
|
+
```
|
|
1704
|
+
|
|
1705
|
+
### Usage in Development vs Production
|
|
1706
|
+
|
|
1707
|
+
```javascript
|
|
1708
|
+
// Development: Enable debug logging
|
|
1709
|
+
// KORM_LOG_LEVEL=debug node app.js
|
|
1710
|
+
|
|
1711
|
+
// Production: Only show warnings and errors
|
|
1712
|
+
// KORM_LOG_LEVEL=warn node app.js
|
|
1713
|
+
|
|
1714
|
+
// Or configure programmatically
|
|
1715
|
+
const { logger } = require('@dreamtree-org/korm-js');
|
|
1716
|
+
|
|
1717
|
+
if (process.env.NODE_ENV === 'development') {
|
|
1718
|
+
logger.setLevel('debug');
|
|
1719
|
+
} else {
|
|
1720
|
+
logger.setLevel('warn');
|
|
1721
|
+
}
|
|
1722
|
+
```
|
|
1723
|
+
|
|
1507
1724
|
## Database Support
|
|
1508
1725
|
|
|
1509
1726
|
### MySQL (Current Guide)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const HelperUtility=require("./HelperUtility"),logger=require("../../Logger");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}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,i={};for(const[t,o]of Object.entries(e))if(t.startsWith(r)){i[t.slice(r.length)]=o}return i}_applyWithWhereConditions(e,t){for(const[r,i]of Object.entries(t)){const{joinType:t="AND",column:o}=this.parseWhereColumn(r),{operator:l,value:
|
|
1
|
+
const HelperUtility=require("./HelperUtility"),logger=require("../../Logger");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}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,i={};for(const[t,o]of Object.entries(e))if(t.startsWith(r)){i[t.slice(r.length)]=o}return i}_applyWithWhereConditions(e,t){for(const[r,i]of Object.entries(t)){const{joinType:t="AND",column:o}=this.parseWhereColumn(r),{operator:l,value:n}=this.parseWhereValue(i);"AND"===t?this._applyAndWhereCondition(e,o,l,n):this._applyOrWhereCondition(e,o,l,n)}}async fetchRelatedRows(e,t,r={}){if(e.through){let i=await this.db(e.through).whereIn(e.throughLocalKey,t),o=this.db(e.table).whereIn(e.foreignKey,i.map(t=>t[e.throughForeignKey]));return Object.keys(r).length>0&&this._applyWithWhereConditions(o,r),o}{let i=this.db(e.table).whereIn(e.foreignKey,t);return Object.keys(r).length>0&&this._applyWithWhereConditions(i,r),i}}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:i,withTree:o,relation:l,withWhere:n={}}=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,withWhere:n};await e[l](a)}else logger.warn(`Method ${l} not found in model ${i.name}`);return t}let a=t.map(e=>e[l.localKey]),s=[];const h="one"===l?.type,p=this._getWithWhereForRelation(n,r);s=await this.fetchRelatedRows(l,a,p);let c=new Map;for(const e of s){let t=e[l.foreignKey];c.has(t)||c.set(t,[]),c.get(t).push(e)}for(const e of t){let t=e[l.localKey];h&&1==c.get(t)?.length?e[r]=c.get(t)[0]:e[r]=c.get(t)||[]}let y=Object.keys(o);for(const e of y){let i=o[e],l=t.filter(e=>h?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],withWhere:n})}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:n={column:"id",direction:"asc"},limit:a=10,offset:s=0,page:h,groupBy:p,having:c,distinct:y,join:u,leftJoin:f,rightJoin:g,innerJoin:d,count:W=!1}=t;let w=this.getQueryBuilder(e);l&&(Array.isArray(l)||"string"==typeof l)?w.select(l):w.select("*"),y&&(Array.isArray(y)||"string"==typeof y?w.distinct(y):w.distinct()),u&&this._applyJoins(w,u,"join"),f&&this._applyJoins(w,f,"leftJoin"),g&&this._applyJoins(w,g,"rightJoin"),d&&this._applyJoins(w,d,"innerJoin"),this._applyWhereClause(w,r,i),p&&(Array.isArray(p),w.groupBy(p)),c&&this._applyHavingClause(w,c),n&&this._applyOrderBy(w,n);let b=!1,_=a,m=s,A=1,k=0;h&&a>0&&(A=Math.max(1,parseInt(h)),m=(A-1)*a),a>0&&(w.limit(_),m>0&&w.offset(m));const j=await w;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],withWhere:o||{}})}let C=null;if(a>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"),g&&this._applyJoins(t,g,"rightJoin"),d&&this._applyJoins(t,d,"innerJoin");C=(await t.count("* as cnt").first()).cnt}catch(e){logger.warn("Failed to get total count:",e.message),C=j.length}a>0&&null!==C&&(k=Math.ceil(C/a),b=A<k);return{data:j,totalCount:C,...a>0?{pagination:{page:A,limit:_,offset:m,totalPages:k,hasNext:b,hasPrev:A>1,nextPage:b?A+1:null,prevPage:A>1?A-1:null}}:{}}}catch(t){throw logger.error("QueryService.getQuery error:",t),new Error(`Failed to execute query: ${t.message} on model ${e.name}`)}}_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);logger.debug({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:n}=this.parseWhereValue(i);"AND"===r?this._applyAndWhereCondition(e,o,l,n):this._applyOrWhereCondition(e,o,l,n)}}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 n=this.helperUtility.getDotWalkQuery(t,l);if(n&&Object.keys(n).length>0){let t=this.utils.getModel(this.controllerWrapper,l),a=o.hasRelations[l],s=r.map(e=>this.helperUtility.pluckDotWalkKey(e,1));e.whereExists(function(){let e=i.getQueryBuilder(t,this.select("*").from(t.table));e.whereRaw(`${a.foreignKey} = ${o.table}.${a.localKey}`),i._applyWhereClause(e,n,s)})}}}_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){logger.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 HelperUtility=require("./HelperUtility"),logger=require("../../Logger");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}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,i={};for(const[t,o]of Object.entries(e))if(t.startsWith(r)){i[t.slice(r.length)]=o}return i}_applyWithWhereConditions(e,t){for(const[r,i]of Object.entries(t)){const{joinType:t="AND",column:o}=this.parseWhereColumn(r),{operator:l,value:
|
|
1
|
+
const HelperUtility=require("./HelperUtility"),logger=require("../../Logger");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}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,i={};for(const[t,o]of Object.entries(e))if(t.startsWith(r)){i[t.slice(r.length)]=o}return i}_applyWithWhereConditions(e,t){for(const[r,i]of Object.entries(t)){const{joinType:t="AND",column:o}=this.parseWhereColumn(r),{operator:l,value:n}=this.parseWhereValue(i);"AND"===t?this._applyAndWhereCondition(e,o,l,n):this._applyOrWhereCondition(e,o,l,n)}}async fetchRelatedRows(e,t,r={}){if(e.through){let i=await this.db(e.through).whereIn(e.throughLocalKey,t),o=this.db(e.table).whereIn(e.foreignKey,i.map(t=>t[e.throughForeignKey]));return Object.keys(r).length>0&&this._applyWithWhereConditions(o,r),o}{let i=this.db(e.table).whereIn(e.foreignKey,t);return Object.keys(r).length>0&&this._applyWithWhereConditions(i,r),i}}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:i,withTree:o,relation:l,withWhere:n={}}=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,withWhere:n};await e[l](a)}else logger.warn(`Method ${l} not found in model ${i.name}`);return t}let a=t.map(e=>e[l.localKey]);const s="one"===l?.type;let h=[];const p=this._getWithWhereForRelation(n,r);h=await this.fetchRelatedRows(l,a,p);let c=new Map;for(const e of h){let t=e[l.foreignKey];c.has(t)||c.set(t,[]),c.get(t).push(e)}for(const e of t){let t=e[l.localKey];s&&1==c.get(t)?.length?e[r]=c.get(t)[0]:e[r]=c.get(t)||[]}let y=Object.keys(o);for(const e of y){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],withWhere:n})}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:n={column:"id",direction:"asc"},limit:a=10,offset:s=0,page:h,groupBy:p,having:c,distinct:y,join:u,leftJoin:f,rightJoin:g,innerJoin:d,count:W=!1}=t;let w=this.getQueryBuilder(e);l&&(Array.isArray(l)||"string"==typeof l)?w.select(l):w.select("*"),y&&(Array.isArray(y)||"string"==typeof y?w.distinct(y):w.distinct()),u&&this._applyJoins(w,u,"join"),f&&this._applyJoins(w,f,"leftJoin"),g&&this._applyJoins(w,g,"rightJoin"),d&&this._applyJoins(w,d,"innerJoin"),this._applyWhereClause(w,r,i),p&&(Array.isArray(p),w.groupBy(p)),c&&this._applyHavingClause(w,c),n&&this._applyOrderBy(w,n);let b=!1,_=a,m=s,A=1,k=0;h&&a>0&&(A=Math.max(1,parseInt(h)),m=(A-1)*a),a>0&&(w.limit(_),m>0&&w.offset(m));const j=await w;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],withWhere:o||{}})}let C=null;if(a>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"),g&&this._applyJoins(t,g,"rightJoin"),d&&this._applyJoins(t,d,"innerJoin");C=(await t.count("* as cnt").first()).cnt}catch(e){logger.warn("Failed to get total count:",e.message),C=j.length}a>0&&null!==C&&(k=Math.ceil(C/a),b=A<k);return{data:j,totalCount:C,...a>0?{pagination:{page:A,limit:_,offset:m,totalPages:k,hasNext:b,hasPrev:A>1,nextPage:b?A+1:null,prevPage:A>1?A-1:null}}:{}}}catch(t){throw logger.error("QueryService.getQuery error:",t),new Error(`Failed to execute query: ${t.message} on model ${e.name}`)}}_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);logger.debug({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:n}=this.parseWhereValue(i);"AND"===r?this._applyAndWhereCondition(e,o,l,n):this._applyOrWhereCondition(e,o,l,n)}}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 n=this.helperUtility.getDotWalkQuery(t,l);if(n&&Object.keys(n).length>0){let t=this.utils.getModel(this.controllerWrapper,l),a=o.hasRelations[l],s=r.map(e=>this.helperUtility.pluckDotWalkKey(e,1));e.whereExists(function(){let e=i.getQueryBuilder(t,this.select("*").from(t.table));e.whereRaw(`${a.foreignKey} = ${o.table}.${a.localKey}`),i._applyWhereClause(e,n,s)})}}}_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){logger.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 HelperUtility=require("./HelperUtility"),logger=require("../../Logger");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}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,i={};for(const[t,o]of Object.entries(e))if(t.startsWith(r)){i[t.slice(r.length)]=o}return i}_applyWithWhereConditions(e,t){for(const[r,i]of Object.entries(t)){const{joinType:t="AND",column:o}=this.parseWhereColumn(r),{operator:l,value:
|
|
1
|
+
const HelperUtility=require("./HelperUtility"),logger=require("../../Logger");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}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,i={};for(const[t,o]of Object.entries(e))if(t.startsWith(r)){i[t.slice(r.length)]=o}return i}_applyWithWhereConditions(e,t){for(const[r,i]of Object.entries(t)){const{joinType:t="AND",column:o}=this.parseWhereColumn(r),{operator:l,value:n}=this.parseWhereValue(i);"AND"===t?this._applyAndWhereCondition(e,o,l,n):this._applyOrWhereCondition(e,o,l,n)}}async fetchRelatedRows(e,t,r={}){if(e.through){let i=await this.db(e.through).whereIn(e.throughLocalKey,t),o=this.db(e.table).whereIn(e.foreignKey,i.map(t=>t[e.throughForeignKey]));return Object.keys(r).length>0&&this._applyWithWhereConditions(o,r),o}{let i=this.db(e.table).whereIn(e.foreignKey,t);return Object.keys(r).length>0&&this._applyWithWhereConditions(i,r),i}}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:i,withTree:o,relation:l,withWhere:n={}}=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,withWhere:n};await e[l](a)}else logger.warn(`Method ${l} not found in model ${i.name}`);return t}let a=t.map(e=>e[l.localKey]);const s="one"===l?.type;let h=[];const p=this._getWithWhereForRelation(n,r);h=await this.fetchRelatedRows(l,a,p);let c=new Map;for(const e of h){let t=e[l.foreignKey];c.has(t)||c.set(t,[]),c.get(t).push(e)}for(const e of t){let t=e[l.localKey];s&&1==c.get(t)?.length?e[r]=c.get(t)[0]:e[r]=c.get(t)||[]}let y=Object.keys(o);for(const e of y){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],withWhere:n})}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:n={column:"id",direction:"asc"},limit:a=10,offset:s=0,page:h,groupBy:p,having:c,distinct:y,join:u,leftJoin:f,rightJoin:g,innerJoin:d,count:W=!1}=t;let w=this.getQueryBuilder(e);l&&(Array.isArray(l)||"string"==typeof l)?w.select(l):w.select("*"),y&&(Array.isArray(y)||"string"==typeof y?w.distinct(y):w.distinct()),u&&this._applyJoins(w,u,"join"),f&&this._applyJoins(w,f,"leftJoin"),g&&this._applyJoins(w,g,"rightJoin"),d&&this._applyJoins(w,d,"innerJoin"),this._applyWhereClause(w,r,i),p&&(Array.isArray(p),w.groupBy(p)),c&&this._applyHavingClause(w,c),n&&this._applyOrderBy(w,n);let b=!1,_=a,m=s,A=1,k=0;h&&a>0&&(A=Math.max(1,parseInt(h)),m=(A-1)*a),a>0&&(w.limit(_),m>0&&w.offset(m));const j=await w;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],withWhere:o||{}})}let C=null;if(a>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"),g&&this._applyJoins(t,g,"rightJoin"),d&&this._applyJoins(t,d,"innerJoin");C=(await t.count("* as cnt").first()).cnt}catch(e){logger.warn("Failed to get total count:",e.message),C=j.length}a>0&&null!==C&&(k=Math.ceil(C/a),b=A<k);return{data:j,totalCount:C,...a>0?{pagination:{page:A,limit:_,offset:m,totalPages:k,hasNext:b,hasPrev:A>1,nextPage:b?A+1:null,prevPage:A>1?A-1:null}}:{}}}catch(t){throw logger.error("QueryService.getQuery error:",t),new Error(`Failed to execute query: ${t.message} on model ${e.name}`)}}_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:n}=this.parseWhereValue(i);"AND"===r?this._applyAndWhereCondition(e,o,l,n):this._applyOrWhereCondition(e,o,l,n)}}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 n=this.helperUtility.getDotWalkQuery(t,l);if(n&&Object.keys(n).length>0){let t=this.utils.getModel(this.controllerWrapper,l),a=o.hasRelations[l],s=r.map(e=>this.helperUtility.pluckDotWalkKey(e,1));e.whereExists(function(){let e=i.getQueryBuilder(t,this.select("*").from(t.table));e.whereRaw(`${a.foreignKey} = ${o.table}.${a.localKey}`),i._applyWhereClause(e,n,s)})}}}_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){logger.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.51",
|
|
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",
|