@dreamtree-org/korm-js 1.0.50 → 1.0.52
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/ControllerWrapper.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const mysqlWrapper=require("./clients/mysql"),sqliteWrapper=require("./clients/sqlite"),pgWrapper=require("./clients/pg"),BaseHelperUtility=require("./BaseHelperUtility"),InstanceMapper={mysql2:mysqlWrapper,sqlite:sqliteWrapper,pg:pgWrapper},dbClientMapper={mysql2:"mysql2",mysql:"mysql2",pg:"pg",postgresql:"pg",sqlite:"sqlite",sqlite3:"sqlite"};class ControllerWrapper{static db=null;static dbClient=null;static dbClientClass=null;static schema=null;static resolverPath=null;static dbInstance=null;requestInstance=null;constructor(){this.requestInstance={}}static initializeKORM({db:t,dbClient:e,schema:s,resolverPath:
|
|
1
|
+
const mysqlWrapper=require("./clients/mysql"),sqliteWrapper=require("./clients/sqlite"),pgWrapper=require("./clients/pg"),BaseHelperUtility=require("./BaseHelperUtility"),InstanceMapper={mysql2:mysqlWrapper,sqlite:sqliteWrapper,pg:pgWrapper},dbClientMapper={mysql2:"mysql2",mysql:"mysql2",pg:"pg",postgresql:"pg",sqlite:"sqlite",sqlite3:"sqlite"};class ControllerWrapper{static db=null;static dbClient=null;static dbClientClass=null;static schema=null;static resolverPath=null;static dbInstance=null;static debug=!1;requestInstance=null;constructor(){this.requestInstance={}}static initializeKORM({db:t,dbClient:e,schema:s,resolverPath:a=null,debug:n=!1}){this.db=t,this.dbClient=e,this.schema=s,this.resolverPath=a,this.debug=n;const r=dbClientMapper[e];if(!r)throw new Error(`Database client ${e} not found`);const i=InstanceMapper[r];if(!i)throw new Error(`Database client ${e} not found`);return this.dbClientClass=i,this.dbInstance=new i(this),this}static setSchema(t){this.schema=t;const e=this.dbClientClass;if(!e)throw new Error(`Database client ${dbClient} not found`);return this.dbInstance=new e(this),this}static async processRequest(t,e=null,s=null){return await this.dbInstance.processRequest(t,e,s)}static async processRequestWithOthers(t,e=null,s=null){return await this.dbInstance.processRequest(t,e,s)}static async syncDatabase(){return await this.dbInstance.syncDatabase()}static async generateSchema(){return await this.dbInstance.generateSchema()}static loadModelClass(t){return this.dbInstance.hookService.loadModelClass(t)}static getModelInstance(t){return this.dbInstance.hookService.getModelInstance(t)}}module.exports=ControllerWrapper;
|
package/README.md
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
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
|
|
23
|
+
- 🔎 **SQL Debugging**: Debug mode with SQL statement output for troubleshooting
|
|
24
|
+
- 🚫 **NOT EXISTS Queries**: Support for checking absence of related records with `!` prefix
|
|
22
25
|
|
|
23
26
|
## Installation
|
|
24
27
|
|
|
@@ -54,7 +57,8 @@ const db = knex({
|
|
|
54
57
|
// Initialize KORM with MySQL
|
|
55
58
|
const korm = initializeKORM({
|
|
56
59
|
db: db,
|
|
57
|
-
dbClient: 'mysql'
|
|
60
|
+
dbClient: 'mysql',
|
|
61
|
+
debug: false // Set to true for SQL debugging
|
|
58
62
|
});
|
|
59
63
|
|
|
60
64
|
app.listen(3000, () => {
|
|
@@ -810,8 +814,172 @@ POST /api/Post/crud
|
|
|
810
814
|
},
|
|
811
815
|
"with": ["Comment"]
|
|
812
816
|
}
|
|
817
|
+
|
|
818
|
+
// Deeply nested where conditions
|
|
819
|
+
POST /api/Post/crud
|
|
820
|
+
{
|
|
821
|
+
"action": "list",
|
|
822
|
+
"where": {
|
|
823
|
+
"User.UserRole.Role.name": "admin"
|
|
824
|
+
},
|
|
825
|
+
"with": ["User", "User.UserRole", "User.UserRole.Role"]
|
|
826
|
+
}
|
|
827
|
+
// SQL: WHERE EXISTS (SELECT * FROM users WHERE users.id = posts.user_id
|
|
828
|
+
// AND EXISTS (SELECT * FROM user_roles WHERE user_roles.user_id = users.id
|
|
829
|
+
// AND EXISTS (SELECT * FROM roles WHERE roles.id = user_roles.role_id AND name = 'admin')))
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
#### NOT EXISTS Queries with `!` Prefix
|
|
833
|
+
|
|
834
|
+
Use the `!` prefix on relation names to check for the absence of related records:
|
|
835
|
+
|
|
836
|
+
```javascript
|
|
837
|
+
// Get posts where the user has NO UserRole entries
|
|
838
|
+
POST /api/Post/crud
|
|
839
|
+
{
|
|
840
|
+
"action": "list",
|
|
841
|
+
"where": {
|
|
842
|
+
"User.!UserRole": true
|
|
843
|
+
},
|
|
844
|
+
"with": ["User"]
|
|
845
|
+
}
|
|
846
|
+
// SQL: WHERE EXISTS (SELECT * FROM users WHERE users.id = posts.user_id
|
|
847
|
+
// AND NOT EXISTS (SELECT * FROM user_roles WHERE user_roles.user_id = users.id))
|
|
848
|
+
|
|
849
|
+
// Get users with no posts
|
|
850
|
+
POST /api/User/crud
|
|
851
|
+
{
|
|
852
|
+
"action": "list",
|
|
853
|
+
"where": {
|
|
854
|
+
"!Post": true
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
// SQL: WHERE NOT EXISTS (SELECT * FROM posts WHERE posts.user_id = users.id)
|
|
858
|
+
|
|
859
|
+
// Get posts where user has no admin role
|
|
860
|
+
POST /api/Post/crud
|
|
861
|
+
{
|
|
862
|
+
"action": "list",
|
|
863
|
+
"where": {
|
|
864
|
+
"User.!UserRole.Role.name": "admin"
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
// SQL: WHERE EXISTS (SELECT * FROM users WHERE users.id = posts.user_id
|
|
868
|
+
// AND NOT EXISTS (SELECT * FROM user_roles WHERE user_roles.user_id = users.id
|
|
869
|
+
// AND EXISTS (SELECT * FROM roles WHERE roles.id = user_roles.role_id AND name = 'admin')))
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
**NOT EXISTS Syntax Summary:**
|
|
873
|
+
|
|
874
|
+
| Pattern | Meaning | Use Case |
|
|
875
|
+
|---------|---------|----------|
|
|
876
|
+
| `"!RelName": true` | NOT EXISTS on direct relation | Users with no posts |
|
|
877
|
+
| `"Parent.!Child": true` | NOT EXISTS on nested relation | Posts where user has no roles |
|
|
878
|
+
| `"Parent.!Child.column": value` | NOT EXISTS with condition | Posts where user has no admin role |
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
#### Filtering Eager Loaded Relations with `withWhere`
|
|
882
|
+
|
|
883
|
+
The `withWhere` parameter allows you to apply additional where conditions when fetching related data. This filters the related rows without affecting the parent query.
|
|
884
|
+
|
|
885
|
+
```javascript
|
|
886
|
+
// Get all users with their posts, but only posts from user_id = 15
|
|
887
|
+
POST /api/User/crud
|
|
888
|
+
{
|
|
889
|
+
"action": "list",
|
|
890
|
+
"with": ["Post"],
|
|
891
|
+
"withWhere": {
|
|
892
|
+
"Post.user_id": 15
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Get users with active posts only
|
|
897
|
+
POST /api/User/crud
|
|
898
|
+
{
|
|
899
|
+
"action": "list",
|
|
900
|
+
"with": ["Post"],
|
|
901
|
+
"withWhere": {
|
|
902
|
+
"Post.status": "active",
|
|
903
|
+
"Post.is_published": true
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Multiple relations with different filters
|
|
908
|
+
POST /api/User/crud
|
|
909
|
+
{
|
|
910
|
+
"action": "list",
|
|
911
|
+
"with": ["Post", "Comment"],
|
|
912
|
+
"withWhere": {
|
|
913
|
+
"Post.status": "published",
|
|
914
|
+
"Post.created_at": ">=2024-01-01",
|
|
915
|
+
"Comment.is_approved": true
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Using operators in withWhere (same as regular where)
|
|
920
|
+
POST /api/User/crud
|
|
921
|
+
{
|
|
922
|
+
"action": "list",
|
|
923
|
+
"with": ["Post"],
|
|
924
|
+
"withWhere": {
|
|
925
|
+
"Post.title": "%tutorial%",
|
|
926
|
+
"Post.views": ">=100",
|
|
927
|
+
"Post.category": "[]tech,programming"
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Using OR conditions in withWhere
|
|
932
|
+
POST /api/User/crud
|
|
933
|
+
{
|
|
934
|
+
"action": "list",
|
|
935
|
+
"with": ["Post"],
|
|
936
|
+
"withWhere": {
|
|
937
|
+
"Post.status": "published",
|
|
938
|
+
"Or:Post.is_featured": true
|
|
939
|
+
}
|
|
940
|
+
}
|
|
813
941
|
```
|
|
814
942
|
|
|
943
|
+
**Key differences between `where` and `withWhere`:**
|
|
944
|
+
|
|
945
|
+
| Feature | `where` (Nested) | `withWhere` |
|
|
946
|
+
|---------|-----------------|-------------|
|
|
947
|
+
| Affects parent query | Yes (filters parent rows) | No (only filters related rows) |
|
|
948
|
+
| Purpose | Filter parents that have matching relations | Filter which related rows are loaded |
|
|
949
|
+
| SQL generated | `WHERE EXISTS (subquery)` | Additional `WHERE` on relation query |
|
|
950
|
+
|
|
951
|
+
**Example showing the difference:**
|
|
952
|
+
|
|
953
|
+
```javascript
|
|
954
|
+
// This filters users who have published posts (affects which users are returned)
|
|
955
|
+
{
|
|
956
|
+
"action": "list",
|
|
957
|
+
"where": { "Post.status": "published" },
|
|
958
|
+
"with": ["Post"]
|
|
959
|
+
}
|
|
960
|
+
// Returns: Only users who have at least one published post
|
|
961
|
+
// Each user's Post array contains ALL their posts (not filtered)
|
|
962
|
+
|
|
963
|
+
// This loads all users but only their published posts
|
|
964
|
+
{
|
|
965
|
+
"action": "list",
|
|
966
|
+
"with": ["Post"],
|
|
967
|
+
"withWhere": { "Post.status": "published" }
|
|
968
|
+
}
|
|
969
|
+
// Returns: All users
|
|
970
|
+
// Each user's Post array contains ONLY their published posts
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
**withWhere supports all operators:**
|
|
974
|
+
- Comparison: `">=18"`, `"<=100"`, `">50"`, `"<10"`, `"!deleted"`
|
|
975
|
+
- LIKE: `"%pattern%"`
|
|
976
|
+
- IN: `"[]val1,val2,val3"`
|
|
977
|
+
- NOT IN: `"![]val1,val2"`
|
|
978
|
+
- BETWEEN: `"><min,max"`
|
|
979
|
+
- NOT BETWEEN: `"<>min,max"`
|
|
980
|
+
- NULL: `null`
|
|
981
|
+
- OR prefix: `"Or:column"`
|
|
982
|
+
|
|
815
983
|
### Example: Complete Relationship Structure
|
|
816
984
|
|
|
817
985
|
Example complete relationship structure:
|
|
@@ -1385,7 +1553,8 @@ const korm = initializeKORM({
|
|
|
1385
1553
|
db: db, // Knex database instance
|
|
1386
1554
|
dbClient: 'mysql', // 'mysql', 'mysql2', 'pg', 'postgresql', 'sqlite', 'sqlite3'
|
|
1387
1555
|
schema: null, // Optional: initial schema object
|
|
1388
|
-
resolverPath: null
|
|
1556
|
+
resolverPath: null, // Optional: path to models directory (default: process.cwd())
|
|
1557
|
+
debug: false // Optional: enable SQL debugging (default: false)
|
|
1389
1558
|
});
|
|
1390
1559
|
|
|
1391
1560
|
// Process any CRUD request (automatically handles other_requests if present)
|
|
@@ -1423,6 +1592,7 @@ const modelInstance = korm.getModelInstance(modelDef);
|
|
|
1423
1592
|
| `offset` | number | Records to skip |
|
|
1424
1593
|
| `page` | number | Page number (alternative to offset) |
|
|
1425
1594
|
| `with` | array | Related models to eager load |
|
|
1595
|
+
| `withWhere` | object | Filter conditions for eager loaded relations |
|
|
1426
1596
|
| `groupBy` | array/string | Group by columns |
|
|
1427
1597
|
| `having` | object | Having conditions |
|
|
1428
1598
|
| `distinct` | boolean/array/string | Distinct results |
|
|
@@ -1486,6 +1656,7 @@ const {
|
|
|
1486
1656
|
helperUtility, // Utility functions (file operations, string manipulation)
|
|
1487
1657
|
emitter, // Event emitter instance
|
|
1488
1658
|
validate, // Validation function
|
|
1659
|
+
logger, // Logger utility instance
|
|
1489
1660
|
lib, // Additional utilities
|
|
1490
1661
|
LibClasses // Library classes (Emitter)
|
|
1491
1662
|
} = require('@dreamtree-org/korm-js');
|
|
@@ -1504,6 +1675,197 @@ const {
|
|
|
1504
1675
|
// - createDirectory(path) - Create directory
|
|
1505
1676
|
```
|
|
1506
1677
|
|
|
1678
|
+
## Logger Utility
|
|
1679
|
+
|
|
1680
|
+
KORM includes a built-in logger utility with configurable log levels for debugging and monitoring.
|
|
1681
|
+
|
|
1682
|
+
### Basic Usage
|
|
1683
|
+
|
|
1684
|
+
```javascript
|
|
1685
|
+
const { logger } = require('@dreamtree-org/korm-js');
|
|
1686
|
+
|
|
1687
|
+
// Log methods (from most to least verbose)
|
|
1688
|
+
logger.debug('Detailed debugging information');
|
|
1689
|
+
logger.log('General log message');
|
|
1690
|
+
logger.info('Informational message');
|
|
1691
|
+
logger.warn('Warning message');
|
|
1692
|
+
logger.error('Error message');
|
|
1693
|
+
```
|
|
1694
|
+
|
|
1695
|
+
### Log Levels
|
|
1696
|
+
|
|
1697
|
+
| Level | Value | Description |
|
|
1698
|
+
|-------|-------|-------------|
|
|
1699
|
+
| `NONE` | 0 | Disable all logging |
|
|
1700
|
+
| `ERROR` | 1 | Only errors |
|
|
1701
|
+
| `WARN` | 2 | Warnings and errors (default) |
|
|
1702
|
+
| `INFO` | 3 | Info, warnings, and errors |
|
|
1703
|
+
| `LOG` | 4 | General logs and above |
|
|
1704
|
+
| `DEBUG` | 5 | All messages (most verbose) |
|
|
1705
|
+
|
|
1706
|
+
### Configuration
|
|
1707
|
+
|
|
1708
|
+
```javascript
|
|
1709
|
+
const { logger } = require('@dreamtree-org/korm-js');
|
|
1710
|
+
|
|
1711
|
+
// Set log level programmatically
|
|
1712
|
+
logger.setLevel('debug'); // Show all logs
|
|
1713
|
+
logger.setLevel('warn'); // Only warnings and errors (default)
|
|
1714
|
+
logger.setLevel('error'); // Only errors
|
|
1715
|
+
logger.setLevel('none'); // Disable all logging
|
|
1716
|
+
|
|
1717
|
+
// Enable/disable logging
|
|
1718
|
+
logger.disable(); // Temporarily disable all logging
|
|
1719
|
+
logger.enable(); // Re-enable logging
|
|
1720
|
+
```
|
|
1721
|
+
|
|
1722
|
+
### Environment Variable
|
|
1723
|
+
|
|
1724
|
+
Set the log level via environment variable:
|
|
1725
|
+
|
|
1726
|
+
```bash
|
|
1727
|
+
# In your .env file or environment
|
|
1728
|
+
KORM_LOG_LEVEL=debug # Show all logs
|
|
1729
|
+
KORM_LOG_LEVEL=warn # Only warnings and errors (default)
|
|
1730
|
+
KORM_LOG_LEVEL=error # Only errors
|
|
1731
|
+
KORM_LOG_LEVEL=none # Disable logging
|
|
1732
|
+
```
|
|
1733
|
+
|
|
1734
|
+
```javascript
|
|
1735
|
+
// The logger automatically reads KORM_LOG_LEVEL on initialization
|
|
1736
|
+
// KORM_LOG_LEVEL=debug node app.js
|
|
1737
|
+
```
|
|
1738
|
+
|
|
1739
|
+
### Creating Child Loggers
|
|
1740
|
+
|
|
1741
|
+
Create child loggers with custom prefixes for different modules:
|
|
1742
|
+
|
|
1743
|
+
```javascript
|
|
1744
|
+
const { logger } = require('@dreamtree-org/korm-js');
|
|
1745
|
+
|
|
1746
|
+
// Create a child logger for a specific module
|
|
1747
|
+
const authLogger = logger.child('[Auth]');
|
|
1748
|
+
authLogger.info('User logged in'); // Output: [KORM][Auth] [INFO] User logged in
|
|
1749
|
+
|
|
1750
|
+
const dbLogger = logger.child('[DB]');
|
|
1751
|
+
dbLogger.debug('Query executed'); // Output: [KORM][DB] [DEBUG] Query executed
|
|
1752
|
+
```
|
|
1753
|
+
|
|
1754
|
+
### Logger Options
|
|
1755
|
+
|
|
1756
|
+
```javascript
|
|
1757
|
+
const { Logger } = require('@dreamtree-org/korm-js').logger;
|
|
1758
|
+
|
|
1759
|
+
// Create a custom logger instance
|
|
1760
|
+
const customLogger = new Logger({
|
|
1761
|
+
prefix: '[MyApp]', // Custom prefix (default: '[KORM]')
|
|
1762
|
+
enabled: true, // Enable/disable logging (default: true)
|
|
1763
|
+
level: 'debug', // Log level (default: 'warn' or KORM_LOG_LEVEL)
|
|
1764
|
+
timestamps: true // Include timestamps (default: false)
|
|
1765
|
+
});
|
|
1766
|
+
|
|
1767
|
+
customLogger.info('Application started');
|
|
1768
|
+
// Output: [MyApp] [2024-01-15T10:30:00.000Z] [INFO] Application started
|
|
1769
|
+
```
|
|
1770
|
+
|
|
1771
|
+
### Usage in Development vs Production
|
|
1772
|
+
|
|
1773
|
+
```javascript
|
|
1774
|
+
// Development: Enable debug logging
|
|
1775
|
+
// KORM_LOG_LEVEL=debug node app.js
|
|
1776
|
+
|
|
1777
|
+
// Production: Only show warnings and errors
|
|
1778
|
+
// KORM_LOG_LEVEL=warn node app.js
|
|
1779
|
+
|
|
1780
|
+
// Or configure programmatically
|
|
1781
|
+
const { logger } = require('@dreamtree-org/korm-js');
|
|
1782
|
+
|
|
1783
|
+
if (process.env.NODE_ENV === 'development') {
|
|
1784
|
+
logger.setLevel('debug');
|
|
1785
|
+
} else {
|
|
1786
|
+
logger.setLevel('warn');
|
|
1787
|
+
}
|
|
1788
|
+
```
|
|
1789
|
+
|
|
1790
|
+
## SQL Debugging
|
|
1791
|
+
|
|
1792
|
+
Enable SQL debugging to see the exact SQL statements generated by your queries. This is useful for troubleshooting complex queries and understanding how KORM translates your requests.
|
|
1793
|
+
|
|
1794
|
+
### Enabling Debug Mode
|
|
1795
|
+
|
|
1796
|
+
```javascript
|
|
1797
|
+
const { initializeKORM } = require('@dreamtree-org/korm-js');
|
|
1798
|
+
|
|
1799
|
+
const korm = initializeKORM({
|
|
1800
|
+
db: db,
|
|
1801
|
+
dbClient: 'mysql',
|
|
1802
|
+
debug: true // Enable SQL debugging
|
|
1803
|
+
});
|
|
1804
|
+
```
|
|
1805
|
+
|
|
1806
|
+
### Debug Output
|
|
1807
|
+
|
|
1808
|
+
When `debug: true`, list queries will include a `sqlDebug` array in the response:
|
|
1809
|
+
|
|
1810
|
+
```javascript
|
|
1811
|
+
// Request
|
|
1812
|
+
POST /api/Post/crud
|
|
1813
|
+
{
|
|
1814
|
+
"action": "list",
|
|
1815
|
+
"where": {
|
|
1816
|
+
"User.!UserRole": true
|
|
1817
|
+
},
|
|
1818
|
+
"limit": 5
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
// Response with debug: true
|
|
1822
|
+
{
|
|
1823
|
+
"data": [...],
|
|
1824
|
+
"totalCount": 10,
|
|
1825
|
+
"sqlDebug": [
|
|
1826
|
+
"select * from `posts` where exists (select * from `users` where `users`.`id` = `posts`.`user_id` and not exists (select * from `user_roles` where `user_roles`.`user_id` = `users`.`id`)) order by `id` asc limit ?",
|
|
1827
|
+
"select count(*) as `cnt` from `posts` where exists (select * from `users` where `users`.`id` = `posts`.`user_id` and not exists (select * from `user_roles` where `user_roles`.`user_id` = `users`.`id`))"
|
|
1828
|
+
],
|
|
1829
|
+
"pagination": {
|
|
1830
|
+
"page": 1,
|
|
1831
|
+
"limit": 5,
|
|
1832
|
+
"offset": 0,
|
|
1833
|
+
"totalPages": 2,
|
|
1834
|
+
"hasNext": true,
|
|
1835
|
+
"hasPrev": false,
|
|
1836
|
+
"nextPage": 2,
|
|
1837
|
+
"prevPage": null
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
```
|
|
1841
|
+
|
|
1842
|
+
### What's Included in sqlDebug
|
|
1843
|
+
|
|
1844
|
+
| Index | Query Type | Description |
|
|
1845
|
+
|-------|------------|-------------|
|
|
1846
|
+
| 0 | Main Query | The primary SELECT query with all WHERE, ORDER BY, LIMIT clauses |
|
|
1847
|
+
| 1 | Count Query | The COUNT query used for pagination (only when limit > 0) |
|
|
1848
|
+
|
|
1849
|
+
### Debug Mode Best Practices
|
|
1850
|
+
|
|
1851
|
+
```javascript
|
|
1852
|
+
// Development: Enable debug mode
|
|
1853
|
+
const korm = initializeKORM({
|
|
1854
|
+
db: db,
|
|
1855
|
+
dbClient: 'mysql',
|
|
1856
|
+
debug: process.env.NODE_ENV === 'development'
|
|
1857
|
+
});
|
|
1858
|
+
|
|
1859
|
+
// Or use environment variable
|
|
1860
|
+
const korm = initializeKORM({
|
|
1861
|
+
db: db,
|
|
1862
|
+
dbClient: 'mysql',
|
|
1863
|
+
debug: process.env.KORM_DEBUG === 'true'
|
|
1864
|
+
});
|
|
1865
|
+
```
|
|
1866
|
+
|
|
1867
|
+
**Note:** The `sqlDebug` field is only included in responses when `debug: true`. In production, set `debug: false` to exclude SQL statements from responses.
|
|
1868
|
+
|
|
1507
1869
|
## Database Support
|
|
1508
1870
|
|
|
1509
1871
|
### MySQL (Current Guide)
|
|
@@ -1524,7 +1886,8 @@ const db = knex({
|
|
|
1524
1886
|
|
|
1525
1887
|
const korm = initializeKORM({
|
|
1526
1888
|
db: db,
|
|
1527
|
-
dbClient: 'mysql'
|
|
1889
|
+
dbClient: 'mysql',
|
|
1890
|
+
debug: process.env.NODE_ENV === 'development' // Enable SQL debugging in development
|
|
1528
1891
|
});
|
|
1529
1892
|
```
|
|
1530
1893
|
|
|
@@ -1546,7 +1909,8 @@ const db = knex({
|
|
|
1546
1909
|
|
|
1547
1910
|
const korm = initializeKORM({
|
|
1548
1911
|
db: db,
|
|
1549
|
-
dbClient: 'pg'
|
|
1912
|
+
dbClient: 'pg',
|
|
1913
|
+
debug: process.env.NODE_ENV === 'development'
|
|
1550
1914
|
});
|
|
1551
1915
|
```
|
|
1552
1916
|
|
|
@@ -1564,7 +1928,8 @@ const db = knex({
|
|
|
1564
1928
|
|
|
1565
1929
|
const korm = initializeKORM({
|
|
1566
1930
|
db: db,
|
|
1567
|
-
dbClient: 'sqlite'
|
|
1931
|
+
dbClient: 'sqlite',
|
|
1932
|
+
debug: process.env.NODE_ENV === 'development'
|
|
1568
1933
|
});
|
|
1569
1934
|
```
|
|
1570
1935
|
|
|
@@ -1642,7 +2007,8 @@ const db = knex({
|
|
|
1642
2007
|
// Initialize KORM
|
|
1643
2008
|
const korm = initializeKORM({
|
|
1644
2009
|
db: db,
|
|
1645
|
-
dbClient: 'mysql'
|
|
2010
|
+
dbClient: 'mysql',
|
|
2011
|
+
debug: process.env.NODE_ENV === 'development' // SQL debugging in dev mode
|
|
1646
2012
|
});
|
|
1647
2013
|
|
|
1648
2014
|
// Initialize app
|
|
@@ -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,
|
|
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,o){if(null!==o)if(Array.isArray(o))e.orWhereIn(t,o);else switch(r){case"between":e.orWhereBetween(t,o);break;case"notBetween":e.orWhereNotBetween(t,o);break;case"in":e.orWhereIn(t,o);break;case"notIn":e.orWhereNotIn(t,o);break;case"like":e.orWhereRaw(`\`${t}\` LIKE ?`,[o]);break;default:e.orWhere(t,r,o)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.whereIn(t,o);else switch(r){case"between":e.whereBetween(t,o);break;case"notBetween":e.whereNotBetween(t,o);break;case"in":e.whereIn(t,o);break;case"notIn":e.whereNotIn(t,o);break;case"like":e.whereRaw(`\`${t}\` LIKE ?`,[o]);break;default:e.where(t,r,o)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:o})=>t?t({current:e,part:r,source:o}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{direct:{},nested:{}};const r=`${t}.`,o={},i={};for(const[t,s]of Object.entries(e))if(t.startsWith(r)){const e=t.slice(r.length);e.includes(".")?i[e]=s:o[e]=s}return{direct:o,nested:i}}_applyWithWhereConditions(e,t){for(const[r,o]of Object.entries(t)){const{joinType:t="AND",column:i}=this.parseWhereColumn(r),{operator:s,value:l}=this.parseWhereValue(o);"AND"===t?this._applyAndWhereCondition(e,i,s,l):this._applyOrWhereCondition(e,i,s,l)}}async fetchRelatedRows(e,t,r={}){if(e.through){let o=await this.db(e.through).whereIn(e.throughLocalKey,t),i=this.db(e.table).whereIn(e.foreignKey,o.map(t=>t[e.throughForeignKey]));return Object.keys(r).length>0&&this._applyWithWhereConditions(i,r),i}{let o=this.db(e.table).whereIn(e.foreignKey,t);return Object.keys(r).length>0&&this._applyWithWhereConditions(o,r),o}}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:o,withTree:i,relation:s,withWhere:l={}}=e;if(!s){const e=this.getHookService().getModelInstance(o),s=`get${r.charAt(0).toUpperCase()+r.slice(1)}Relation`;if("function"==typeof e[s]){const{direct:n,nested:a}=this._getWithWhereForRelation(l,r),h={rows:t,relName:r,model:o,withTree:i,controller:this.controllerWrapper,relation:o.hasRelations[r],qb:this,db:this.db,withWhere:n,nestedWithWhere:a};await e[s](h)}else logger.warn(`Method ${s} not found in model ${o.name}`);return t}let n=t.map(e=>e[s.localKey]),a=[];const h="one"===s?.type,{direct:c,nested:p}=this._getWithWhereForRelation(l,r);a=await this.fetchRelatedRows(s,n,c);let y=new Map;for(const e of a){let t=e[s.foreignKey];y.has(t)||y.set(t,[]),y.get(t).push(e)}for(const e of t){let t=e[s.localKey];h&&1==y.get(t)?.length?e[r]=y.get(t)[0]:e[r]=y.get(t)||[]}let u=Object.keys(i);for(const e of u){let o=i[e],s=t.filter(e=>h?e[r]:e[r].length>0).map(e=>e[r]).reduce((e,t)=>e.concat(t),[]),l=this.utils.getModel(this.controllerWrapper,r);await this.fetchAndAttachRelated({parentRows:s,relName:e,model:l,withTree:o,relation:l.hasRelations[e],withWhere:p})}return t}filterWhere(e,t=""){return t?Object.keys(e).filter(e=>e.includes(t)).reduce((r,o)=>(r[o.replace(t,"")]=e[o],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:o,withWhere:i,select:s,orderBy:l={column:"id",direction:"asc"},limit:n=10,offset:a=0,page:h,groupBy:c,having:p,distinct:y,join:u,leftJoin:d,rightJoin:f,innerJoin:g,count:W=!1}=t;let b=this.getQueryBuilder(e);s&&(Array.isArray(s)||"string"==typeof s)?b.select(s):b.select("*"),y&&(Array.isArray(y)||"string"==typeof y?b.distinct(y):b.distinct()),u&&this._applyJoins(b,u,"join"),d&&this._applyJoins(b,d,"leftJoin"),f&&this._applyJoins(b,f,"rightJoin"),g&&this._applyJoins(b,g,"innerJoin"),this._applyWhereClause(b,r,o),c&&(Array.isArray(c),b.groupBy(c)),p&&this._applyHavingClause(b,p),l&&this._applyOrderBy(b,l);let w=!1,_=n,m=a,A=1,j=0;h&&n>0&&(A=Math.max(1,parseInt(h)),m=(A-1)*n),n>0&&(b.limit(_),m>0&&b.offset(m));const k=[],C=b.toSQL();k.push(C.sql);const R=await b;if(o&&o.length>0){const t=this.buildWithTree(o);for(const r of Object.keys(t))await this.fetchAndAttachRelated({parentRows:R,relName:r,model:e,withTree:t[r],relation:e.hasRelations[r],withWhere:i||{}})}let O=null;if(n>0)try{let t=this.getQueryBuilder(e);r&&Object.keys(r).length>0&&this._applyWhereClause(t,r),u&&this._applyJoins(t,u,"join"),d&&this._applyJoins(t,d,"leftJoin"),f&&this._applyJoins(t,f,"rightJoin"),g&&this._applyJoins(t,g,"innerJoin");const o=t.count("* as cnt");k.push(o.toSQL().sql);O=(await o.first()).cnt}catch(e){logger.warn("Failed to get total count:",e.message),O=R.length}n>0&&null!==O&&(j=Math.ceil(O/n),w=A<j);const B=w?A+1:null,N=A>1?A-1:null;return{data:R,totalCount:O,...this.controllerWrapper.debug?{sqlDebug:k}:{},...n>0?{pagination:{page:A,limit:_,offset:m,totalPages:j,hasNext:w,hasPrev:A>1,nextPage:B,prevPage:N}}:{}}}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 o of t)this._applyWhereClause(e,o,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)=>!e.startsWith("!")&&("__exists__"!==e&&("object"!=typeof t||null===t)));for(const[t,o]of Object.entries(r)){const{joinType:r="AND",column:i}=this.parseWhereColumn(t),{operator:s,value:l}=this.parseWhereValue(o);"AND"===r?this._applyAndWhereCondition(e,i,s,l):this._applyOrWhereCondition(e,i,s,l)}}this._applyNestedWhere(e,t,r)}}_getNestedWhereConditions(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,o={};for(const[i,s]of Object.entries(e))if(i.startsWith(r)){o[i.slice(r.length)]=s}else i===t&&!0===s&&(o.__exists__=!0);return o}_getTopLevelRelationsFromWhere(e){if(!e||"object"!=typeof e)return[];const t=new Set;for(const r of Object.keys(e))if(r.includes(".")){const e=r.split(".")[0];t.add(e)}else r.startsWith("!")&&t.add(r);return[...t]}_applyNestedWhere(e,t,r){let o=this,i=e._getMyModel();const s=this._getTopLevelRelationsFromWhere(t);if(0!==s.length)for(let r of s){let s=r.startsWith("!"),l=s?r.slice(1):r,n=this._getNestedWhereConditions(t,r);if(0===Object.keys(n).length)continue;let a,h=i.hasRelations?.[l];if(!h){logger.warn(`Relation ${l} not found in model ${i.modelName}`);continue}try{a=this.utils.getModel(this.controllerWrapper,h.table||l)}catch(e){try{a=this.utils.getModel(this.controllerWrapper,l)}catch(e){logger.warn(`Model for relation ${l} (table: ${h.table}) not found`);continue}}e[s?"whereNotExists":"whereExists"](function(){let e=o.getQueryBuilder(a,this.select("*").from(h.table));e.whereRaw(`\`${h.table}\`.\`${h.foreignKey}\` = \`${i.table}\`.\`${h.localKey}\``);if(!(!0===n.__exists__&&1===Object.keys(n).length)){const t={...n};delete t.__exists__,o._applyWhereClause(e,t,[])}})}}_applyJoins(e,t,r){const o=Array.isArray(t)?t:[t];for(const t of o)"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,o]of Object.entries(t))e.withWhere(r,o)}catch(e){logger.warn("Failed to apply withWhere:",e.message)}}_applyHavingClause(e,t){for(const[r,o]of Object.entries(t))"object"==typeof o&&o.operator?e.having(r,o.operator,o.value):e.having(r,o)}_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,
|
|
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,o){if(null!==o)if(Array.isArray(o))e.orWhereIn(t,o);else switch(r){case"between":e.orWhereBetween(t,o);break;case"notBetween":e.orWhereNotBetween(t,o);break;case"in":e.orWhereIn(t,o);break;case"notIn":e.orWhereNotIn(t,o);break;case"like":e.orWhere(t,"like",o);break;default:e.orWhere(t,r,o)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.whereIn(t,o);else switch(r){case"between":e.whereBetween(t,o);break;case"notBetween":e.whereNotBetween(t,o);break;case"in":e.whereIn(t,o);break;case"notIn":e.whereNotIn(t,o);break;case"like":e.where(t,"like",o);break;default:e.where(t,r,o)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:o})=>t?t({current:e,part:r,source:o}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{direct:{},nested:{}};const r=`${t}.`,o={},i={};for(const[t,s]of Object.entries(e))if(t.startsWith(r)){const e=t.slice(r.length);e.includes(".")?i[e]=s:o[e]=s}return{direct:o,nested:i}}_applyWithWhereConditions(e,t){for(const[r,o]of Object.entries(t)){const{joinType:t="AND",column:i}=this.parseWhereColumn(r),{operator:s,value:l}=this.parseWhereValue(o);"AND"===t?this._applyAndWhereCondition(e,i,s,l):this._applyOrWhereCondition(e,i,s,l)}}async fetchRelatedRows(e,t,r={}){if(e.through){let o=await this.db(e.through).whereIn(e.throughLocalKey,t),i=this.db(e.table).whereIn(e.foreignKey,o.map(t=>t[e.throughForeignKey]));return Object.keys(r).length>0&&this._applyWithWhereConditions(i,r),i}{let o=this.db(e.table).whereIn(e.foreignKey,t);return Object.keys(r).length>0&&this._applyWithWhereConditions(o,r),o}}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:o,withTree:i,relation:s,withWhere:l={}}=e;if(!s){const e=this.getHookService().getModelInstance(o),s=`get${r.charAt(0).toUpperCase()+r.slice(1)}Relation`;if("function"==typeof e[s]){const{direct:n,nested:a}=this._getWithWhereForRelation(l,r),h={rows:t,relName:r,model:o,withTree:i,controller:this.controllerWrapper,relation:o.hasRelations[r],qb:this,db:this.db,withWhere:n,nestedWithWhere:a};await e[s](h)}else logger.warn(`Method ${s} not found in model ${o.name}`);return t}let n=t.map(e=>e[s.localKey]);const a="one"===s?.type;let h=[];const{direct:c,nested:p}=this._getWithWhereForRelation(l,r);h=await this.fetchRelatedRows(s,n,c);let y=new Map;for(const e of h){let t=e[s.foreignKey];y.has(t)||y.set(t,[]),y.get(t).push(e)}for(const e of t){let t=e[s.localKey];a&&1==y.get(t)?.length?e[r]=y.get(t)[0]:e[r]=y.get(t)||[]}let u=Object.keys(i);for(const e of u){let o=i[e],s=t.filter(e=>a?e[r]:e[r].length>0).map(e=>e[r]).reduce((e,t)=>e.concat(t),[]),l=this.utils.getModel(this.controllerWrapper,r);await this.fetchAndAttachRelated({parentRows:s,relName:e,model:l,withTree:o,relation:l.hasRelations[e],withWhere:p})}return t}filterWhere(e,t=""){return t?Object.keys(e).filter(e=>e.includes(t)).reduce((r,o)=>(r[o.replace(t,"")]=e[o],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:o,withWhere:i,select:s,orderBy:l={column:"id",direction:"asc"},limit:n=10,offset:a=0,page:h,groupBy:c,having:p,distinct:y,join:u,leftJoin:d,rightJoin:f,innerJoin:g,count:W=!1}=t;let b=this.getQueryBuilder(e);s&&(Array.isArray(s)||"string"==typeof s)?b.select(s):b.select("*"),y&&(Array.isArray(y)||"string"==typeof y?b.distinct(y):b.distinct()),u&&this._applyJoins(b,u,"join"),d&&this._applyJoins(b,d,"leftJoin"),f&&this._applyJoins(b,f,"rightJoin"),g&&this._applyJoins(b,g,"innerJoin"),this._applyWhereClause(b,r,o),c&&(Array.isArray(c),b.groupBy(c)),p&&this._applyHavingClause(b,p),l&&this._applyOrderBy(b,l);let w=!1,_=n,m=a,A=1,j=0;h&&n>0&&(A=Math.max(1,parseInt(h)),m=(A-1)*n),n>0&&(b.limit(_),m>0&&b.offset(m));const k=[],C=b.toSQL();k.push(C.sql);const O=await b;if(o&&o.length>0){const t=this.buildWithTree(o);for(const r of Object.keys(t))await this.fetchAndAttachRelated({parentRows:O,relName:r,model:e,withTree:t[r],relation:e.hasRelations[r],withWhere:i||{}})}let R=null;if(n>0)try{let t=this.getQueryBuilder(e);r&&Object.keys(r).length>0&&this._applyWhereClause(t,r),u&&this._applyJoins(t,u,"join"),d&&this._applyJoins(t,d,"leftJoin"),f&&this._applyJoins(t,f,"rightJoin"),g&&this._applyJoins(t,g,"innerJoin");const o=t.count("* as cnt");k.push(o.toSQL().sql);R=(await o.first()).cnt}catch(e){logger.warn("Failed to get total count:",e.message),R=O.length}n>0&&null!==R&&(j=Math.ceil(R/n),w=A<j);const B=w?A+1:null,N=A>1?A-1:null;return{data:O,totalCount:R,...this.controllerWrapper.debug?{sqlDebug:k}:{},...n>0?{pagination:{page:A,limit:_,offset:m,totalPages:j,hasNext:w,hasPrev:A>1,nextPage:B,prevPage:N}}:{}}}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 o of t)this._applyWhereClause(e,o,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)=>!e.startsWith("!")&&("__exists__"!==e&&("object"!=typeof t||null===t)));for(const[t,o]of Object.entries(r)){const{joinType:r="AND",column:i}=this.parseWhereColumn(t),{operator:s,value:l}=this.parseWhereValue(o);"AND"===r?this._applyAndWhereCondition(e,i,s,l):this._applyOrWhereCondition(e,i,s,l)}}this._applyNestedWhere(e,t,r)}}_getNestedWhereConditions(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,o={};for(const[i,s]of Object.entries(e))if(i.startsWith(r)){o[i.slice(r.length)]=s}else i===t&&!0===s&&(o.__exists__=!0);return o}_getTopLevelRelationsFromWhere(e){if(!e||"object"!=typeof e)return[];const t=new Set;for(const r of Object.keys(e))if(r.includes(".")){const e=r.split(".")[0];t.add(e)}else r.startsWith("!")&&t.add(r);return[...t]}_applyNestedWhere(e,t,r){let o=this,i=e._getMyModel();const s=this._getTopLevelRelationsFromWhere(t);if(0!==s.length)for(let r of s){let s=r.startsWith("!"),l=s?r.slice(1):r,n=this._getNestedWhereConditions(t,r);if(0===Object.keys(n).length)continue;let a,h=i.hasRelations?.[l];if(!h){logger.warn(`Relation ${l} not found in model ${i.modelName}`);continue}try{a=this.utils.getModel(this.controllerWrapper,h.table||l)}catch(e){try{a=this.utils.getModel(this.controllerWrapper,l)}catch(e){logger.warn(`Model for relation ${l} (table: ${h.table}) not found`);continue}}e[s?"whereNotExists":"whereExists"](function(){let e=o.getQueryBuilder(a,this.select("*").from(h.table));e.whereRaw(`"${h.table}"."${h.foreignKey}" = "${i.table}"."${h.localKey}"`);if(!(!0===n.__exists__&&1===Object.keys(n).length)){const t={...n};delete t.__exists__,o._applyWhereClause(e,t,[])}})}}_applyJoins(e,t,r){const o=Array.isArray(t)?t:[t];for(const t of o)"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,o]of Object.entries(t))e.withWhere(r,o)}catch(e){logger.warn("Failed to apply withWhere:",e.message)}}_applyHavingClause(e,t){for(const[r,o]of Object.entries(t))"object"==typeof o&&o.operator?e.having(r,o.operator,o.value):e.having(r,o)}_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,
|
|
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,o){if(null!==o)if(Array.isArray(o))e.orWhereIn(t,o);else switch(r){case"between":e.orWhereBetween(t,o);break;case"notBetween":e.orWhereNotBetween(t,o);break;case"in":e.orWhereIn(t,o);break;case"notIn":e.orWhereNotIn(t,o);break;case"like":e.orWhere(t,"like",o);break;default:e.orWhere(t,r,o)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.whereIn(t,o);else switch(r){case"between":e.whereBetween(t,o);break;case"notBetween":e.whereNotBetween(t,o);break;case"in":e.whereIn(t,o);break;case"notIn":e.whereNotIn(t,o);break;case"like":e.where(t,"like",o);break;default:e.where(t,r,o)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:o})=>t?t({current:e,part:r,source:o}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{direct:{},nested:{}};const r=`${t}.`,o={},i={};for(const[t,s]of Object.entries(e))if(t.startsWith(r)){const e=t.slice(r.length);e.includes(".")?i[e]=s:o[e]=s}return{direct:o,nested:i}}_applyWithWhereConditions(e,t){for(const[r,o]of Object.entries(t)){const{joinType:t="AND",column:i}=this.parseWhereColumn(r),{operator:s,value:l}=this.parseWhereValue(o);"AND"===t?this._applyAndWhereCondition(e,i,s,l):this._applyOrWhereCondition(e,i,s,l)}}async fetchRelatedRows(e,t,r={}){if(e.through){let o=await this.db(e.through).whereIn(e.throughLocalKey,t),i=this.db(e.table).whereIn(e.foreignKey,o.map(t=>t[e.throughForeignKey]));return Object.keys(r).length>0&&this._applyWithWhereConditions(i,r),i}{let o=this.db(e.table).whereIn(e.foreignKey,t);return Object.keys(r).length>0&&this._applyWithWhereConditions(o,r),o}}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:o,withTree:i,relation:s,withWhere:l={}}=e;if(!s){const e=this.getHookService().getModelInstance(o),s=`get${r.charAt(0).toUpperCase()+r.slice(1)}Relation`;if("function"==typeof e[s]){const{direct:n,nested:a}=this._getWithWhereForRelation(l,r),h={rows:t,relName:r,model:o,withTree:i,controller:this.controllerWrapper,relation:o.hasRelations[r],qb:this,db:this.db,withWhere:n,nestedWithWhere:a};await e[s](h)}else logger.warn(`Method ${s} not found in model ${o.name}`);return t}let n=t.map(e=>e[s.localKey]);const a="one"===s?.type;let h=[];const{direct:c,nested:p}=this._getWithWhereForRelation(l,r);h=await this.fetchRelatedRows(s,n,c);let y=new Map;for(const e of h){let t=e[s.foreignKey];y.has(t)||y.set(t,[]),y.get(t).push(e)}for(const e of t){let t=e[s.localKey];a&&1==y.get(t)?.length?e[r]=y.get(t)[0]:e[r]=y.get(t)||[]}let u=Object.keys(i);for(const e of u){let o=i[e],s=t.filter(e=>a?e[r]:e[r].length>0).map(e=>e[r]).reduce((e,t)=>e.concat(t),[]),l=this.utils.getModel(this.controllerWrapper,r);await this.fetchAndAttachRelated({parentRows:s,relName:e,model:l,withTree:o,relation:l.hasRelations[e],withWhere:p})}return t}filterWhere(e,t=""){return t?Object.keys(e).filter(e=>e.includes(t)).reduce((r,o)=>(r[o.replace(t,"")]=e[o],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:o,withWhere:i,select:s,orderBy:l={column:"id",direction:"asc"},limit:n=10,offset:a=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);s&&(Array.isArray(s)||"string"==typeof s)?b.select(s):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,o),c&&(Array.isArray(c),b.groupBy(c)),p&&this._applyHavingClause(b,p),l&&this._applyOrderBy(b,l);let w=!1,_=n,m=a,A=1,j=0;h&&n>0&&(A=Math.max(1,parseInt(h)),m=(A-1)*n),n>0&&(b.limit(_),m>0&&b.offset(m));const k=[],C=b.toSQL();k.push(C.sql);const O=await b;if(o&&o.length>0){const t=this.buildWithTree(o);for(const r of Object.keys(t))await this.fetchAndAttachRelated({parentRows:O,relName:r,model:e,withTree:t[r],relation:e.hasRelations[r],withWhere:i||{}})}let R=null;if(n>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");const o=t.count("* as cnt");k.push(o.toSQL().sql);R=(await o.first()).cnt}catch(e){logger.warn("Failed to get total count:",e.message),R=O.length}n>0&&null!==R&&(j=Math.ceil(R/n),w=A<j);const B=w?A+1:null,N=A>1?A-1:null;return{data:O,totalCount:R,...this.controllerWrapper.debug?{sqlDebug:k}:{},...n>0?{pagination:{page:A,limit:_,offset:m,totalPages:j,hasNext:w,hasPrev:A>1,nextPage:B,prevPage:N}}:{}}}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 o of t)this._applyWhereClause(e,o,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)=>!e.startsWith("!")&&("__exists__"!==e&&("object"!=typeof t||null===t)));for(const[t,o]of Object.entries(r)){const{joinType:r="AND",column:i}=this.parseWhereColumn(t),{operator:s,value:l}=this.parseWhereValue(o);"AND"===r?this._applyAndWhereCondition(e,i,s,l):this._applyOrWhereCondition(e,i,s,l)}}this._applyNestedWhere(e,t,r)}}_getNestedWhereConditions(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,o={};for(const[i,s]of Object.entries(e))if(i.startsWith(r)){o[i.slice(r.length)]=s}else i===t&&!0===s&&(o.__exists__=!0);return o}_getTopLevelRelationsFromWhere(e){if(!e||"object"!=typeof e)return[];const t=new Set;for(const r of Object.keys(e))if(r.includes(".")){const e=r.split(".")[0];t.add(e)}else r.startsWith("!")&&t.add(r);return[...t]}_applyNestedWhere(e,t,r){let o=this,i=e._getMyModel();const s=this._getTopLevelRelationsFromWhere(t);if(0!==s.length)for(let r of s){let s=r.startsWith("!"),l=s?r.slice(1):r,n=this._getNestedWhereConditions(t,r);if(0===Object.keys(n).length)continue;let a,h=i.hasRelations?.[l];if(!h){logger.warn(`Relation ${l} not found in model ${i.modelName}`);continue}try{a=this.utils.getModel(this.controllerWrapper,h.table||l)}catch(e){try{a=this.utils.getModel(this.controllerWrapper,l)}catch(e){logger.warn(`Model for relation ${l} (table: ${h.table}) not found`);continue}}e[s?"whereNotExists":"whereExists"](function(){let e=o.getQueryBuilder(a,this.select("*").from(h.table));e.whereRaw(`"${h.table}"."${h.foreignKey}" = "${i.table}"."${h.localKey}"`);if(!(!0===n.__exists__&&1===Object.keys(n).length)){const t={...n};delete t.__exists__,o._applyWhereClause(e,t,[])}})}}_applyJoins(e,t,r){const o=Array.isArray(t)?t:[t];for(const t of o)"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,o]of Object.entries(t))e.withWhere(r,o)}catch(e){logger.warn("Failed to apply withWhere:",e.message)}}_applyHavingClause(e,t){for(const[r,o]of Object.entries(t))"object"==typeof o&&o.operator?e.having(r,o.operator,o.value):e.having(r,o)}_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.52",
|
|
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",
|