@dreamtree-org/korm-js 1.0.46 → 1.0.48

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/Logger.js ADDED
@@ -0,0 +1 @@
1
+ const LOG_LEVELS={NONE:0,ERROR:1,WARN:2,INFO:3,LOG:4,DEBUG:5};class Logger{constructor(e={}){this.prefix=e.prefix||"[KORM]",this.enabled=!1!==e.enabled,this.level=this._parseLevel(e.level||process.env.KORM_LOG_LEVEL||"warn"),this.timestamps=e.timestamps||!1}_parseLevel(e){if("number"==typeof e)return e;const s=String(e).toUpperCase();return void 0!==LOG_LEVELS[s]?LOG_LEVELS[s]:LOG_LEVELS.WARN}_getTimestamp(){return(new Date).toISOString()}_formatMessage(e,s){const t=[this.prefix];return this.timestamps&&t.push(`[${this._getTimestamp()}]`),t.push(`[${e}]`),t}debug(...e){this.enabled&&this.level>=LOG_LEVELS.DEBUG&&console.debug(...this._formatMessage("DEBUG",e),...e)}log(...e){this.enabled&&this.level>=LOG_LEVELS.LOG&&console.log(...this._formatMessage("LOG",e),...e)}info(...e){this.enabled&&this.level>=LOG_LEVELS.INFO&&console.info(...this._formatMessage("INFO",e),...e)}warn(...e){this.enabled&&this.level>=LOG_LEVELS.WARN&&console.warn(...this._formatMessage("WARN",e),...e)}error(...e){this.enabled&&this.level>=LOG_LEVELS.ERROR&&console.error(...this._formatMessage("ERROR",e),...e)}setLevel(e){this.level=this._parseLevel(e)}enable(){this.enabled=!0}disable(){this.enabled=!1}child(e){return new Logger({prefix:`${this.prefix}${e}`,enabled:this.enabled,level:this.level,timestamps:this.timestamps})}}const logger=new Logger;module.exports=logger,module.exports.Logger=Logger,module.exports.LOG_LEVELS=LOG_LEVELS;
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|auto_increment",
1174
- "username": "varchar|size:255",
1175
- "email": "varchar|size:255",
1176
- "first_name": "varchar|size:255|nullable",
1177
- "last_name": "varchar|size:255|nullable",
1178
- "age": "int|size:4|nullable",
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|nullable"
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){const t=e.modelName,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
+ const path=require("path"),logger=require("../../Logger");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 logger.debug("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:l,relation:o}=e;let a=t.map(e=>e[o.localKey]),s=[];s=await this.fetchRelatedRows(o,a);let n=new Map;for(const e of s){let t=e[o.foreignKey];n.has(t)||n.set(t,[]),n.get(t).push(e)}for(const e of t){let t=e[o.localKey];"one"===o?.type&&1==n.get(t)?.length?e[r]=n.get(t)[0]:e[r]=n.get(t)||[]}let h=Object.keys(l);for(const e of h){let i=l[e],o=t.filter(e=>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:o,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:l,select:o,orderBy:a={column:"id",direction:"asc"},limit:s=10,offset:n=0,page:h,groupBy:p,having:c,distinct:y,join:u,leftJoin:f,rightJoin:d,innerJoin:g,count:w=!1}=t;let W=this.getQueryBuilder(e);o&&(Array.isArray(o)||"string"==typeof o)?W.select(o):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"),d&&this._applyJoins(W,d,"rightJoin"),g&&this._applyJoins(W,g,"innerJoin"),this._applyWhereClause(W,r,i),p&&(Array.isArray(p),W.groupBy(p)),c&&this._applyHavingClause(W,c),a&&this._applyOrderBy(W,a);let b=!1,A=s,_=n,m=1,k=0;h&&s>0&&(m=Math.max(1,parseInt(h)),_=(m-1)*s),s>0&&(W.limit(A),_>0&&W.offset(_));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]})}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),b=m<k);return{data:j,totalCount:B,...s>0?{pagination:{page:m,limit:A,offset:_,totalPages:k,hasNext:b,hasPrev:m>1,nextPage:b?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:l}=this.parseWhereColumn(t),{operator:o,value:a}=this.parseWhereValue(i);"AND"===r?this._applyAndWhereCondition(e,l,o,a):this._applyOrWhereCondition(e,l,o,a)}}r&&r.length>0&&this._applyNestedWhere(e,t,r)}}_applyNestedWhere(e,t,r){let i=this,l=e._getMyModel();if(r&&r.length>0)for(const o of r){let a=this.helperUtility.getDotWalkQuery(t,o);if(a&&Object.keys(a).length>0){let t=this.utils.getModel(this.controllerWrapper,o),s=l.hasRelations[o],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} = ${l.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
+ 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:a}=this.parseWhereValue(i);"AND"===t?this._applyAndWhereCondition(e,o,l,a):this._applyOrWhereCondition(e,o,l,a)}}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:a={}}=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 s={rows:t,relName:r,model:i,withTree:o,controller:this.controllerWrapper,relation:i.hasRelations[r],qb:this,db:this.db,withWhere:a};await e[l](s)}return t}let s=t.map(e=>e[l.localKey]),n=[];const h="one"===l?.type,p=this._getWithWhereForRelation(a,r);n=await this.fetchRelatedRows(l,s,p);let c=new Map;for(const e of n){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),[]),s=this.utils.getModel(this.controllerWrapper,r);await this.fetchAndAttachRelated({parentRows:l,relName:e,model:s,withTree:i,relation:s.hasRelations[e],withWhere:a})}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: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),a&&this._applyOrderBy(w,a);let b=!1,_=s,A=n,m=1,k=0;h&&s>0&&(m=Math.max(1,parseInt(h)),A=(m-1)*s),s>0&&(w.limit(_),A>0&&w.offset(A));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(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"),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}s>0&&null!==C&&(k=Math.ceil(C/s),b=m<k);return{data:j,totalCount:C,...s>0?{pagination:{page:m,limit:_,offset:A,totalPages:k,hasNext:b,hasPrev:m>1,nextPage:b?m+1:null,prevPage:m>1?m-1:null}}:{}}}catch(e){throw logger.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);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: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){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 CurdTable=require("./CurdTable"),HelperUtility=require("./HelperUtility");class SyncTable{constructor(e,t,n=null){this.db=e,this.utils=t,this.controllerWrapper=n,this.curd=new CurdTable(e,t,n),this.helperUtility=new HelperUtility}_getClientName(){return"mysql"}async executeSql(e,t=[]){return this.db.raw(e,t)}async existsTable(e){return this.db.schema.hasTable(e)}async getCurrentColumns(e){const t=this.db.client.database(),[n]=await this.executeSql("\n SELECT \n c.COLUMN_NAME,\n c.DATA_TYPE,\n c.COLUMN_TYPE,\n c.IS_NULLABLE,\n c.COLUMN_KEY,\n c.EXTRA,\n c.CHARACTER_MAXIMUM_LENGTH,\n c.COLUMN_DEFAULT,\n c.COLUMN_COMMENT AS COMMENT,\n kcu.REFERENCED_TABLE_NAME,\n kcu.REFERENCED_COLUMN_NAME\n FROM information_schema.COLUMNS c\n LEFT JOIN information_schema.KEY_COLUMN_USAGE kcu\n ON c.TABLE_SCHEMA = kcu.TABLE_SCHEMA\n AND c.TABLE_NAME = kcu.TABLE_NAME\n AND c.COLUMN_NAME = kcu.COLUMN_NAME\n WHERE c.TABLE_SCHEMA = ?\n AND c.TABLE_NAME = ?\n ",[t,e]),a={};for(const e of n)a[e.COLUMN_NAME]=this.utils.formatColumnDef(e.COLUMN_NAME,e);return a}hasColumnChanged(e,t){const n={isNullableChanged:e.nullable!==t.nullable,isTypeChanged:e.type!==t.type,isSizeChanged:e.size!==t.size,isUnsignedChanged:e.isUnsigned!==t.isUnsigned,isPrimaryChanged:e.primary!==t.primary,isUniqueChanged:e.unique!==t.unique,isAutoIncrementChanged:e.autoIncrement!==t.autoIncrement,isDefaultChanged:e.default!==t.default,isOnUpdateChanged:e.onUpdate!==t.onUpdate,isCommentChanged:e.comment!==t.comment,isForeignKeyChanged:e.hasForeignKey!==t.hasForeignKey},a=Object.values(n).some(Boolean);return a&&console.log({changes:n,oldComment:e.comment,newComment:t.comment,name:e.name}),a}async getAlterations(e){const t={add:[],drop:[],modify:[]},n=await this.getCurrentColumns(e.table);for(const[a,s]of Object.entries(e.columns)){const e=this.utils.formatColumnSchema(a,s),o=n[a];o?(e.oldColDef=o,this.hasColumnChanged(o,e)&&t.modify.push(e)):t.add.push(e)}for(const a of Object.keys(n))e.columns[a]||t.drop.push({name:a});return t}getColumnStr(e,t,n={actionType:"CREATE",tableName:""}){const{actionType:a,tableName:s}=n,o="string"==typeof t?this.utils.formatColumnSchema(e,t):t,i=o.type,l=o.size??this.utils.getDefaultTypeSize(i);let r=`\`${e}\` ${i}${l?`(${l})`:""}`;if(o.isUnsigned&&(r+=" UNSIGNED"),o.primary&&["CREATE","ADD_COLUMN"].includes(a)&&(r+=" PRIMARY KEY"),o.autoIncrement&&(r+=" AUTO_INCREMENT"),o.nullable||(r+=" NOT NULL"),o.unique&&(r+=" UNIQUE"),o.default&&(r+=` DEFAULT ${o.default}`),o.onUpdate&&(r+=` ON UPDATE ${o.onUpdate}`),o.comment&&(r+=` COMMENT '${this.utils.escapeComment(o.comment)}'`),o.hasForeignKey&&1===o.foreignMapTables?.length&&"CREATE"===a){const{table:t,column:n}=o.foreignMapTables[0],a=`idx_${s}__${e}__fk_${t}_${n}`;r+=`, KEY \`${a}\` (\`${e}\`), CONSTRAINT \`cn_${a}\`\n FOREIGN KEY (\`${e}\`) REFERENCES \`${t}\` (\`${n}\`)\n ON DELETE RESTRICT ON UPDATE RESTRICT`}return r}async createTable(e){const t=e.table,n=e.columns,a=[];for(const[e,s]of Object.entries(n))a.push(this.getColumnStr(e,s,{actionType:"CREATE",tableName:t}));const s=`CREATE TABLE IF NOT EXISTS \`${t}\` (${a.join(", ")})`;await this.executeSql(s)}async alterTable(e,t){if(!t||"object"!=typeof t)throw new Error("alterations must be an object");const n=[];if(t.add?.length)for(const a of t.add)n.push(`ADD COLUMN ${this.getColumnStr(a.name,a,{actionType:"ADD_COLUMN",tableName:e})}`);if(t.drop?.length)for(const e of t.drop)n.push(`DROP COLUMN \`${e.name}\``);if(t.modify?.length)for(const a of t.modify)n.push(`MODIFY COLUMN ${this.getColumnStr(a.name,a,{actionType:"MODIFY_COLUMN",tableName:e})}`);if(!n.length)return void console.log("No alterations to apply for",e);const a=`ALTER TABLE \`${e}\` ${n.join(", ")}`;await this.executeSql(a)}async alterColumn(e,t,n){const a=`ALTER TABLE \`${e}\` MODIFY COLUMN ${this.getColumnStr(t,n,{actionType:"MODIFY_COLUMN",tableName:e})}`;await this.executeSql(a)}async alterIndex(e,t,n){const a=`ALTER TABLE \`${e}\` MODIFY INDEX ${`${t} ${n.type} ${n.unique?"UNIQUE":""}`}`;await this.executeSql(a)}async dropIndex(e,t){const n=`DROP INDEX \`${t}\` ON \`${e}\``;await this.executeSql(n)}async dropTable(e){const t=`DROP TABLE IF EXISTS \`${e}\``;await this.executeSql(t)}async updateTable(e){console.log(`Update table not implemented for ${e.table}`)}async syncTable(e){if(await this.existsTable(e.table)){const t=await this.getAlterations(e);return console.log(...Object.values(t)),void await this.alterTable(e.table,t)}await this.createTable(e)}async syncSeedData(e,t){let n=this.utils.getModel(this.controllerWrapper,t),a=await this.curd.processRequest({action:"count"},n.name,{isCallFromServer:!0});if(console.log("count",a),a>0)console.log("Seed data already synced for",t);else if(e.seed&&Array.isArray(e.seed)){for(const t of e.seed)await this.curd.processRequest({action:"create",data:t},n.name,{isCallFromServer:!0});console.log("Seed data synced for",t)}}async syncDatabase(){if(!this.controllerWrapper?.schema)throw new Error("controllerWrapper.schema not set.");const e=this.controllerWrapper.schema;for(const t of Object.keys(e))await this.syncTable(e[t]),await this.syncSeedData(e[t],t);console.log("Database synced by SyncTable...")}async getTablesOfDatabase(){const[e]=await this.db.raw("SHOW TABLES");return e.map(e=>e[`Tables_in_${this.db.client.database()}`])}getColumnString(e){return Object.keys(e).reduce((t,n)=>{const a=e[n];let s=a.type,o=a.size,i=a.isUnsigned,l=a.primary,r=a.autoIncrement,c=a.nullable,u=a.unique,m=a.default,d=a.onUpdate,E=a.comment,h=a.hasForeignKey,C=a.foreignMapTables?.[0]?.table,T=a.foreignMapTables?.[0]?.column;return t[n]=`${s}`,o&&(t[n]+=`|size:${o}`),i&&(t[n]+="|unsigned"),l&&(t[n]+="|primaryKey"),r&&(t[n]+="|autoIncrement"),c&&(t[n]+="|nullable"),u&&(t[n]+="|unique"),m&&(t[n]+=`|default:${m}`),d&&(t[n]+=`|onUpdate:${d}`),E&&(t[n]+=`|comment:${E}`),h&&(t[n]+=`|foreignKey:${C}:${T}`),t},{})}async getRelations(e){await this.db.raw(`SHOW CREATE TABLE ${e}`);return{}}async generateSchema(){const e=await this.getTablesOfDatabase(),t={};for(const n of e){let e=this.helperUtility.modelName(n),a=await this.getRelations(n);t[e]={table:n,alias:e,columns:this.getColumnString(await this.getCurrentColumns(n)),modelName:e,seed:[],hasRelations:a,indexes:[]}}return t}}module.exports=SyncTable;
1
+ const CurdTable=require("./CurdTable"),HelperUtility=require("./HelperUtility"),logger=require("../../Logger");class SyncTable{constructor(e,t,a=null){this.db=e,this.utils=t,this.controllerWrapper=a,this.curd=new CurdTable(e,t,a),this.helperUtility=new HelperUtility}_getClientName(){return"mysql"}async executeSql(e,t=[]){return this.db.raw(e,t)}async existsTable(e){return this.db.schema.hasTable(e)}async getCurrentColumns(e){const t=this.db.client.database(),[a]=await this.executeSql("\n SELECT \n c.COLUMN_NAME,\n c.DATA_TYPE,\n c.COLUMN_TYPE,\n c.IS_NULLABLE,\n c.COLUMN_KEY,\n c.EXTRA,\n c.CHARACTER_MAXIMUM_LENGTH,\n c.COLUMN_DEFAULT,\n c.COLUMN_COMMENT AS COMMENT,\n kcu.REFERENCED_TABLE_NAME,\n kcu.REFERENCED_COLUMN_NAME\n FROM information_schema.COLUMNS c\n LEFT JOIN information_schema.KEY_COLUMN_USAGE kcu\n ON c.TABLE_SCHEMA = kcu.TABLE_SCHEMA\n AND c.TABLE_NAME = kcu.TABLE_NAME\n AND c.COLUMN_NAME = kcu.COLUMN_NAME\n WHERE c.TABLE_SCHEMA = ?\n AND c.TABLE_NAME = ?\n ",[t,e]),n={};for(const e of a)n[e.COLUMN_NAME]=this.utils.formatColumnDef(e.COLUMN_NAME,e);return n}hasColumnChanged(e,t){const a={isNullableChanged:e.nullable!==t.nullable,isTypeChanged:e.type!==t.type,isSizeChanged:e.size!==t.size,isUnsignedChanged:e.isUnsigned!==t.isUnsigned,isPrimaryChanged:e.primary!==t.primary,isUniqueChanged:e.unique!==t.unique,isAutoIncrementChanged:e.autoIncrement!==t.autoIncrement,isDefaultChanged:e.default!==t.default,isOnUpdateChanged:e.onUpdate!==t.onUpdate,isCommentChanged:e.comment!==t.comment,isForeignKeyChanged:e.hasForeignKey!==t.hasForeignKey},n=Object.values(a).some(Boolean);return n&&logger.debug({changes:a,oldComment:e.comment,newComment:t.comment,name:e.name}),n}async getAlterations(e){const t={add:[],drop:[],modify:[]},a=await this.getCurrentColumns(e.table);for(const[n,s]of Object.entries(e.columns)){const e=this.utils.formatColumnSchema(n,s),o=a[n];o?(e.oldColDef=o,this.hasColumnChanged(o,e)&&t.modify.push(e)):t.add.push(e)}for(const n of Object.keys(a))e.columns[n]||t.drop.push({name:n});return t}getColumnStr(e,t,a={actionType:"CREATE",tableName:""}){const{actionType:n,tableName:s}=a,o="string"==typeof t?this.utils.formatColumnSchema(e,t):t,i=o.type,r=o.size??this.utils.getDefaultTypeSize(i);let l=`\`${e}\` ${i}${r?`(${r})`:""}`;if(o.isUnsigned&&(l+=" UNSIGNED"),o.primary&&["CREATE","ADD_COLUMN"].includes(n)&&(l+=" PRIMARY KEY"),o.autoIncrement&&(l+=" AUTO_INCREMENT"),o.nullable||(l+=" NOT NULL"),o.unique&&(l+=" UNIQUE"),o.default&&(l+=` DEFAULT ${o.default}`),o.onUpdate&&(l+=` ON UPDATE ${o.onUpdate}`),o.comment&&(l+=` COMMENT '${this.utils.escapeComment(o.comment)}'`),o.hasForeignKey&&1===o.foreignMapTables?.length&&"CREATE"===n){const{table:t,column:a}=o.foreignMapTables[0],n=`idx_${s}__${e}__fk_${t}_${a}`;l+=`, KEY \`${n}\` (\`${e}\`), CONSTRAINT \`cn_${n}\`\n FOREIGN KEY (\`${e}\`) REFERENCES \`${t}\` (\`${a}\`)\n ON DELETE RESTRICT ON UPDATE RESTRICT`}return l}async createTable(e){const t=e.table,a=e.columns,n=[];for(const[e,s]of Object.entries(a))n.push(this.getColumnStr(e,s,{actionType:"CREATE",tableName:t}));const s=`CREATE TABLE IF NOT EXISTS \`${t}\` (${n.join(", ")})`;await this.executeSql(s)}async alterTable(e,t){if(!t||"object"!=typeof t)throw new Error("alterations must be an object");const a=[];if(t.add?.length)for(const n of t.add)a.push(`ADD COLUMN ${this.getColumnStr(n.name,n,{actionType:"ADD_COLUMN",tableName:e})}`);if(t.drop?.length)for(const e of t.drop)a.push(`DROP COLUMN \`${e.name}\``);if(t.modify?.length)for(const n of t.modify)a.push(`MODIFY COLUMN ${this.getColumnStr(n.name,n,{actionType:"MODIFY_COLUMN",tableName:e})}`);if(!a.length)return void logger.info("No alterations to apply for",e);const n=`ALTER TABLE \`${e}\` ${a.join(", ")}`;await this.executeSql(n)}async alterColumn(e,t,a){const n=`ALTER TABLE \`${e}\` MODIFY COLUMN ${this.getColumnStr(t,a,{actionType:"MODIFY_COLUMN",tableName:e})}`;await this.executeSql(n)}async alterIndex(e,t,a){const n=`ALTER TABLE \`${e}\` MODIFY INDEX ${`${t} ${a.type} ${a.unique?"UNIQUE":""}`}`;await this.executeSql(n)}async dropIndex(e,t){const a=`DROP INDEX \`${t}\` ON \`${e}\``;await this.executeSql(a)}async dropTable(e){const t=`DROP TABLE IF EXISTS \`${e}\``;await this.executeSql(t)}async updateTable(e){logger.warn(`Update table not implemented for ${e.table}`)}async syncTable(e){if(await this.existsTable(e.table)){const t=await this.getAlterations(e);return logger.debug(...Object.values(t)),void await this.alterTable(e.table,t)}await this.createTable(e)}async syncSeedData(e,t){let a=this.utils.getModel(this.controllerWrapper,t),n=await this.curd.processRequest({action:"count"},a.name,{isCallFromServer:!0});if(logger.debug("count",n),n>0)logger.info("Seed data already synced for",t);else if(e.seed&&Array.isArray(e.seed)){for(const t of e.seed)await this.curd.processRequest({action:"create",data:t},a.name,{isCallFromServer:!0});logger.info("Seed data synced for",t)}}async syncDatabase(){if(!this.controllerWrapper?.schema)throw new Error("controllerWrapper.schema not set.");const e=this.controllerWrapper.schema;for(const t of Object.keys(e))await this.syncTable(e[t]),await this.syncSeedData(e[t],t);logger.info("Database synced by SyncTable...")}async getTablesOfDatabase(){const[e]=await this.db.raw("SHOW TABLES");return e.map(e=>e[`Tables_in_${this.db.client.database()}`])}getColumnString(e){return Object.keys(e).reduce((t,a)=>{const n=e[a];let s=n.type,o=n.size,i=n.isUnsigned,r=n.primary,l=n.autoIncrement,c=n.nullable,u=n.unique,m=n.default,d=n.onUpdate,E=n.comment,h=n.hasForeignKey,C=n.foreignMapTables?.[0]?.table,T=n.foreignMapTables?.[0]?.column;return t[a]=`${s}`,o&&(t[a]+=`|size:${o}`),i&&(t[a]+="|unsigned"),r&&(t[a]+="|primaryKey"),l&&(t[a]+="|autoIncrement"),c&&(t[a]+="|nullable"),u&&(t[a]+="|unique"),m&&(t[a]+=`|default:${m}`),d&&(t[a]+=`|onUpdate:${d}`),E&&(t[a]+=`|comment:${E}`),h&&(t[a]+=`|foreignKey:${C}:${T}`),t},{})}async getRelations(e){await this.db.raw(`SHOW CREATE TABLE ${e}`);return{}}async generateSchema(){const e=await this.getTablesOfDatabase(),t={};for(const a of e){let e=this.helperUtility.modelName(a),n=await this.getRelations(a);t[e]={table:a,alias:e,columns:this.getColumnString(await this.getCurrentColumns(a)),modelName:e,seed:[],hasRelations:n,indexes:[]}}return t}}module.exports=SyncTable;
@@ -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){const t=e.modelName,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
+ const path=require("path"),logger=require("../../Logger");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 logger.debug("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:l,relation:o}=e;let a=t.map(e=>e[o.localKey]),s=[];s=await this.fetchRelatedRows(o,a);let n=new Map;for(const e of s){let t=e[o.foreignKey];n.has(t)||n.set(t,[]),n.get(t).push(e)}for(const e of t){let t=e[o.localKey];"one"===o?.type&&1==n.get(t)?.length?e[r]=n.get(t)[0]:e[r]=n.get(t)||[]}let h=Object.keys(l);for(const e of h){let i=l[e],o=t.filter(e=>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:o,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:l,select:o,orderBy:a={column:"id",direction:"asc"},limit:s=10,offset:n=0,page:h,groupBy:p,having:c,distinct:y,join:u,leftJoin:f,rightJoin:d,innerJoin:g,count:w=!1}=t;let W=this.getQueryBuilder(e);o&&(Array.isArray(o)||"string"==typeof o)?W.select(o):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"),d&&this._applyJoins(W,d,"rightJoin"),g&&this._applyJoins(W,g,"innerJoin"),this._applyWhereClause(W,r,i),p&&(Array.isArray(p),W.groupBy(p)),c&&this._applyHavingClause(W,c),a&&this._applyOrderBy(W,a);let b=!1,A=s,_=n,m=1,k=0;h&&s>0&&(m=Math.max(1,parseInt(h)),_=(m-1)*s),s>0&&(W.limit(A),_>0&&W.offset(_));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]})}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),b=m<k);return{data:j,totalCount:B,...s>0?{pagination:{page:m,limit:A,offset:_,totalPages:k,hasNext:b,hasPrev:m>1,nextPage:b?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:l}=this.parseWhereColumn(t),{operator:o,value:a}=this.parseWhereValue(i);"AND"===r?this._applyAndWhereCondition(e,l,o,a):this._applyOrWhereCondition(e,l,o,a)}}r&&r.length>0&&this._applyNestedWhere(e,t,r)}}_applyNestedWhere(e,t,r){let i=this,l=e._getMyModel();if(r&&r.length>0)for(const o of r){let a=this.helperUtility.getDotWalkQuery(t,o);if(a&&Object.keys(a).length>0){let t=this.utils.getModel(this.controllerWrapper,o),s=l.hasRelations[o],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} = ${l.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
+ 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:s}=this.parseWhereValue(i);"AND"===t?this._applyAndWhereCondition(e,o,l,s):this._applyOrWhereCondition(e,o,l,s)}}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:s={}}=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 n={rows:t,relName:r,model:i,withTree:o,controller:this.controllerWrapper,relation:i.hasRelations[r],qb:this,db:this.db,withWhere:s};await e[l](n)}return t}let n=t.map(e=>e[l.localKey]);const a="one"===l?.type;let h=[];const p=this._getWithWhereForRelation(s,r);h=await this.fetchRelatedRows(l,n,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];a&&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=>a?e[r]:e[r].length>0).map(e=>e[r]).reduce((e,t)=>e.concat(t),[]),n=this.utils.getModel(this.controllerWrapper,r);await this.fetchAndAttachRelated({parentRows:l,relName:e,model:n,withTree:i,relation:n.hasRelations[e],withWhere:s})}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:s={column:"id",direction:"asc"},limit:n=10,offset:a=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),s&&this._applyOrderBy(w,s);let b=!1,_=n,A=a,k=1,m=0;h&&n>0&&(k=Math.max(1,parseInt(h)),A=(k-1)*n),n>0&&(w.limit(_),A>0&&w.offset(A));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(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"),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}n>0&&null!==C&&(m=Math.ceil(C/n),b=k<m);return{data:j,totalCount:C,...n>0?{pagination:{page:k,limit:_,offset:A,totalPages:m,hasNext:b,hasPrev:k>1,nextPage:b?k+1:null,prevPage:k>1?k-1:null}}:{}}}catch(e){throw logger.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);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:s}=this.parseWhereValue(i);"AND"===r?this._applyAndWhereCondition(e,o,l,s):this._applyOrWhereCondition(e,o,l,s)}}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 s=this.helperUtility.getDotWalkQuery(t,l);if(s&&Object.keys(s).length>0){let t=this.utils.getModel(this.controllerWrapper,l),n=o.hasRelations[l],a=r.map(e=>this.helperUtility.pluckDotWalkKey(e,1));e.whereExists(function(){let e=i.getQueryBuilder(t,this.select("*").from(t.table));e.whereRaw(`${n.foreignKey} = ${o.table}.${n.localKey}`),i._applyWhereClause(e,s,a)})}}}_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 CurdTable=require("./CurdTable"),HelperUtility=require("./HelperUtility"),{getSchemaType:getSchemaType}=require("./DataTypeMap");class SyncTable{constructor(e,t,n=null){this.db=e,this.utils=t,this.controllerWrapper=n,this.curd=new CurdTable(e,t,n),this.helperUtility=new HelperUtility}_getClientName(){return"pg"}async executeSql(e,t=[]){return this.db.raw(e,t)}async existsTable(e){return this.db.schema.hasTable(e)}async getTablesReferencedByTable(e,t="public"){return(await this.executeSql("SELECT\n tc.table_name AS \"TABLE_NAME\", -- The table that contains the FK (e.g., 'Posts')\n kcu.column_name AS \"REFERENCING_COLUMN_NAME\", -- The FK column (e.g., 'user_id')\n kcu2.column_name AS \"REFERENCED_COLUMN_NAME\" -- The PK/Unique column (e.g., 'id')\n FROM\n information_schema.referential_constraints AS rc\n -- Join to get the table and column being referenced (the PK/Unique key on the target table)\n JOIN\n information_schema.key_column_usage AS kcu2\n ON rc.unique_constraint_name = kcu2.constraint_name\n AND rc.unique_constraint_schema = kcu2.constraint_schema\n -- Join to get the table constraint information for the foreign key\n JOIN\n information_schema.table_constraints AS tc\n ON rc.constraint_name = tc.constraint_name\n AND rc.constraint_schema = tc.table_schema\n -- Join to get the column in the referencing table (the FK column)\n JOIN\n information_schema.key_column_usage AS kcu\n ON rc.constraint_name = kcu.constraint_name\n AND rc.constraint_schema = kcu.table_schema\n -- Link the FK column to its corresponding PK/Unique column\n AND kcu.position_in_unique_constraint = kcu2.ordinal_position\n WHERE\n kcu2.table_name = ?\n AND kcu2.table_schema = ?\n AND tc.constraint_type = 'FOREIGN KEY'\n ORDER BY\n tc.table_name, kcu.column_name",[e,t])).rows||[]}async getTablesWithColumn(e){return(await this.executeSql("\n SELECT table_name\n FROM information_schema.columns\n WHERE column_name = ?\n ",[e])).rows||[]}async getCurrentColumns(e){const t=(await this.executeSql('\n SELECT \n c.column_name AS "COLUMN_NAME",\n c.data_type AS "DATA_TYPE",\n c.udt_name AS "UDT_NAME",\n c.is_nullable AS "IS_NULLABLE",\n c.column_default AS "COLUMN_DEFAULT",\n c.character_maximum_length AS "CHARACTER_MAXIMUM_LENGTH",\n pgd.description AS "COMMENT",\n tc.constraint_type AS "CONSTRAINT_TYPE",\n kcu2.table_name AS "REFERENCED_TABLE_NAME",\n kcu2.column_name AS "REFERENCED_COLUMN_NAME"\n FROM information_schema.columns c\n LEFT JOIN pg_catalog.pg_statio_all_tables as st\n ON c.table_schema = st.schemaname AND c.table_name = st.relname\n LEFT JOIN pg_catalog.pg_description pgd\n ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position\n LEFT JOIN information_schema.key_column_usage kcu\n ON c.table_name = kcu.table_name\n AND c.column_name = kcu.column_name\n AND c.table_schema = kcu.table_schema\n LEFT JOIN information_schema.table_constraints tc\n ON kcu.constraint_name = tc.constraint_name\n AND kcu.table_schema = tc.table_schema\n LEFT JOIN information_schema.referential_constraints rc\n ON tc.constraint_name = rc.constraint_name\n AND tc.table_schema = rc.constraint_schema\n LEFT JOIN information_schema.key_column_usage kcu2\n ON rc.unique_constraint_name = kcu2.constraint_name\n AND rc.unique_constraint_schema = kcu2.constraint_schema\n AND kcu.ordinal_position = kcu2.ordinal_position\n WHERE c.table_schema = ?\n AND c.table_name = ?\n ORDER BY c.ordinal_position\n ',["public",e])).rows||[],n={};for(const e of t)n[e.COLUMN_NAME]=this.utils.formatColumnDef(e.COLUMN_NAME,e);return n}logColumnChanges(e,t,n){console.log(`${t.name} changed`),e.isTypeChanged&&console.log(`Type changed from ${t.type} to ${n.type}`),e.isSizeChanged&&console.log(`Size changed from ${t.size} to ${n.size}`),e.isNullableChanged&&console.log(`Nullable changed from ${t.nullable} to ${n.nullable}`),e.isPrimaryChanged&&console.log(`Primary changed from ${t.primary} to ${n.primary}`),e.isUniqueChanged&&console.log(`Unique changed from ${t.unique} to ${n.unique}`),e.isAutoIncrementChanged&&console.log(`Auto increment changed from ${t.autoIncrement} to ${n.autoIncrement}`),e.isDefaultChanged&&console.log(`Default changed from ${t.default} to ${n.default}`),e.isOnUpdateChanged&&console.log(`On update changed from ${t.onUpdate} to ${n.onUpdate}`),e.isCommentChanged&&console.log(`Comment changed from ${t.comment} to ${n.comment}`),e.isForeignKeyChanged&&console.log(`ForeignKey changed from ${t.hasForeignKey} to ${n.hasForeignKey}`)}hasColumnChanged(e,t){const n={isNullableChanged:e.nullable!==t.nullable,isTypeChanged:e.type!==t.type,isSizeChanged:e.size!==t.size,isUnsignedChanged:e.isUnsigned!==t.isUnsigned,isPrimaryChanged:e.primary!==t.primary,isUniqueChanged:e.unique!==t.unique,isAutoIncrementChanged:e.autoIncrement!==t.autoIncrement,isDefaultChanged:e.default!==t.default,isOnUpdateChanged:e.onUpdate!==t.onUpdate,isCommentChanged:e.comment!==t.comment,isForeignKeyChanged:e.hasForeignKey!==t.hasForeignKey},a=Object.values(n).some(Boolean);return a&&this.logColumnChanges(n,e,t),a}async getAlterations(e){const t={add:[],drop:[],modify:[]},n=await this.getCurrentColumns(e.table);for(const[a,o]of Object.entries(e.columns)){const e=this.utils.formatColumnSchema(a,o),l=n[a];l?(e.oldColDef=l,this.hasColumnChanged(l,e)&&t.modify.push(e)):t.add.push(e)}for(const a of Object.keys(n))e.columns[a]||t.drop.push({name:a});return t}getColumnStr(e,t,n={actionType:"CREATE",tableName:""}){const{actionType:a,tableName:o}=n,l="string"==typeof t?this.utils.formatColumnSchema(e,t):t;let s=`"${e}"`,i=l.type,c=l.size??this.utils.getDefaultTypeSize(i);if(c&&["character varying","varchar","char","character"].includes(i)?s+=` ${i}(${c})`:s+="numeric"===i&&c?` ${i}(${c})`:` ${i}`,l.autoIncrement&&("integer"===i||"int4"===i?s=`"${e}" SERIAL`:"bigint"!==i&&"int8"!==i||(s=`"${e}" BIGSERIAL`)),l.primary&&["CREATE","ADD_COLUMN"].includes(a)&&(s+=" PRIMARY KEY"),!1===l.nullable||void 0===l.nullable?s+=" NOT NULL":!0===l.nullable&&(s+=" NULL"),l.unique&&(s+=" UNIQUE"),void 0!==l.default&&null!==l.default&&!l.primary&&!l.autoIncrement){s+=this.utils.isInternalDefault(l.default)?` DEFAULT ${l.default}`:` DEFAULT '${l.default}'`}if(l.hasForeignKey&&1===l.foreignMapTables?.length&&"CREATE"===a){const{table:e,column:t}=l.foreignMapTables[0];s+=` REFERENCES "${e}"("${t}")`,s+=" ON DELETE RESTRICT ON UPDATE RESTRICT"}return s}async createTable(e){const t=e.table,n=e.columns,a=[],o=[];for(const[e,l]of Object.entries(n)){const n="string"==typeof l?this.utils.formatColumnSchema(e,l):l;if(a.push(this.getColumnStr(e,n,{actionType:"CREATE",tableName:t})),n.hasForeignKey&&1===n.foreignMapTables?.length){const{table:a,column:l}=n.foreignMapTables[0],s=`fk_${t}_${e}_${a}_${l}`;o.push(`CONSTRAINT "${s}" FOREIGN KEY ("${e}") REFERENCES "${a}"("${l}") ON DELETE RESTRICT ON UPDATE RESTRICT`)}}const l=`CREATE TABLE IF NOT EXISTS "${t}" (${a.concat(o).join(", ")})`;await this.executeSql(l);for(const[e,a]of Object.entries(n)){const n="string"==typeof a?this.utils.formatColumnSchema(e,a):a;if(n.comment){const a=`COMMENT ON COLUMN "${t}"."${e}" IS '${this.utils.escapeComment(n.comment)}'`;await this.executeSql(a)}}for(const n of e.indexes)if(n?.columns?.length){const e=`CREATE ${n.unique?"UNIQUE":""} INDEX IF NOT EXISTS "${n.name}" ON "${t}" (${n.columns.map(e=>`"${e}"`).join(", ")})`;await this.executeSql(e)}}async alterTable(e,t){if(!t||"object"!=typeof t)throw new Error("alterations must be an object");const n=[];if(t.add?.length)for(const a of t.add)n.push(`ADD COLUMN ${this.getColumnStr(a.name,a,{actionType:"ADD_COLUMN",tableName:e})}`);if(t.drop?.length)for(const e of t.drop)n.push(`DROP COLUMN "${e.name}"`);if(t.modify?.length)for(const e of t.modify){if(e.type){let t=e.type;e.size&&["character varying","varchar","char","character"].includes(e.type)&&(t+=`(${e.size})`),n.push(`ALTER COLUMN "${e.name}" TYPE ${t}`)}if(!1===e.nullable||void 0===e.nullable?n.push(`ALTER COLUMN "${e.name}" SET NOT NULL`):e.nullable,void 0!==e.default&&null!==e.default){let t=this.utils.isInternalDefault(e.default);"string"!=typeof e.default||t?n.push(`ALTER COLUMN "${e.name}" SET DEFAULT ${e.default}`):n.push(`ALTER COLUMN "${e.name}" SET DEFAULT '${e.default}'`)}else n.push(`ALTER COLUMN "${e.name}" DROP DEFAULT`);e.comment}if(!n.length)return void console.log("No alterations to apply for",e);const a=`ALTER TABLE "${e}" ${n.join(", ")}`;if(await this.executeSql(a),t.modify?.length)for(const n of t.modify)if(n.comment){const t=`COMMENT ON COLUMN "${e}"."${n.name}" IS '${this.utils.escapeComment(n.comment)}'`;await this.executeSql(t)}}async alterColumn(e,t,n){const a="string"==typeof n?this.utils.formatColumnSchema(t,n):n;if(a.type){let n=a.type;a.size&&["character varying","varchar","char","character"].includes(a.type)&&(n+=`(${a.size})`);const o=`ALTER TABLE "${e}" ALTER COLUMN "${t}" TYPE ${n}`;await this.executeSql(o)}if(!1===a.nullable||void 0===a.nullable?await this.executeSql(`ALTER TABLE "${e}" ALTER COLUMN "${t}" SET NOT NULL`):!0===a.nullable&&await this.executeSql(`ALTER TABLE "${e}" ALTER COLUMN "${t}" DROP NOT NULL`),void 0!==a.default&&null!==a.default){this.utils.isInternalDefault(a.default)?await this.executeSql(`ALTER TABLE "${e}" ALTER COLUMN "${t}" SET DEFAULT ${a.default}`):await this.executeSql(`ALTER TABLE "${e}" ALTER COLUMN "${t}" SET DEFAULT '${a.default}'`)}else await this.executeSql(`ALTER TABLE "${e}" ALTER COLUMN "${t}" DROP DEFAULT`);if(a.comment){const n=`COMMENT ON COLUMN "${e}"."${t}" IS '${this.utils.escapeComment(a.comment)}'`;await this.executeSql(n)}}async alterIndex(e,t,n){const a=Array.isArray(n.columns)?n.columns:[n.columns],o=`CREATE ${n.unique?"UNIQUE ":""}INDEX "${t}" ON "${e}" (${a.map(e=>`"${e}"`).join(", ")})`;await this.executeSql(o)}async dropIndex(e,t){const n=`DROP INDEX IF EXISTS "${t}"`;await this.executeSql(n)}async dropTable(e){const t=`DROP TABLE IF EXISTS "${e}" CASCADE`;await this.executeSql(t)}async updateTable(e){console.log(`Update table not implemented for ${e.table}`)}async syncTable(e){if(await this.existsTable(e.table)){const t=await this.getAlterations(e);return void await this.alterTable(e.table,t)}await this.createTable(e)}async syncSeedData(e,t){let n=this.utils.getModel(this.controllerWrapper,t),a=await this.curd.processRequest({action:"count"},n.name,{isCallFromServer:!0});if(console.log("count",a),a>0)console.log("Seed data already synced for",t);else if(e.seed&&Array.isArray(e.seed)){for(const t of e.seed)await this.curd.processRequest({action:"create",data:t},n.name,{isCallFromServer:!0});console.log("Seed data synced for",t)}}async syncDatabase(){if(!this.controllerWrapper?.schema)throw new Error("controllerWrapper.schema not set.");const e=this.controllerWrapper.schema;for(const t of Object.keys(e))await this.syncTable(e[t]),await this.syncSeedData(e[t],t);console.log("Database synced by SyncTable...")}async getTablesOfDatabase(){return((await this.db.raw("\n SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = 'public'\n AND table_type = 'BASE TABLE'\n ")).rows||[]).map(e=>e.table_name)}getColumnString(e){return Object.keys(e).reduce((t,n)=>{const a=e[n];let o=a.type,l=a.size,s=a.isUnsigned,i=a.primary,c=a.autoIncrement,r=a.nullable,u=a.unique,m=a.default,h=a.onUpdate,E=a.comment,d=a.hasForeignKey,g=a.foreignMapTables?.[0]?.table,f=a.foreignMapTables?.[0]?.column,T=getSchemaType(o);return t[n]=`${T}`,l&&(t[n]+=`|size:${l}`),s&&(t[n]+="|unsigned"),i&&(t[n]+="|primaryKey"),c&&(t[n]+="|autoIncrement"),r&&(t[n]+="|nullable"),u&&(t[n]+="|unique"),m&&(t[n]+=`|default:${m}`),h&&(t[n]+=`|onUpdate:${h}`),E&&(t[n]+=`|comment:${E}`),d&&(t[n]+=`|foreignKey:${g}:${f}`),t},{})}async getRelations(e){let t=await this.getCurrentColumns(e);const n=Object.keys(t).filter(e=>t[e].hasForeignKey).reduce((e,n)=>{let a=t[n];if(!a)return e;if(0===a.foreignMapTables?.length)return e;let o=a.foreignMapTables;for(const t of o){let a=t.table,o=t.column;e[this.helperUtility.modelName(a)]={type:"one",table:a,localKey:n,foreignKey:o,through:null,throughLocalKey:null,throughForeignKey:null}}return e},{}),a=(this.helperUtility.modelName(e).toLowerCase(),await this.getTablesReferencedByTable(e)),o=a.map(e=>e.TABLE_NAME);for(const e of o){const t=this.helperUtility.modelName(e),o=a.find(t=>t.TABLE_NAME===e)?.REFERENCED_COLUMN_NAME,l=a.find(t=>t.TABLE_NAME===e)?.COLUMN_NAME;n[t]={type:"many",table:e,localKey:o,foreignKey:l,through:null,throughLocalKey:null,throughForeignKey:null}}return n}async generateSchema(){const e=await this.getTablesOfDatabase(),t={};for(const n of e){let e=this.helperUtility.modelName(n),a=await this.getRelations(n);t[e]={table:n,alias:e,columns:this.getColumnString(await this.getCurrentColumns(n)),modelName:e,seed:[],hasRelations:a,indexes:[]}}return t}}module.exports=SyncTable;
1
+ const CurdTable=require("./CurdTable"),HelperUtility=require("./HelperUtility"),{getSchemaType:getSchemaType}=require("./DataTypeMap"),logger=require("../../Logger");class SyncTable{constructor(e,t,n=null){this.db=e,this.utils=t,this.controllerWrapper=n,this.curd=new CurdTable(e,t,n),this.helperUtility=new HelperUtility}_getClientName(){return"pg"}async executeSql(e,t=[]){return this.db.raw(e,t)}async existsTable(e){return this.db.schema.hasTable(e)}async getTablesReferencedByTable(e,t="public"){return(await this.executeSql("SELECT\n tc.table_name AS \"TABLE_NAME\", -- The table that contains the FK (e.g., 'Posts')\n kcu.column_name AS \"REFERENCING_COLUMN_NAME\", -- The FK column (e.g., 'user_id')\n kcu2.column_name AS \"REFERENCED_COLUMN_NAME\" -- The PK/Unique column (e.g., 'id')\n FROM\n information_schema.referential_constraints AS rc\n -- Join to get the table and column being referenced (the PK/Unique key on the target table)\n JOIN\n information_schema.key_column_usage AS kcu2\n ON rc.unique_constraint_name = kcu2.constraint_name\n AND rc.unique_constraint_schema = kcu2.constraint_schema\n -- Join to get the table constraint information for the foreign key\n JOIN\n information_schema.table_constraints AS tc\n ON rc.constraint_name = tc.constraint_name\n AND rc.constraint_schema = tc.table_schema\n -- Join to get the column in the referencing table (the FK column)\n JOIN\n information_schema.key_column_usage AS kcu\n ON rc.constraint_name = kcu.constraint_name\n AND rc.constraint_schema = kcu.table_schema\n -- Link the FK column to its corresponding PK/Unique column\n AND kcu.position_in_unique_constraint = kcu2.ordinal_position\n WHERE\n kcu2.table_name = ?\n AND kcu2.table_schema = ?\n AND tc.constraint_type = 'FOREIGN KEY'\n ORDER BY\n tc.table_name, kcu.column_name",[e,t])).rows||[]}async getTablesWithColumn(e){return(await this.executeSql("\n SELECT table_name\n FROM information_schema.columns\n WHERE column_name = ?\n ",[e])).rows||[]}async getCurrentColumns(e){const t=(await this.executeSql('\n SELECT \n c.column_name AS "COLUMN_NAME",\n c.data_type AS "DATA_TYPE",\n c.udt_name AS "UDT_NAME",\n c.is_nullable AS "IS_NULLABLE",\n c.column_default AS "COLUMN_DEFAULT",\n c.character_maximum_length AS "CHARACTER_MAXIMUM_LENGTH",\n pgd.description AS "COMMENT",\n tc.constraint_type AS "CONSTRAINT_TYPE",\n kcu2.table_name AS "REFERENCED_TABLE_NAME",\n kcu2.column_name AS "REFERENCED_COLUMN_NAME"\n FROM information_schema.columns c\n LEFT JOIN pg_catalog.pg_statio_all_tables as st\n ON c.table_schema = st.schemaname AND c.table_name = st.relname\n LEFT JOIN pg_catalog.pg_description pgd\n ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position\n LEFT JOIN information_schema.key_column_usage kcu\n ON c.table_name = kcu.table_name\n AND c.column_name = kcu.column_name\n AND c.table_schema = kcu.table_schema\n LEFT JOIN information_schema.table_constraints tc\n ON kcu.constraint_name = tc.constraint_name\n AND kcu.table_schema = tc.table_schema\n LEFT JOIN information_schema.referential_constraints rc\n ON tc.constraint_name = rc.constraint_name\n AND tc.table_schema = rc.constraint_schema\n LEFT JOIN information_schema.key_column_usage kcu2\n ON rc.unique_constraint_name = kcu2.constraint_name\n AND rc.unique_constraint_schema = kcu2.constraint_schema\n AND kcu.ordinal_position = kcu2.ordinal_position\n WHERE c.table_schema = ?\n AND c.table_name = ?\n ORDER BY c.ordinal_position\n ',["public",e])).rows||[],n={};for(const e of t)n[e.COLUMN_NAME]=this.utils.formatColumnDef(e.COLUMN_NAME,e);return n}logColumnChanges(e,t,n){logger.debug(`${t.name} changed`),e.isTypeChanged&&logger.debug(`Type changed from ${t.type} to ${n.type}`),e.isSizeChanged&&logger.debug(`Size changed from ${t.size} to ${n.size}`),e.isNullableChanged&&logger.debug(`Nullable changed from ${t.nullable} to ${n.nullable}`),e.isPrimaryChanged&&logger.debug(`Primary changed from ${t.primary} to ${n.primary}`),e.isUniqueChanged&&logger.debug(`Unique changed from ${t.unique} to ${n.unique}`),e.isAutoIncrementChanged&&logger.debug(`Auto increment changed from ${t.autoIncrement} to ${n.autoIncrement}`),e.isDefaultChanged&&logger.debug(`Default changed from ${t.default} to ${n.default}`),e.isOnUpdateChanged&&logger.debug(`On update changed from ${t.onUpdate} to ${n.onUpdate}`),e.isCommentChanged&&logger.debug(`Comment changed from ${t.comment} to ${n.comment}`),e.isForeignKeyChanged&&logger.debug(`ForeignKey changed from ${t.hasForeignKey} to ${n.hasForeignKey}`)}hasColumnChanged(e,t){const n={isNullableChanged:e.nullable!==t.nullable,isTypeChanged:e.type!==t.type,isSizeChanged:e.size!==t.size,isUnsignedChanged:e.isUnsigned!==t.isUnsigned,isPrimaryChanged:e.primary!==t.primary,isUniqueChanged:e.unique!==t.unique,isAutoIncrementChanged:e.autoIncrement!==t.autoIncrement,isDefaultChanged:e.default!==t.default,isOnUpdateChanged:e.onUpdate!==t.onUpdate,isCommentChanged:e.comment!==t.comment,isForeignKeyChanged:e.hasForeignKey!==t.hasForeignKey},a=Object.values(n).some(Boolean);return a&&this.logColumnChanges(n,e,t),a}async getAlterations(e){const t={add:[],drop:[],modify:[]},n=await this.getCurrentColumns(e.table);for(const[a,o]of Object.entries(e.columns)){const e=this.utils.formatColumnSchema(a,o),i=n[a];i?(e.oldColDef=i,this.hasColumnChanged(i,e)&&t.modify.push(e)):t.add.push(e)}for(const a of Object.keys(n))e.columns[a]||t.drop.push({name:a});return t}getColumnStr(e,t,n={actionType:"CREATE",tableName:""}){const{actionType:a,tableName:o}=n,i="string"==typeof t?this.utils.formatColumnSchema(e,t):t;let l=`"${e}"`,s=i.type,c=i.size??this.utils.getDefaultTypeSize(s);if(c&&["character varying","varchar","char","character"].includes(s)?l+=` ${s}(${c})`:l+="numeric"===s&&c?` ${s}(${c})`:` ${s}`,i.autoIncrement&&("integer"===s||"int4"===s?l=`"${e}" SERIAL`:"bigint"!==s&&"int8"!==s||(l=`"${e}" BIGSERIAL`)),i.primary&&["CREATE","ADD_COLUMN"].includes(a)&&(l+=" PRIMARY KEY"),!1===i.nullable||void 0===i.nullable?l+=" NOT NULL":!0===i.nullable&&(l+=" NULL"),i.unique&&(l+=" UNIQUE"),void 0!==i.default&&null!==i.default&&!i.primary&&!i.autoIncrement){l+=this.utils.isInternalDefault(i.default)?` DEFAULT ${i.default}`:` DEFAULT '${i.default}'`}if(i.hasForeignKey&&1===i.foreignMapTables?.length&&"CREATE"===a){const{table:e,column:t}=i.foreignMapTables[0];l+=` REFERENCES "${e}"("${t}")`,l+=" ON DELETE RESTRICT ON UPDATE RESTRICT"}return l}async createTable(e){const t=e.table,n=e.columns,a=[],o=[];for(const[e,i]of Object.entries(n)){const n="string"==typeof i?this.utils.formatColumnSchema(e,i):i;if(a.push(this.getColumnStr(e,n,{actionType:"CREATE",tableName:t})),n.hasForeignKey&&1===n.foreignMapTables?.length){const{table:a,column:i}=n.foreignMapTables[0],l=`fk_${t}_${e}_${a}_${i}`;o.push(`CONSTRAINT "${l}" FOREIGN KEY ("${e}") REFERENCES "${a}"("${i}") ON DELETE RESTRICT ON UPDATE RESTRICT`)}}const i=`CREATE TABLE IF NOT EXISTS "${t}" (${a.concat(o).join(", ")})`;await this.executeSql(i);for(const[e,a]of Object.entries(n)){const n="string"==typeof a?this.utils.formatColumnSchema(e,a):a;if(n.comment){const a=`COMMENT ON COLUMN "${t}"."${e}" IS '${this.utils.escapeComment(n.comment)}'`;await this.executeSql(a)}}for(const n of e.indexes)if(n?.columns?.length){const e=`CREATE ${n.unique?"UNIQUE":""} INDEX IF NOT EXISTS "${n.name}" ON "${t}" (${n.columns.map(e=>`"${e}"`).join(", ")})`;await this.executeSql(e)}}async alterTable(e,t){if(!t||"object"!=typeof t)throw new Error("alterations must be an object");const n=[];if(t.add?.length)for(const a of t.add)n.push(`ADD COLUMN ${this.getColumnStr(a.name,a,{actionType:"ADD_COLUMN",tableName:e})}`);if(t.drop?.length)for(const e of t.drop)n.push(`DROP COLUMN "${e.name}"`);if(t.modify?.length)for(const e of t.modify){if(e.type){let t=e.type;e.size&&["character varying","varchar","char","character"].includes(e.type)&&(t+=`(${e.size})`),n.push(`ALTER COLUMN "${e.name}" TYPE ${t}`)}if(!1===e.nullable||void 0===e.nullable?n.push(`ALTER COLUMN "${e.name}" SET NOT NULL`):e.nullable,void 0!==e.default&&null!==e.default){let t=this.utils.isInternalDefault(e.default);"string"!=typeof e.default||t?n.push(`ALTER COLUMN "${e.name}" SET DEFAULT ${e.default}`):n.push(`ALTER COLUMN "${e.name}" SET DEFAULT '${e.default}'`)}else n.push(`ALTER COLUMN "${e.name}" DROP DEFAULT`);e.comment}if(!n.length)return void logger.info("No alterations to apply for",e);const a=`ALTER TABLE "${e}" ${n.join(", ")}`;if(await this.executeSql(a),t.modify?.length)for(const n of t.modify)if(n.comment){const t=`COMMENT ON COLUMN "${e}"."${n.name}" IS '${this.utils.escapeComment(n.comment)}'`;await this.executeSql(t)}}async alterColumn(e,t,n){const a="string"==typeof n?this.utils.formatColumnSchema(t,n):n;if(a.type){let n=a.type;a.size&&["character varying","varchar","char","character"].includes(a.type)&&(n+=`(${a.size})`);const o=`ALTER TABLE "${e}" ALTER COLUMN "${t}" TYPE ${n}`;await this.executeSql(o)}if(!1===a.nullable||void 0===a.nullable?await this.executeSql(`ALTER TABLE "${e}" ALTER COLUMN "${t}" SET NOT NULL`):!0===a.nullable&&await this.executeSql(`ALTER TABLE "${e}" ALTER COLUMN "${t}" DROP NOT NULL`),void 0!==a.default&&null!==a.default){this.utils.isInternalDefault(a.default)?await this.executeSql(`ALTER TABLE "${e}" ALTER COLUMN "${t}" SET DEFAULT ${a.default}`):await this.executeSql(`ALTER TABLE "${e}" ALTER COLUMN "${t}" SET DEFAULT '${a.default}'`)}else await this.executeSql(`ALTER TABLE "${e}" ALTER COLUMN "${t}" DROP DEFAULT`);if(a.comment){const n=`COMMENT ON COLUMN "${e}"."${t}" IS '${this.utils.escapeComment(a.comment)}'`;await this.executeSql(n)}}async alterIndex(e,t,n){const a=Array.isArray(n.columns)?n.columns:[n.columns],o=`CREATE ${n.unique?"UNIQUE ":""}INDEX "${t}" ON "${e}" (${a.map(e=>`"${e}"`).join(", ")})`;await this.executeSql(o)}async dropIndex(e,t){const n=`DROP INDEX IF EXISTS "${t}"`;await this.executeSql(n)}async dropTable(e){const t=`DROP TABLE IF EXISTS "${e}" CASCADE`;await this.executeSql(t)}async updateTable(e){logger.warn(`Update table not implemented for ${e.table}`)}async syncTable(e){if(await this.existsTable(e.table)){const t=await this.getAlterations(e);return void await this.alterTable(e.table,t)}await this.createTable(e)}async syncSeedData(e,t){let n=this.utils.getModel(this.controllerWrapper,t),a=await this.curd.processRequest({action:"count"},n.name,{isCallFromServer:!0});if(logger.debug("count",a),a>0)logger.info("Seed data already synced for",t);else if(e.seed&&Array.isArray(e.seed)){for(const t of e.seed)await this.curd.processRequest({action:"create",data:t},n.name,{isCallFromServer:!0});logger.info("Seed data synced for",t)}}async syncDatabase(){if(!this.controllerWrapper?.schema)throw new Error("controllerWrapper.schema not set.");const e=this.controllerWrapper.schema;for(const t of Object.keys(e))await this.syncTable(e[t]),await this.syncSeedData(e[t],t);logger.info("Database synced by SyncTable...")}async getTablesOfDatabase(){return((await this.db.raw("\n SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = 'public'\n AND table_type = 'BASE TABLE'\n ")).rows||[]).map(e=>e.table_name)}getColumnString(e){return Object.keys(e).reduce((t,n)=>{const a=e[n];let o=a.type,i=a.size,l=a.isUnsigned,s=a.primary,c=a.autoIncrement,r=a.nullable,u=a.unique,m=a.default,h=a.onUpdate,g=a.comment,d=a.hasForeignKey,E=a.foreignMapTables?.[0]?.table,f=a.foreignMapTables?.[0]?.column,T=getSchemaType(o);return t[n]=`${T}`,i&&(t[n]+=`|size:${i}`),l&&(t[n]+="|unsigned"),s&&(t[n]+="|primaryKey"),c&&(t[n]+="|autoIncrement"),r&&(t[n]+="|nullable"),u&&(t[n]+="|unique"),m&&(t[n]+=`|default:${m}`),h&&(t[n]+=`|onUpdate:${h}`),g&&(t[n]+=`|comment:${g}`),d&&(t[n]+=`|foreignKey:${E}:${f}`),t},{})}async getRelations(e){let t=await this.getCurrentColumns(e);const n=Object.keys(t).filter(e=>t[e].hasForeignKey).reduce((e,n)=>{let a=t[n];if(!a)return e;if(0===a.foreignMapTables?.length)return e;let o=a.foreignMapTables;for(const t of o){let a=t.table,o=t.column;e[this.helperUtility.modelName(a)]={type:"one",table:a,localKey:n,foreignKey:o,through:null,throughLocalKey:null,throughForeignKey:null}}return e},{}),a=(this.helperUtility.modelName(e).toLowerCase(),await this.getTablesReferencedByTable(e)),o=a.map(e=>e.TABLE_NAME);for(const e of o){const t=this.helperUtility.modelName(e),o=a.find(t=>t.TABLE_NAME===e)?.REFERENCED_COLUMN_NAME,i=a.find(t=>t.TABLE_NAME===e)?.COLUMN_NAME;n[t]={type:"many",table:e,localKey:o,foreignKey:i,through:null,throughLocalKey:null,throughForeignKey:null}}return n}async generateSchema(){const e=await this.getTablesOfDatabase(),t={};for(const n of e){let e=this.helperUtility.modelName(n),a=await this.getRelations(n);t[e]={table:n,alias:e,columns:this.getColumnString(await this.getCurrentColumns(n)),modelName:e,seed:[],hasRelations:a,indexes:[]}}return t}}module.exports=SyncTable;
@@ -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={}){let o=this.utils.getModel(this.controllerWrapper,r);const s=e?.action||"list";let c=null;switch(this.hookService?.executeValidatorHook&&await this.hookService.executeValidatorHook(o,s,e,t),this.hookService?.executeBeforeHook&&(e.beforeActionData=await this.hookService.executeBeforeHook(o,s,e,t)),s){case"count":c=await this.queryService.executeCountQuery(o,e);break;case"list":c=await this.hookService.executeHasSoftDeleteHook(o)?await this.queryService.getSoftDeleteQuery(o,e):await this.queryService.getQuery(o,e);break;case"show":c=await this.queryService.executeShowQuery(o,e);break;case"create":c=await this.queryService.executeCreateQuery(o,e);break;case"update":{const r=await this.queryService.executeUpdateQuery(o,e);if(!r)throw new Error(`Record not found or not updated: ${o.table} returned ${r}`);c={message:"Record updated successfully",data:r,success:!0};break}case"replace":if(c=await this.queryService.executeReplaceQuery(o,e),!c)throw new Error(`Record not found or not replaced: ${r} returned ${c}`);c={message:"Record replaced successfully",data:c,success:!0};break;case"upsert":if(c=await this.queryService.executeUpsertQuery(o,e),!c)throw new Error(`Record not found or not upserted: ${r} returned ${c}`);c={message:"Record upserted successfully",data:c,success:!0};break;case"sync":if(c=await this.queryService.executeSyncQuery(o,e),!c)throw new Error(`Record not found or not synced: ${r} returned ${c}`);c={message:"Record synced successfully",data:c,success:!0};break;case"delete":{let t=null;if(t=await this.hookService.executeHasSoftDeleteHook(o)?await this.queryService.executeSoftDeleteQuery(o,e):await this.queryService.executeDeleteQuery(o,e),!t)throw new Error(`Record not found or not deleted: ${r} returned ${t}`);c={message:"Record deleted successfully",data:t,success:!0};break}default:if(!this.hookService?.executeCustomAction)throw new Error(`Unknown action "${s}" and no custom action hook provided.`);c=await this.hookService.executeCustomAction(r,s,e,t)}if(this.hookService?.executeAfterHook&&(c=await this.hookService.executeAfterHook(r,s,c,e,t)),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);c.other_responses=r}return c}}module.exports=CurdTable;
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){const t=e.name,o=this.loadModelClass(t);if(o)return"function"==typeof o?new o:o}resolveModelHook(e,t,o){const s=e.name,r=this.loadModelClass(s);if(!r)return;const i=this.getModelInstance(e);let a;if("validate"===t)a="validate";else if("on"===t)a=`on${o.charAt(0).toUpperCase()+o.slice(1)}`;else if("before"===t)a=`before${o.charAt(0).toUpperCase()+o.slice(1)}`;else if("after"===t)a=`after${o.charAt(0).toUpperCase()+o.slice(1)}`;else{if("custom"!==t)return;a=`custom${o.charAt(0).toUpperCase()+o.slice(1)}`}return"function"==typeof i[a]?i[a].bind(i):"function"==typeof r[a]?r[a].bind(r):void 0}async executeValidatorHook(e,t,o,s){const r=this.resolveModelHook(e,"validate",t);if(r)return await r({model:e,action:t,request:o,context:s,db:this.db,utils:this.utils})}async executeBeforeHook(e,t,o,s){const r=this.resolveModelHook(e,"before",t);if(r)return await r({model:e,action:t,request:o,context:s,db:this.db,utils:this.utils})}async executeAfterHook(e,t,o,s,r){const i=this.resolveModelHook(e,"after",t);return i?await i({model:e,action:t,data:o,request:s,context:r,db:this.db,utils:this.utils}):o}async executeCustomAction(e,t,o,s){const r=this.resolveModelHook(e,"custom",t);if(r)return await r({model:e,action:t,request:o,context:s,db:this.db,utils:this.utils});throw new Error(`No custom action hook found for ${e}.${t}`)}async executeHasSoftDeleteHook(e){const t=this.getModelInstance(e);if(!t)return!1;return t.hasOwnProperty("hasSoftDelete")&&!0===t.hasSoftDelete}}module.exports=HookService;
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){return this.db(e.table)}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:l,relation:o}=e;let a=t.map(e=>e[o.localKey]),s=[];s=await this.fetchRelatedRows(o,a);let n=new Map;for(const e of s){let t=e[o.foreignKey];n.has(t)||n.set(t,[]),n.get(t).push(e)}for(const e of t){let t=e[o.localKey];"one"===o?.type&&1==n.get(t)?.length?e[r]=n.get(t)[0]:e[r]=n.get(t)||[]}let h=Object.keys(l);for(const e of h){let i=l[e],o=t.filter(e=>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:o,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:l,select:o,orderBy:a={column:"id",direction:"asc"},limit:s=10,offset:n=0,page:h,groupBy:p,having:c,distinct:y,join:u,leftJoin:f,rightJoin:d,innerJoin:g,count:w=!1}=t;let W=this.getQueryBuilder(e);o&&(Array.isArray(o)||"string"==typeof o)?W.select(o):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"),d&&this._applyJoins(W,d,"rightJoin"),g&&this._applyJoins(W,g,"innerJoin"),this._applyWhereClause(W,r,i),p&&(Array.isArray(p),W.groupBy(p)),c&&this._applyHavingClause(W,c),a&&this._applyOrderBy(W,a);let b=!1,A=s,_=n,k=1,m=0;h&&s>0&&(k=Math.max(1,parseInt(h)),_=(k-1)*s),s>0&&(W.limit(A),_>0&&W.offset(_));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]})}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&&(m=Math.ceil(B/s),b=k<m);return{data:j,totalCount:B,...s>0?{pagination:{page:k,limit:A,offset:_,totalPages:m,hasNext:b,hasPrev:k>1,nextPage:b?k+1:null,prevPage:k>1?k-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:l}=this.parseWhereColumn(t),{operator:o,value:a}=this.parseWhereValue(i);"AND"===r?this._applyAndWhereCondition(e,l,o,a):this._applyOrWhereCondition(e,l,o,a)}}r&&r.length>0&&this._applyNestedWhere(e,t,r)}}_applyNestedWhere(e,t,r){if(r&&r.length>0)for(const i of r){let l=this.helperUtility.getDotWalkQuery(t,i);if(l&&Object.keys(l).length>0){let t=this.getQueryBuilder(this.utils.getModel(this.controllerWrapper,i)),o=this.helperUtility.map(r,e=>this.helperUtility.pluckDotWalkKey(e,1));e.whereExists(this._applyWhereClause(t,l,o))}}}_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
+ 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:s}=this.parseWhereValue(i);"AND"===t?this._applyAndWhereCondition(e,o,l,s):this._applyOrWhereCondition(e,o,l,s)}}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:s={}}=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 n={rows:t,relName:r,model:i,withTree:o,controller:this.controllerWrapper,relation:i.hasRelations[r],qb:this,db:this.db,withWhere:s};await e[l](n)}return t}let n=t.map(e=>e[l.localKey]);const a="one"===l?.type;let h=[];const p=this._getWithWhereForRelation(s,r);h=await this.fetchRelatedRows(l,n,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];a&&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=>a?e[r]:e[r].length>0).map(e=>e[r]).reduce((e,t)=>e.concat(t),[]),n=this.utils.getModel(this.controllerWrapper,r);await this.fetchAndAttachRelated({parentRows:l,relName:e,model:n,withTree:i,relation:n.hasRelations[e],withWhere:s})}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:s={column:"id",direction:"asc"},limit:n=10,offset:a=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),s&&this._applyOrderBy(w,s);let b=!1,_=n,A=a,k=1,m=0;h&&n>0&&(k=Math.max(1,parseInt(h)),A=(k-1)*n),n>0&&(w.limit(_),A>0&&w.offset(A));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(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"),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}n>0&&null!==C&&(m=Math.ceil(C/n),b=k<m);return{data:j,totalCount:C,...n>0?{pagination:{page:k,limit:_,offset:A,totalPages:m,hasNext:b,hasPrev:k>1,nextPage:b?k+1:null,prevPage:k>1?k-1:null}}:{}}}catch(e){throw logger.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:s}=this.parseWhereValue(i);"AND"===r?this._applyAndWhereCondition(e,o,l,s):this._applyOrWhereCondition(e,o,l,s)}}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 s=this.helperUtility.getDotWalkQuery(t,l);if(s&&Object.keys(s).length>0){let t=this.utils.getModel(this.controllerWrapper,l),n=o.hasRelations[l],a=r.map(e=>this.helperUtility.pluckDotWalkKey(e,1));e.whereExists(function(){let e=i.getQueryBuilder(t,this.select("*").from(t.table));e.whereRaw(`${n.foreignKey} = ${o.table}.${n.localKey}`),i._applyWhereClause(e,s,a)})}}}_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 CurdTable=require("./CurdTable"),HelperUtility=require("./HelperUtility");class SyncTable{constructor(e,t,n=null){this.db=e,this.utils=t,this.controllerWrapper=n,this.curd=new CurdTable(e,t,n),this.helperUtility=new HelperUtility}_getClientName(){return"mysql"}async executeSql(e,t=[]){return this.db.raw(e,t)}async existsTable(e){return this.db.schema.hasTable(e)}async getCurrentColumns(e){const t=this.db.client.database(),[n]=await this.executeSql("\n SELECT \n c.COLUMN_NAME,\n c.DATA_TYPE,\n c.COLUMN_TYPE,\n c.IS_NULLABLE,\n c.COLUMN_KEY,\n c.EXTRA,\n c.CHARACTER_MAXIMUM_LENGTH,\n c.COLUMN_DEFAULT,\n c.COLUMN_COMMENT AS COMMENT,\n kcu.REFERENCED_TABLE_NAME,\n kcu.REFERENCED_COLUMN_NAME\n FROM information_schema.COLUMNS c\n LEFT JOIN information_schema.KEY_COLUMN_USAGE kcu\n ON c.TABLE_SCHEMA = kcu.TABLE_SCHEMA\n AND c.TABLE_NAME = kcu.TABLE_NAME\n AND c.COLUMN_NAME = kcu.COLUMN_NAME\n WHERE c.TABLE_SCHEMA = ?\n AND c.TABLE_NAME = ?\n ",[t,e]),a={};for(const e of n)a[e.COLUMN_NAME]=this.utils.formatColumnDef(e.COLUMN_NAME,e);return a}hasColumnChanged(e,t){const n={isNullableChanged:e.nullable!==t.nullable,isTypeChanged:e.type!==t.type,isSizeChanged:e.size!==t.size,isUnsignedChanged:e.isUnsigned!==t.isUnsigned,isPrimaryChanged:e.primary!==t.primary,isUniqueChanged:e.unique!==t.unique,isAutoIncrementChanged:e.autoIncrement!==t.autoIncrement,isDefaultChanged:e.default!==t.default,isOnUpdateChanged:e.onUpdate!==t.onUpdate,isCommentChanged:e.comment!==t.comment,isForeignKeyChanged:e.hasForeignKey!==t.hasForeignKey},a=Object.values(n).some(Boolean);return a&&console.log({changes:n,oldComment:e.comment,newComment:t.comment,name:e.name}),a}async getAlterations(e){const t={add:[],drop:[],modify:[]},n=await this.getCurrentColumns(e.table);for(const[a,s]of Object.entries(e.columns)){const e=this.utils.formatColumnSchema(a,s),o=n[a];o?(e.oldColDef=o,this.hasColumnChanged(o,e)&&t.modify.push(e)):t.add.push(e)}for(const a of Object.keys(n))e.columns[a]||t.drop.push({name:a});return t}getColumnStr(e,t,n={actionType:"CREATE",tableName:""}){const{actionType:a,tableName:s}=n,o="string"==typeof t?this.utils.formatColumnSchema(e,t):t,i=o.type,l=o.size??this.utils.getDefaultTypeSize(i);let r=`\`${e}\` ${i}${l?`(${l})`:""}`;if(o.isUnsigned&&(r+=" UNSIGNED"),o.primary&&["CREATE","ADD_COLUMN"].includes(a)&&(r+=" PRIMARY KEY"),o.autoIncrement&&(r+=" AUTO_INCREMENT"),o.nullable||(r+=" NOT NULL"),o.unique&&(r+=" UNIQUE"),o.default&&(r+=` DEFAULT ${o.default}`),o.onUpdate&&(r+=` ON UPDATE ${o.onUpdate}`),o.comment&&(r+=` COMMENT '${this.utils.escapeComment(o.comment)}'`),o.hasForeignKey&&1===o.foreignMapTables?.length&&"CREATE"===a){const{table:t,column:n}=o.foreignMapTables[0],a=`idx_${s}__${e}__fk_${t}_${n}`;r+=`, KEY \`${a}\` (\`${e}\`), CONSTRAINT \`cn_${a}\`\n FOREIGN KEY (\`${e}\`) REFERENCES \`${t}\` (\`${n}\`)\n ON DELETE RESTRICT ON UPDATE RESTRICT`}return r}async createTable(e){const t=e.table,n=e.columns,a=[];for(const[e,s]of Object.entries(n))a.push(this.getColumnStr(e,s,{actionType:"CREATE",tableName:t}));const s=`CREATE TABLE IF NOT EXISTS \`${t}\` (${a.join(", ")})`;await this.executeSql(s)}async alterTable(e,t){if(!t||"object"!=typeof t)throw new Error("alterations must be an object");const n=[];if(t.add?.length)for(const a of t.add)n.push(`ADD COLUMN ${this.getColumnStr(a.name,a,{actionType:"ADD_COLUMN",tableName:e})}`);if(t.drop?.length)for(const e of t.drop)n.push(`DROP COLUMN \`${e.name}\``);if(t.modify?.length)for(const a of t.modify)n.push(`MODIFY COLUMN ${this.getColumnStr(a.name,a,{actionType:"MODIFY_COLUMN",tableName:e})}`);if(!n.length)return void console.log("No alterations to apply for",e);const a=`ALTER TABLE \`${e}\` ${n.join(", ")}`;await this.executeSql(a)}async alterColumn(e,t,n){const a=`ALTER TABLE \`${e}\` MODIFY COLUMN ${this.getColumnStr(t,n,{actionType:"MODIFY_COLUMN",tableName:e})}`;await this.executeSql(a)}async alterIndex(e,t,n){const a=`ALTER TABLE \`${e}\` MODIFY INDEX ${`${t} ${n.type} ${n.unique?"UNIQUE":""}`}`;await this.executeSql(a)}async dropIndex(e,t){const n=`DROP INDEX \`${t}\` ON \`${e}\``;await this.executeSql(n)}async dropTable(e){const t=`DROP TABLE IF EXISTS \`${e}\``;await this.executeSql(t)}async updateTable(e){console.log(`Update table not implemented for ${e.table}`)}async syncTable(e){if(await this.existsTable(e.table)){const t=await this.getAlterations(e);return console.log(...Object.values(t)),void await this.alterTable(e.table,t)}await this.createTable(e)}async syncSeedData(e,t){let n=this.utils.getModel(this.controllerWrapper,t),a=await this.curd.processRequest({action:"count"},n.name,{isCallFromServer:!0});if(console.log("count",a),a>0)console.log("Seed data already synced for",t);else if(e.seed&&Array.isArray(e.seed)){for(const t of e.seed)await this.curd.processRequest({action:"create",data:t},n.name,{isCallFromServer:!0});console.log("Seed data synced for",t)}}async syncDatabase(){if(!this.controllerWrapper?.schema)throw new Error("controllerWrapper.schema not set.");const e=this.controllerWrapper.schema;for(const t of Object.keys(e))await this.syncTable(e[t]),await this.syncSeedData(e[t],t);console.log("Database synced by SyncTable...")}async getTablesOfDatabase(){const[e]=await this.db.raw("SHOW TABLES");return e.map(e=>e[`Tables_in_${this.db.client.database()}`])}getColumnString(e){return Object.keys(e).reduce((t,n)=>{const a=e[n];let s=a.type,o=a.size,i=a.isUnsigned,l=a.primary,r=a.autoIncrement,c=a.nullable,u=a.unique,m=a.default,d=a.onUpdate,E=a.comment,h=a.hasForeignKey,C=a.foreignMapTables?.[0]?.table,T=a.foreignMapTables?.[0]?.column;return t[n]=`${s}`,o&&(t[n]+=`|size:${o}`),i&&(t[n]+="|unsigned"),l&&(t[n]+="|primaryKey"),r&&(t[n]+="|autoIncrement"),c&&(t[n]+="|nullable"),u&&(t[n]+="|unique"),m&&(t[n]+=`|default:${m}`),d&&(t[n]+=`|onUpdate:${d}`),E&&(t[n]+=`|comment:${E}`),h&&(t[n]+=`|foreignKey:${C}:${T}`),t},{})}async generateSchema(){const e=await this.getTablesOfDatabase(),t={};for(const n of e){let e=this.helperUtility.modelName(n);t[e]={table:n,alias:e,columns:this.getColumnString(await this.getCurrentColumns(n)),modelName:e,seed:[],hasRelations:{},indexes:[]}}return t}}module.exports=SyncTable;
1
+ const CurdTable=require("./CurdTable"),HelperUtility=require("./HelperUtility"),logger=require("../../Logger");class SyncTable{constructor(e,t,n=null){this.db=e,this.utils=t,this.controllerWrapper=n,this.curd=new CurdTable(e,t,n),this.helperUtility=new HelperUtility}_getClientName(){return"mysql"}async executeSql(e,t=[]){return this.db.raw(e,t)}async existsTable(e){return this.db.schema.hasTable(e)}async getCurrentColumns(e){const t=this.db.client.database(),[n]=await this.executeSql("\n SELECT \n c.COLUMN_NAME,\n c.DATA_TYPE,\n c.COLUMN_TYPE,\n c.IS_NULLABLE,\n c.COLUMN_KEY,\n c.EXTRA,\n c.CHARACTER_MAXIMUM_LENGTH,\n c.COLUMN_DEFAULT,\n c.COLUMN_COMMENT AS COMMENT,\n kcu.REFERENCED_TABLE_NAME,\n kcu.REFERENCED_COLUMN_NAME\n FROM information_schema.COLUMNS c\n LEFT JOIN information_schema.KEY_COLUMN_USAGE kcu\n ON c.TABLE_SCHEMA = kcu.TABLE_SCHEMA\n AND c.TABLE_NAME = kcu.TABLE_NAME\n AND c.COLUMN_NAME = kcu.COLUMN_NAME\n WHERE c.TABLE_SCHEMA = ?\n AND c.TABLE_NAME = ?\n ",[t,e]),a={};for(const e of n)a[e.COLUMN_NAME]=this.utils.formatColumnDef(e.COLUMN_NAME,e);return a}hasColumnChanged(e,t){const n={isNullableChanged:e.nullable!==t.nullable,isTypeChanged:e.type!==t.type,isSizeChanged:e.size!==t.size,isUnsignedChanged:e.isUnsigned!==t.isUnsigned,isPrimaryChanged:e.primary!==t.primary,isUniqueChanged:e.unique!==t.unique,isAutoIncrementChanged:e.autoIncrement!==t.autoIncrement,isDefaultChanged:e.default!==t.default,isOnUpdateChanged:e.onUpdate!==t.onUpdate,isCommentChanged:e.comment!==t.comment,isForeignKeyChanged:e.hasForeignKey!==t.hasForeignKey},a=Object.values(n).some(Boolean);return a&&logger.debug({changes:n,oldComment:e.comment,newComment:t.comment,name:e.name}),a}async getAlterations(e){const t={add:[],drop:[],modify:[]},n=await this.getCurrentColumns(e.table);for(const[a,s]of Object.entries(e.columns)){const e=this.utils.formatColumnSchema(a,s),o=n[a];o?(e.oldColDef=o,this.hasColumnChanged(o,e)&&t.modify.push(e)):t.add.push(e)}for(const a of Object.keys(n))e.columns[a]||t.drop.push({name:a});return t}getColumnStr(e,t,n={actionType:"CREATE",tableName:""}){const{actionType:a,tableName:s}=n,o="string"==typeof t?this.utils.formatColumnSchema(e,t):t,i=o.type,r=o.size??this.utils.getDefaultTypeSize(i);let l=`\`${e}\` ${i}${r?`(${r})`:""}`;if(o.isUnsigned&&(l+=" UNSIGNED"),o.primary&&["CREATE","ADD_COLUMN"].includes(a)&&(l+=" PRIMARY KEY"),o.autoIncrement&&(l+=" AUTO_INCREMENT"),o.nullable||(l+=" NOT NULL"),o.unique&&(l+=" UNIQUE"),o.default&&(l+=` DEFAULT ${o.default}`),o.onUpdate&&(l+=` ON UPDATE ${o.onUpdate}`),o.comment&&(l+=` COMMENT '${this.utils.escapeComment(o.comment)}'`),o.hasForeignKey&&1===o.foreignMapTables?.length&&"CREATE"===a){const{table:t,column:n}=o.foreignMapTables[0],a=`idx_${s}__${e}__fk_${t}_${n}`;l+=`, KEY \`${a}\` (\`${e}\`), CONSTRAINT \`cn_${a}\`\n FOREIGN KEY (\`${e}\`) REFERENCES \`${t}\` (\`${n}\`)\n ON DELETE RESTRICT ON UPDATE RESTRICT`}return l}async createTable(e){const t=e.table,n=e.columns,a=[];for(const[e,s]of Object.entries(n))a.push(this.getColumnStr(e,s,{actionType:"CREATE",tableName:t}));const s=`CREATE TABLE IF NOT EXISTS \`${t}\` (${a.join(", ")})`;await this.executeSql(s)}async alterTable(e,t){if(!t||"object"!=typeof t)throw new Error("alterations must be an object");const n=[];if(t.add?.length)for(const a of t.add)n.push(`ADD COLUMN ${this.getColumnStr(a.name,a,{actionType:"ADD_COLUMN",tableName:e})}`);if(t.drop?.length)for(const e of t.drop)n.push(`DROP COLUMN \`${e.name}\``);if(t.modify?.length)for(const a of t.modify)n.push(`MODIFY COLUMN ${this.getColumnStr(a.name,a,{actionType:"MODIFY_COLUMN",tableName:e})}`);if(!n.length)return void logger.info("No alterations to apply for",e);const a=`ALTER TABLE \`${e}\` ${n.join(", ")}`;await this.executeSql(a)}async alterColumn(e,t,n){const a=`ALTER TABLE \`${e}\` MODIFY COLUMN ${this.getColumnStr(t,n,{actionType:"MODIFY_COLUMN",tableName:e})}`;await this.executeSql(a)}async alterIndex(e,t,n){const a=`ALTER TABLE \`${e}\` MODIFY INDEX ${`${t} ${n.type} ${n.unique?"UNIQUE":""}`}`;await this.executeSql(a)}async dropIndex(e,t){const n=`DROP INDEX \`${t}\` ON \`${e}\``;await this.executeSql(n)}async dropTable(e){const t=`DROP TABLE IF EXISTS \`${e}\``;await this.executeSql(t)}async updateTable(e){logger.warn(`Update table not implemented for ${e.table}`)}async syncTable(e){if(await this.existsTable(e.table)){const t=await this.getAlterations(e);return logger.debug(...Object.values(t)),void await this.alterTable(e.table,t)}await this.createTable(e)}async syncSeedData(e,t){let n=this.utils.getModel(this.controllerWrapper,t),a=await this.curd.processRequest({action:"count"},n.name,{isCallFromServer:!0});if(logger.debug("count",a),a>0)logger.info("Seed data already synced for",t);else if(e.seed&&Array.isArray(e.seed)){for(const t of e.seed)await this.curd.processRequest({action:"create",data:t},n.name,{isCallFromServer:!0});logger.info("Seed data synced for",t)}}async syncDatabase(){if(!this.controllerWrapper?.schema)throw new Error("controllerWrapper.schema not set.");const e=this.controllerWrapper.schema;for(const t of Object.keys(e))await this.syncTable(e[t]),await this.syncSeedData(e[t],t);logger.info("Database synced by SyncTable...")}async getTablesOfDatabase(){const[e]=await this.db.raw("SHOW TABLES");return e.map(e=>e[`Tables_in_${this.db.client.database()}`])}getColumnString(e){return Object.keys(e).reduce((t,n)=>{const a=e[n];let s=a.type,o=a.size,i=a.isUnsigned,r=a.primary,l=a.autoIncrement,c=a.nullable,u=a.unique,m=a.default,d=a.onUpdate,E=a.comment,h=a.hasForeignKey,C=a.foreignMapTables?.[0]?.table,T=a.foreignMapTables?.[0]?.column;return t[n]=`${s}`,o&&(t[n]+=`|size:${o}`),i&&(t[n]+="|unsigned"),r&&(t[n]+="|primaryKey"),l&&(t[n]+="|autoIncrement"),c&&(t[n]+="|nullable"),u&&(t[n]+="|unique"),m&&(t[n]+=`|default:${m}`),d&&(t[n]+=`|onUpdate:${d}`),E&&(t[n]+=`|comment:${E}`),h&&(t[n]+=`|foreignKey:${C}:${T}`),t},{})}async generateSchema(){const e=await this.getTablesOfDatabase(),t={};for(const n of e){let e=this.helperUtility.modelName(n);t[e]={table:n,alias:e,columns:this.getColumnString(await this.getCurrentColumns(n)),modelName:e,seed:[],hasRelations:{},indexes:[]}}return t}}module.exports=SyncTable;
package/helpers/files.js CHANGED
@@ -1 +1 @@
1
- const fs=require("fs"),path=require("path"),{S3Client:S3Client,PutObjectCommand:PutObjectCommand}=require("@aws-sdk/client-s3");class Files{s3Client=null;config=null;validateConfig(e){let t=[];if(e.region||t.push("Region as config.region"),e.endpoint||e.host||t.push("Endpoint as config.endpoint or Host as config.host"),e.accessKeyId||t.push("Access key ID as config.accessKeyId"),e.secretAccessKey||t.push("Secret access key as config.secretAccessKey"),e.bucketName||t.push("Bucket name as config.bucketName"),t.length>0)throw new Error(`Missing config: ${t.join(", ")}. Please set the config in the config file.`)}setConfig(e){this.validateConfig(e),this.config=e;let t={region:e.region,endpoint:e.endpoint||`https://${e.region}.${e.host}`,credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}};this.s3Client=new S3Client(t)}async upload2Spaces(e){const{fileBuffer:t,originalName:i,mimeType:s}=e;console.log("Uploading file to Spaces",{fileBuffer:t,originalName:i,mimeType:s},e);const n=i.split(".").pop(),r=this.config.bucketName,a=`${this.config.folderName||"uploads"}/${`${Date.now()}-${Math.random().toString(36).substring(2,9)}`}-${i}.${n}`,c={Bucket:r,Key:a,Body:t,ContentType:s,ACL:"public-read",CacheControl:"max-age=31536000"};try{const e=new PutObjectCommand(c);await this.s3Client.send(e);const t=`https://${r}.${this.config.region}.cdn.digitaloceanspaces.com/${a}`;return console.log(`File uploaded to Spaces key: ${a}`),{key:a,url:t}}catch(e){throw console.error("DigitalOcean Spaces Upload Error:",e),new Error(`Spaces upload failed: ${e.message}`)}}needSync(e,t){let i=this.readJSON(e),s=this.checkFileUpdatedAt(e),n=this.readFile(t);return n&&(n=new Date(+n)),{needSync:n&&n<s||!n,syncSchema:i,lastSyncedAt:s.getTime().toString()}}writeFromTemplate(e,t,i){const s=__dirname,n=fs.readFileSync(`${s}/${t}`,"utf8").replace(/{{(.*?)}}/g,(e,t)=>i[t]||e);this.createDirectory(path.dirname(e)),fs.writeFileSync(e,n)}writeModel(e,t){this.exists(e)?console.log("File already exists",e):(console.log("Writing model to",e),this.writeFromTemplate(e,"../templates/model.template",t))}checkFileUpdatedAt(e){return this.exists(e)?fs.statSync(e).mtime:null}isLatestFile(e,t=1e3){let i=Date.now(),s=this.checkFileUpdatedAt(e);return!!s&&i-new Date(s).getTime()<t}createDirectory(e){this.exists(e)||fs.mkdirSync(e,{recursive:!0})}deleteDirectory(e){this.exists(e)&&fs.rmSync(e,{recursive:!0,force:!0})}exists(e){return fs.existsSync(e)}rename(e,t){this.exists(e)&&fs.renameSync(e,t)}deleteFile(e){this.exists(e)&&fs.unlinkSync(e)}readFile(e,t="utf8"){return this.exists(e)?fs.readFileSync(e,t):null}writeFile(e,t,i="utf8"){fs.writeFileSync(e,t,i)}appendFile(e,t,i="utf8"){fs.appendFileSync(e,t,i)}readJSON(e){return JSON.parse(this.readFile(e))}writeJSON(e,t){this.writeFile(e,JSON.stringify(t,null,2))}readJSONL(e){return this.readFile(e).trim().split("\n").map(e=>JSON.parse(e))}writeJSONL(e,t){const i=t.map(e=>JSON.stringify(e)).join("\n");this.writeFile(e,i)}appendJSONL(e,t){const i=t.map(e=>JSON.stringify(e)).join("\n");this.appendFile(e,"\n"+i)}escapeCSV(e){if(null==e)return"";const t=String(e);return t.includes('"')||t.includes(",")||t.includes("\n")?`"${t.replace(/"/g,'""')}"`:t}writeCSV(e,t){if(!Array.isArray(t)||0===t.length)throw new Error("Data must be a non-empty array");const i=Object.keys(t[0]),s=[i.join(","),...t.map(e=>i.map(t=>this.escapeCSV(e[t])).join(","))];this.writeFile(e,s.join("\n"))}readCSV(e){return this.readFile(e).trim().split("\n").map(e=>e.split(","))}appendCSV(e,t){if(!Array.isArray(t)||0===t.length)return;const[i]=this.readFile(e).split("\n"),s=i.split(","),n=t.map(e=>s.map(t=>this.escapeCSV(e[t])).join(","));this.appendFile(e,"\n"+n.join("\n"))}}module.exports=Files;
1
+ const fs=require("fs"),path=require("path"),{S3Client:S3Client,PutObjectCommand:PutObjectCommand}=require("@aws-sdk/client-s3"),logger=require("../Logger");class Files{s3Client=null;config=null;validateConfig(e){let t=[];if(e.region||t.push("Region as config.region"),e.endpoint||e.host||t.push("Endpoint as config.endpoint or Host as config.host"),e.accessKeyId||t.push("Access key ID as config.accessKeyId"),e.secretAccessKey||t.push("Secret access key as config.secretAccessKey"),e.bucketName||t.push("Bucket name as config.bucketName"),t.length>0)throw new Error(`Missing config: ${t.join(", ")}. Please set the config in the config file.`)}setConfig(e){this.validateConfig(e),this.config=e;let t={region:e.region,endpoint:e.endpoint||`https://${e.region}.${e.host}`,credentials:{accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey}};this.s3Client=new S3Client(t)}async upload2Spaces(e){const{fileBuffer:t,originalName:i,mimeType:s}=e;logger.debug("Uploading file to Spaces",{fileBuffer:t,originalName:i,mimeType:s},e);const n=i.split(".").pop(),r=this.config.bucketName,a=`${this.config.folderName||"uploads"}/${`${Date.now()}-${Math.random().toString(36).substring(2,9)}`}-${i}.${n}`,c={Bucket:r,Key:a,Body:t,ContentType:s,ACL:"public-read",CacheControl:"max-age=31536000"};try{const e=new PutObjectCommand(c);await this.s3Client.send(e);const t=`https://${r}.${this.config.region}.cdn.digitaloceanspaces.com/${a}`;return logger.info(`File uploaded to Spaces key: ${a}`),{key:a,url:t}}catch(e){throw logger.error("DigitalOcean Spaces Upload Error:",e),new Error(`Spaces upload failed: ${e.message}`)}}needSync(e,t){let i=this.readJSON(e),s=this.checkFileUpdatedAt(e),n=this.readFile(t);return n&&(n=new Date(+n)),{needSync:n&&n<s||!n,syncSchema:i,lastSyncedAt:s.getTime().toString()}}writeFromTemplate(e,t,i){const s=__dirname,n=fs.readFileSync(`${s}/${t}`,"utf8").replace(/{{(.*?)}}/g,(e,t)=>i[t]||e);this.createDirectory(path.dirname(e)),fs.writeFileSync(e,n)}writeModel(e,t){this.exists(e)?logger.debug("File already exists",e):(logger.debug("Writing model to",e),this.writeFromTemplate(e,"../templates/model.template",t))}checkFileUpdatedAt(e){return this.exists(e)?fs.statSync(e).mtime:null}isLatestFile(e,t=1e3){let i=Date.now(),s=this.checkFileUpdatedAt(e);return!!s&&i-new Date(s).getTime()<t}createDirectory(e){this.exists(e)||fs.mkdirSync(e,{recursive:!0})}deleteDirectory(e){this.exists(e)&&fs.rmSync(e,{recursive:!0,force:!0})}exists(e){return fs.existsSync(e)}rename(e,t){this.exists(e)&&fs.renameSync(e,t)}deleteFile(e){this.exists(e)&&fs.unlinkSync(e)}readFile(e,t="utf8"){return this.exists(e)?fs.readFileSync(e,t):null}writeFile(e,t,i="utf8"){fs.writeFileSync(e,t,i)}appendFile(e,t,i="utf8"){fs.appendFileSync(e,t,i)}readJSON(e){return JSON.parse(this.readFile(e))}writeJSON(e,t){this.writeFile(e,JSON.stringify(t,null,2))}readJSONL(e){return this.readFile(e).trim().split("\n").map(e=>JSON.parse(e))}writeJSONL(e,t){const i=t.map(e=>JSON.stringify(e)).join("\n");this.writeFile(e,i)}appendJSONL(e,t){const i=t.map(e=>JSON.stringify(e)).join("\n");this.appendFile(e,"\n"+i)}escapeCSV(e){if(null==e)return"";const t=String(e);return t.includes('"')||t.includes(",")||t.includes("\n")?`"${t.replace(/"/g,'""')}"`:t}writeCSV(e,t){if(!Array.isArray(t)||0===t.length)throw new Error("Data must be a non-empty array");const i=Object.keys(t[0]),s=[i.join(","),...t.map(e=>i.map(t=>this.escapeCSV(e[t])).join(","))];this.writeFile(e,s.join("\n"))}readCSV(e){return this.readFile(e).trim().split("\n").map(e=>e.split(","))}appendCSV(e,t){if(!Array.isArray(t)||0===t.length)return;const[i]=this.readFile(e).split("\n"),s=i.split(","),n=t.map(e=>s.map(t=>this.escapeCSV(e[t])).join(","));this.appendFile(e,"\n"+n.join("\n"))}}module.exports=Files;
package/index.js CHANGED
@@ -1 +1 @@
1
- const ControllerWrapper=require("./ControllerWrapper"),BaseHelperUtility=require("./BaseHelperUtility"),RequestValidator=require("./RequestValidator"),Emitter=require("./Emitter");module.exports={LibClasses:{Emitter:Emitter},initializeKORM(){return ControllerWrapper.initializeKORM(...arguments)},helperUtility:new BaseHelperUtility,emitter:new Emitter,validate:RequestValidator.validate,lib:{createValidationMiddleware:RequestValidator.createValidationMiddleware,validateEmail:RequestValidator.validateEmail,validatePassword:RequestValidator.validatePassword,validatePhone:RequestValidator.validatePhone,validatePAN:RequestValidator.validatePAN,validateAadhaar:RequestValidator.validateAadhaar}};
1
+ const ControllerWrapper=require("./ControllerWrapper"),BaseHelperUtility=require("./BaseHelperUtility"),RequestValidator=require("./RequestValidator"),Emitter=require("./Emitter"),logger=require("./Logger");module.exports={LibClasses:{Emitter:Emitter},initializeKORM(){return ControllerWrapper.initializeKORM(...arguments)},helperUtility:new BaseHelperUtility,emitter:new Emitter,logger:logger,validate:RequestValidator.validate,lib:{createValidationMiddleware:RequestValidator.createValidationMiddleware,validateEmail:RequestValidator.validateEmail,validatePassword:RequestValidator.validatePassword,validatePhone:RequestValidator.validatePhone,validatePAN:RequestValidator.validatePAN,validateAadhaar:RequestValidator.validateAadhaar}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamtree-org/korm-js",
3
- "version": "1.0.46",
3
+ "version": "1.0.48",
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",