@dreamtree-org/korm-js 1.0.53 → 1.0.54
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/BaseHelperUtility.js +1 -1
- package/ControllerWrapper.js +1 -1
- package/Emitter.js +1 -1
- package/README.md +263 -226
- package/build.js +1 -1
- package/cli.js +2 -0
- package/clients/mysql/BaseUtility.js +1 -1
- package/clients/mysql/CurdTable.js +1 -1
- package/clients/mysql/DataTypeMap.js +1 -1
- package/clients/mysql/HookService.js +1 -1
- package/clients/mysql/QueryBuilder.js +1 -1
- package/clients/mysql/QueryService.js +1 -1
- package/clients/mysql/SyncTable.js +1 -1
- package/clients/pg/BaseUtility.js +1 -1
- package/clients/pg/CurdTable.js +1 -1
- package/clients/pg/HookService.js +1 -1
- package/clients/pg/QueryBuilder.js +1 -1
- package/clients/pg/QueryService.js +1 -1
- package/clients/pg/SyncTable.js +1 -1
- package/clients/sqlite/BaseUtility.js +1 -1
- package/clients/sqlite/CurdTable.js +1 -1
- package/clients/sqlite/HookService.js +1 -1
- package/clients/sqlite/QueryBuilder.js +1 -1
- package/clients/sqlite/QueryService.js +1 -1
- package/clients/sqlite/SyncTable.js +1 -1
- package/helpers/files.js +1 -1
- package/jest.config.js +1 -1
- package/package.json +9 -4
package/build.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const fs=require("fs"),path=require("path"),{execSync:execSync}=require("child_process");function progressBar(e,s,i=30){const o=e/s,
|
|
2
|
+
const fs=require("fs"),path=require("path"),{execSync:execSync}=require("child_process");function progressBar(e,s,i=30){const o=e/s,n=Math.round(i*o),t=i-n,c="█".repeat(n)+"-".repeat(t);process.stdout.write(`\r[${c}] ${(100*o).toFixed(1)}% (${e}/${s})`),e===s&&process.stdout.write("\n")}function getAllJsFiles(e,s=["node_modules","dist","test"]){let i=[];return fs.readdirSync(e).forEach(o=>{const n=path.join(e,o),t=fs.statSync(n);t&&t.isDirectory()?s.includes(o)||(i=i.concat(getAllJsFiles(n,s))):o.endsWith(".js")&&i.push(n)}),i}function ensureDir(e){fs.existsSync(e)||(fs.mkdirSync(e,{recursive:!0}),console.log(`✅ Created directory: ${e}`))}function minifyFile(e,s){try{return execSync(`npx terser "${e}" -o "${s}" --compress --mangle --comments false`,{stdio:"pipe"}),!0}catch(i){return fs.copyFileSync(e,s),!1}}function copyFile(e,s){fs.copyFileSync(e,s)}function copyDirectory(e,s){if(!fs.existsSync(e))return;ensureDir(s);fs.readdirSync(e,{withFileTypes:!0}).forEach(i=>{const o=path.join(e,i.name),n=path.join(s,i.name);i.isDirectory()?copyDirectory(o,n):copyFile(o,n)})}async function build(){console.log("🚀 KORM Build: Minifying all JS files to dist/ with progress bar\n"),fs.existsSync("dist")&&(fs.rmSync("dist",{recursive:!0,force:!0}),console.log("✅ Cleaned dist/")),ensureDir("dist");const e=getAllJsFiles(".",["node_modules","dist","test"]),s=e.length;let i=0,o=0,n=0;e.forEach((e,t)=>{const c=path.relative(".",e),r=path.join("dist",c);ensureDir(path.dirname(r));const l=fs.statSync(e).size,a=(minifyFile(e,r),fs.statSync(r).size);o+=l,n+=a,i++,progressBar(i,s)}),["README.md","LICENSE"].forEach(e=>{fs.existsSync(e)&&(copyFile(e,path.join("dist",e)),console.log(`✅ Copied: ${e}`))}),fs.existsSync("templates")&&(copyDirectory("templates",path.join("dist","templates")),console.log("✅ Copied templates/ to dist/")),fs.existsSync("node_modules")&&(execSync("cp -r node_modules dist/",{stdio:"pipe"}),console.log("✅ Copied node_modules/ to dist/"));const t=path.join(".","version-manager.js");let c=!1,r=null;if(!("1"===process.env.BUILD_SKIP_VERSION_BUMP)&&fs.existsSync(t))try{const e=new(require("./version-manager"));"function"==typeof e.smartAutoIncrement&&(r=await e.smartAutoIncrement(),c=!0,console.log(`✅ Version updated using version-manager.js: ${r}`))}catch(e){console.warn("⚠️ Could not update version using version-manager.js:",e.message)}if(fs.existsSync("package.json")){const e=JSON.parse(fs.readFileSync("package.json","utf8"));e.scripts&&(delete e.scripts.build,delete e.scripts.clean,delete e.scripts.minify,delete e.scripts["minify:js"],delete e.scripts.prepublishOnly),delete e.devDependencies,e.main="index.js",delete e.files,c&&r&&("string"==typeof r?(e.version=r,console.log(`✅ Set version in dist/package.json: ${r}`)):(console.warn(`⚠️ newVersion is not a string: ${typeof r} - ${JSON.stringify(r)}`),e.version=e.version||"1.0.0")),fs.writeFileSync(path.join("dist","package.json"),JSON.stringify(e,null,2)),console.log("✅ Created dist/package.json")}const l=((o-n)/o*100).toFixed(1);console.log("\n📊 Build Statistics:"),console.log(` JS files processed: ${s}`),console.log(` Original size: ${(o/1024).toFixed(1)} KB`),console.log(` Minified size: ${(n/1024).toFixed(1)} KB`),console.log(` Size reduction: ${l}%`),c&&r&&console.log(` New version: ${r}`),console.log("\n🎉 Build completed! Output in dist/")}build().catch(console.error);
|
package/cli.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";const fs=require("fs"),path=require("path"),BEGIN="\x3c!-- BEGIN korm-js skill (auto-generated — re-run `npx @dreamtree-org/korm-js init --ai <provider>` to refresh) --\x3e",END="\x3c!-- END korm-js skill --\x3e",PROVIDERS={claude:{path:"CLAUDE.md",mode:"block"},openai:{path:"AGENTS.md",mode:"block"},gemini:{path:"GEMINI.md",mode:"block"},copilot:{path:".github/copilot-instructions.md",mode:"block"},kiro:{path:".kiro/steering/korm-js.md",mode:"file"},windsurf:{path:".windsurf/rules/korm-js.md",mode:"file"},cursor:{path:".cursor/rules/korm-js.mdc",mode:"file",frontmatter:"---\ndescription: KORM-JS request contract reference for @dreamtree-org/korm-js\nalwaysApply: false\n---\n\n"}},PROVIDER_ALIASES={"claude-code":"claude",codex:"openai","github-copilot":"copilot"};function parseArgs(e){const r={_:[],flags:{}};for(let o=0;o<e.length;o++){const n=e[o];if(n.startsWith("--")){const s=n.slice(2),t=e[o+1];t&&!t.startsWith("--")?(r.flags[s]=t,o++):r.flags[s]=!0}else r._.push(n)}return r}function usage(){return["Usage:"," npx @dreamtree-org/korm-js init --ai <provider> [--cwd <dir>] [--force] [--dry-run]","","Providers:"," claude -> CLAUDE.md (block insert)"," openai -> AGENTS.md (block insert)"," gemini -> GEMINI.md (block insert)"," copilot -> .github/copilot-instructions.md (block insert)"," kiro -> .kiro/steering/korm-js.md"," windsurf -> .windsurf/rules/korm-js.md"," cursor -> .cursor/rules/korm-js.mdc","","Use --ai all to install every provider in one go."].join("\n")}function readSkillBody(){const e=path.join(__dirname,"ai-skills","korm-js.md");if(!fs.existsSync(e))throw new Error("Skill source missing at "+e+" — reinstall @dreamtree-org/korm-js.");return fs.readFileSync(e,"utf8")}function ensureDir(e){const r=path.dirname(e);r&&"."!==r&&!fs.existsSync(r)&&fs.mkdirSync(r,{recursive:!0})}function writeBlock(e,r,o){const n=BEGIN+"\n\n"+r.trim()+"\n\n"+END+"\n";let s,t;if(fs.existsSync(e)){const r=fs.readFileSync(e,"utf8"),o=r.indexOf(BEGIN),i=r.indexOf(END);if(-1!==o&&-1!==i&&i>o){const e=r.slice(0,o).replace(/\s+$/,""),l=r.slice(i+26).replace(/^\s+/,"");s=(e?e+"\n\n":"")+n+(l?"\n"+l:""),t="updated"}else{s=r.replace(/\s+$/,"")+"\n\n"+n,t="appended"}}else s=n,t="created";return o.dryRun?{action:t+" (dry-run)",path:e}:(ensureDir(e),fs.writeFileSync(e,s,"utf8"),{action:t,path:e})}function writeFile(e,r,o,n){const s=(o||"")+r;let t;if(fs.existsSync(e)){if(!n.force)return{action:"skipped (exists; pass --force to overwrite)",path:e};t="overwritten"}else t="created";return n.dryRun?{action:t+" (dry-run)",path:e}:(ensureDir(e),fs.writeFileSync(e,s,"utf8"),{action:t,path:e})}function installFor(e,r,o,n){const s=PROVIDERS[e],t=path.join(o,s.path);return"block"===s.mode?writeBlock(t,r,n):writeFile(t,r,s.frontmatter,n)}function resolveProvider(e){if(!e)return null;const r=String(e).toLowerCase();return"all"===r?"all":PROVIDERS[r]?r:PROVIDER_ALIASES[r]?PROVIDER_ALIASES[r]:null}function runInit(e){const r=e.flags.ai;r&&!0!==r||(console.error("Missing --ai <provider>.\n"),console.error(usage()),process.exit(1));const o=resolveProvider(r);o||(console.error("Unknown provider: "+r),console.error("Known: "+Object.keys(PROVIDERS).join(", ")+", all"),process.exit(1));const n=e.flags.cwd?path.resolve(String(e.flags.cwd)):process.cwd(),s={force:Boolean(e.flags.force),dryRun:Boolean(e.flags["dry-run"])},t=readSkillBody(),i=("all"===o?Object.keys(PROVIDERS):[o]).map(function(e){try{const r=installFor(e,t,n,s);return{provider:e,ok:!0,action:r.action,path:r.path}}catch(r){return{provider:e,ok:!1,error:r.message}}}),l=i.filter(function(e){return!e.ok});for(const e of i)e.ok?console.log(" ["+e.provider.padEnd(8)+"] "+e.action+" -> "+path.relative(n,e.path)):console.error(" ["+e.provider.padEnd(8)+"] FAILED: "+e.error);console.log(""),console.log(s.dryRun?"Dry run complete. No files were written.":"KORM-JS skill installed."),l.length&&process.exit(1)}function main(){const e=parseArgs(process.argv.slice(2)),r=e._[0];r&&"--help"!==r&&"-h"!==r&&"help"!==r?"init"!==r?(console.error("Unknown command: "+r+"\n"),console.error(usage()),process.exit(1)):runInit(e):console.log(usage())}require.main===module&&main(),module.exports={PROVIDERS:PROVIDERS,PROVIDER_ALIASES:PROVIDER_ALIASES,parseArgs:parseArgs,resolveProvider:resolveProvider,BEGIN:BEGIN,END:END};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const{getDefaultTypeSize:getDefaultTypeSize,getDbType:getDbType}=require("./DataTypeMap");class BaseUtility{getModel(e,t){
|
|
1
|
+
const{getDefaultTypeSize:getDefaultTypeSize,getDbType:getDbType}=require("./DataTypeMap");class BaseUtility{getModel(e,t){const n=e?.schema;let r=null;const l=Object.keys(n);if(l.forEach(e=>{e===t&&(r=n[e])}),r||l.forEach(e=>{const l=n[e];l.table===t&&(r=l)}),r)return{...r,name:t,columns:Object.keys(r.columns).map(e=>{const t=r.columns[e];return this.parseColumnString(e,t)})};throw new Error(`Model ${t} not found`)}map2DbDefault(e){switch(e){case"now":case"now()":return"CURRENT_TIMESTAMP";default:return e}}getCollenedValue(e,{splitChar:t=":",give:n="right",trimWrap:r="{}"}={}){if(null==e)return null;const l=String(e),i=l.indexOf(t),a=e=>{if(!r)return e;if("string"==typeof r&&2===r.length){const[t,n]=r;return e.startsWith(t)&&e.endsWith(n)?e.slice(1,-1):e}return"string"==typeof r&&r.length&&e.startsWith(r)&&e.endsWith(r)?e.slice(r.length,-r.length):e};if(i>=0){const e=l.slice(0,i).trim(),r=l.slice(i+t.length).trim();return"both"===n?{left:a(e),right:a(r)}:a("left"===n?e:r)}return a(l)}parseForeignKey(e){if(!e)return{foreignMapTables:[]};const t=this.getCollenedValue(e),[n,r="id"]=String(t||"").split(":"),l=(this.getCollenedValue(n)||"").split(",").filter(Boolean),i=(this.getCollenedValue(r)||"").split(",").filter(Boolean);return{foreignMapTables:l.map((e,t)=>({table:e,column:i[t]||i[0]||"id"}))}}parseColumnString(e,t){const[n,...r]=String(t).split("|"),l=e=>r.find(t=>t.startsWith(e+":")),i=e=>r.join("|").includes(e),a=e=>e&&e.includes(":")?e.slice(e.indexOf(":")+1):null,s=l("default"),u=l("onUpdate"),o=l("comment"),c=l("foreignKey"),g=l("size"),p=this.parseForeignKey(c),m=!!c,f=getDbType(n,g?a(g):null),E=g?a(g):getDefaultTypeSize(f);return{name:e,type:f,size:E,isUnsigned:i("unsigned")||i("primaryKey")||m,columnType:`${n}${E?`(${E})`:""}`,nullable:!i("notNull"),primary:i("primaryKey"),autoIncrement:i("autoIncrement"),unique:i("unique"),default:this.map2DbDefault(a(s)),onUpdate:this.map2DbDefault(a(u)),comment:a(o)||"",hasForeignKey:m,...p}}formatColumnSchema(e,t){return this.parseColumnString(e,t)}formatColumnDef(e,t){return{name:e,type:t.DATA_TYPE,size:t.CHARACTER_MAXIMUM_LENGTH||getDefaultTypeSize(t.DATA_TYPE),isUnsigned:String(t.COLUMN_TYPE||"").toLowerCase().includes("unsigned"),columnType:t.COLUMN_TYPE,nullable:"YES"===t.IS_NULLABLE,primary:"PRI"===t.COLUMN_KEY,unique:"UNI"===t.COLUMN_KEY,autoIncrement:String(t.EXTRA||"").toLowerCase().includes("auto_increment"),hasForeignKey:"MUL"===t.COLUMN_KEY,comment:t.COMMENT,default:t.COLUMN_DEFAULT,onUpdate:((e="")=>{const t=String(e).match(/on update\s+([a-zA-Z_]+)/i);return t?t[1]:null})(t.EXTRA),foreignMapTables:"MUL"===t.COLUMN_KEY&&t.REFERENCED_TABLE_NAME?[{table:t.REFERENCED_TABLE_NAME,column:t.REFERENCED_COLUMN_NAME}]:[]}}escapeComment(e){return null==e?"":String(e).replace(/'/g,"\\'")}getDefaultTypeSize(e){return getDefaultTypeSize(e)}}module.exports=BaseUtility;
|
|
@@ -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={}){const o=this.controllerWrapper
|
|
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,s=this.utils.getModel(this.controllerWrapper,r),c=e?.action||"list";let a=null;const i={model:s,action:c,request:e,ctx:t,controller:o};switch(this.hookService?.executeValidatorHook&&await this.hookService.executeValidatorHook({...i}),this.hookService?.executeBeforeHook&&(e.beforeActionData=await this.hookService.executeBeforeHook({...i})),c){case"count":a=await this.queryService.executeCountQuery(s,e);break;case"sum":a=await this.queryService.executeSumQuery(s,e);break;case"list":a=await this.hookService.executeHasSoftDeleteHook(s)?await this.queryService.getSoftDeleteQuery(s,e):await this.queryService.getQuery(s,e);break;case"show":a=await this.queryService.executeShowQuery(s,e);break;case"create":a=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}`);a={message:"Record updated successfully",data:r,success:!0};break}case"replace":if(a=await this.queryService.executeReplaceQuery(s,e),!a)throw new Error(`Record not found or not replaced: ${r} returned ${a}`);a={message:"Record replaced successfully",data:a,success:!0};break;case"upsert":if(a=await this.queryService.executeUpsertQuery(s,e),!a)throw new Error(`Record not found or not upserted: ${r} returned ${a}`);a={message:"Record upserted successfully",data:a,success:!0};break;case"sync":if(a=await this.queryService.executeSyncQuery(s,e),!a)throw new Error(`Record not found or not synced: ${r} returned ${a}`);a={message:"Record synced successfully",data:a,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}`);a={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.`);a=await this.hookService.executeCustomAction({...i})}if(this.hookService?.executeAfterHook&&(a=await this.hookService.executeAfterHook({...i,data:a})),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);a.other_responses=r}return a}}module.exports=CurdTable;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const DataTypeMap=[{type:"number",maxSize:1,dbType:"tinyint"},{type:"number",maxSize:2,dbType:"smallint"},{type:"number",maxSize:3,dbType:"mediumint"},{type:"number",maxSize:4,dbType:"int"},{type:"number",maxSize:8,dbType:"bigint"},{type:"number",maxSize:null,dbType:"decimal"},{type:"number",maxSize:null,dbType:"numeric"},{type:"number",maxSize:null,dbType:"float"},{type:"number",maxSize:null,dbType:"double"},{type:"number",maxSize:null,dbType:"real"},{type:"number",maxSize:null,dbType:"bit"},{type:"string",maxSize:255,dbType:"varchar"},{type:"string",maxSize:1,dbType:"char"},{type:"string",maxSize:65535,dbType:"text"},{type:"string",maxSize:255,dbType:"tinytext"},{type:"string",maxSize:65535,dbType:"text"},{type:"string",maxSize:16777215,dbType:"mediumtext"},{type:"string",maxSize:4294967295,dbType:"longtext"},{type:"string",maxSize:255,dbType:"enum"},{type:"string",maxSize:255,dbType:"set"},{type:"string",maxSize:null,dbType:"json"},{type:"string",maxSize:36,dbType:"uuid"},{type:"buffer",maxSize:255,dbType:"binary"},{type:"buffer",maxSize:255,dbType:"varbinary"},{type:"buffer",maxSize:255,dbType:"tinyblob"},{type:"buffer",maxSize:65535,dbType:"blob"},{type:"buffer",maxSize:16777215,dbType:"mediumblob"},{type:"buffer",maxSize:4294967295,dbType:"longblob"},{type:"boolean",maxSize:null,dbType:"tinyint(1)"},{type:"boolean",maxSize:null,dbType:"boolean"},{type:"date",maxSize:null,dbType:"date"},{type:"date",maxSize:null,dbType:"datetime"},{type:"date",maxSize:null,dbType:"timestamp"},{type:"date",maxSize:null,dbType:"time"},{type:"date",maxSize:null,dbType:"year"},{type:"string",maxSize:null,dbType:"geometry"},{type:"string",maxSize:null,dbType:"point"},{type:"string",maxSize:null,dbType:"linestring"},{type:"string",maxSize:null,dbType:"polygon"},{type:"string",maxSize:null,dbType:"multipoint"},{type:"string",maxSize:null,dbType:"multilinestring"},{type:"string",maxSize:null,dbType:"multipolygon"},{type:"string",maxSize:null,dbType:"geometrycollection"},{type:"string",maxSize:null,dbType:"USER-DEFINED"}];function getDbType(e,t,p){if(p){const y=DataTypeMap.find(y=>y.type===e&&y.maxSize===t&&y.dbType.toLowerCase()===p.toLowerCase());if(y)return y.dbType}let y=DataTypeMap.find(p=>p.type===e&&p.maxSize===t);if(!y){
|
|
1
|
+
const DataTypeMap=[{type:"number",maxSize:1,dbType:"tinyint"},{type:"number",maxSize:2,dbType:"smallint"},{type:"number",maxSize:3,dbType:"mediumint"},{type:"number",maxSize:4,dbType:"int"},{type:"number",maxSize:8,dbType:"bigint"},{type:"number",maxSize:null,dbType:"decimal"},{type:"number",maxSize:null,dbType:"numeric"},{type:"number",maxSize:null,dbType:"float"},{type:"number",maxSize:null,dbType:"double"},{type:"number",maxSize:null,dbType:"real"},{type:"number",maxSize:null,dbType:"bit"},{type:"string",maxSize:255,dbType:"varchar"},{type:"string",maxSize:1,dbType:"char"},{type:"string",maxSize:65535,dbType:"text"},{type:"string",maxSize:255,dbType:"tinytext"},{type:"string",maxSize:65535,dbType:"text"},{type:"string",maxSize:16777215,dbType:"mediumtext"},{type:"string",maxSize:4294967295,dbType:"longtext"},{type:"string",maxSize:255,dbType:"enum"},{type:"string",maxSize:255,dbType:"set"},{type:"string",maxSize:null,dbType:"json"},{type:"string",maxSize:36,dbType:"uuid"},{type:"buffer",maxSize:255,dbType:"binary"},{type:"buffer",maxSize:255,dbType:"varbinary"},{type:"buffer",maxSize:255,dbType:"tinyblob"},{type:"buffer",maxSize:65535,dbType:"blob"},{type:"buffer",maxSize:16777215,dbType:"mediumblob"},{type:"buffer",maxSize:4294967295,dbType:"longblob"},{type:"boolean",maxSize:null,dbType:"tinyint(1)"},{type:"boolean",maxSize:null,dbType:"boolean"},{type:"date",maxSize:null,dbType:"date"},{type:"date",maxSize:null,dbType:"datetime"},{type:"date",maxSize:null,dbType:"timestamp"},{type:"date",maxSize:null,dbType:"time"},{type:"date",maxSize:null,dbType:"year"},{type:"string",maxSize:null,dbType:"geometry"},{type:"string",maxSize:null,dbType:"point"},{type:"string",maxSize:null,dbType:"linestring"},{type:"string",maxSize:null,dbType:"polygon"},{type:"string",maxSize:null,dbType:"multipoint"},{type:"string",maxSize:null,dbType:"multilinestring"},{type:"string",maxSize:null,dbType:"multipolygon"},{type:"string",maxSize:null,dbType:"geometrycollection"},{type:"string",maxSize:null,dbType:"USER-DEFINED"}];function getDbType(e,t,p){if(p){const y=DataTypeMap.find(y=>y.type===e&&y.maxSize===t&&y.dbType.toLowerCase()===p.toLowerCase());if(y)return y.dbType}let y=DataTypeMap.find(p=>p.type===e&&p.maxSize===t);if(!y){const t=DataTypeMap.filter(t=>t.type===e&&null===t.maxSize);y=t.length>1&&p?t.find(e=>e.dbType.toLowerCase()===p.toLowerCase()):t[0]}return y?y.dbType:e}function getSchemaType(e){return DataTypeMap.find(t=>t.dbType===e)?.type}function getDefaultTypeSize(e){return DataTypeMap.find(t=>t.dbType===e)?.maxSize}module.exports={DataTypeMap:DataTypeMap,getDbType:getDbType,getSchemaType:getSchemaType,getDefaultTypeSize:getDefaultTypeSize};
|
|
@@ -1 +1 @@
|
|
|
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){
|
|
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){const t="string"==typeof e?e: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 +1 @@
|
|
|
1
|
-
const HelperUtility=require("./HelperUtility"),logger=require("../../Logger");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getHookService(){return this.controllerWrapper.hookService}getQueryBuilder(e,t=null){let r=this.db(e.table);return t&&(r=t),r._getMyModel=()=>e,r}parseValue(e){return this.helperUtility.parseValue(e)}parseWhereValue(e){return this.helperUtility.parseWhereValue(e)}parseWhereColumn(e){return this.helperUtility.parseWhereColumn(e)}_applyOrWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.orWhereIn(t,o);else switch(r){case"between":e.orWhereBetween(t,o);break;case"notBetween":e.orWhereNotBetween(t,o);break;case"in":e.orWhereIn(t,o);break;case"notIn":e.orWhereNotIn(t,o);break;case"like":e.orWhereRaw(`\`${t}\` LIKE ?`,[o]);break;default:e.orWhere(t,r,o)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.whereIn(t,o);else switch(r){case"between":e.whereBetween(t,o);break;case"notBetween":e.whereNotBetween(t,o);break;case"in":e.whereIn(t,o);break;case"notIn":e.whereNotIn(t,o);break;case"like":e.whereRaw(`\`${t}\` LIKE ?`,[o]);break;default:e.where(t,r,o)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:o})=>t?t({current:e,part:r,source:o}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{direct:{},nested:{}};const r=`${t}.`,o={},i={};for(const[t,s]of Object.entries(e))if(t.startsWith(r)){const e=t.slice(r.length);e.includes(".")?i[e]=s:o[e]=s}return{direct:o,nested:i}}_applyWithWhereConditions(e,t){for(const[r,o]of Object.entries(t)){const{joinType:t="AND",column:i}=this.parseWhereColumn(r),{operator:s,value:n}=this.parseWhereValue(o);"AND"===t?this._applyAndWhereCondition(e,i,s,n):this._applyOrWhereCondition(e,i,s,n)}}async fetchRelatedRows(e,t,r={}){if(e.through){
|
|
1
|
+
const HelperUtility=require("./HelperUtility"),logger=require("../../Logger");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getHookService(){return this.controllerWrapper.hookService}getQueryBuilder(e,t=null){let r=this.db(e.table);return t&&(r=t),r._getMyModel=()=>e,r}parseValue(e){return this.helperUtility.parseValue(e)}parseWhereValue(e){return this.helperUtility.parseWhereValue(e)}parseWhereColumn(e){return this.helperUtility.parseWhereColumn(e)}_applyOrWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.orWhereIn(t,o);else switch(r){case"between":e.orWhereBetween(t,o);break;case"notBetween":e.orWhereNotBetween(t,o);break;case"in":e.orWhereIn(t,o);break;case"notIn":e.orWhereNotIn(t,o);break;case"like":e.orWhereRaw(`\`${t}\` LIKE ?`,[o]);break;default:e.orWhere(t,r,o)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.whereIn(t,o);else switch(r){case"between":e.whereBetween(t,o);break;case"notBetween":e.whereNotBetween(t,o);break;case"in":e.whereIn(t,o);break;case"notIn":e.whereNotIn(t,o);break;case"like":e.whereRaw(`\`${t}\` LIKE ?`,[o]);break;default:e.where(t,r,o)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:o})=>t?t({current:e,part:r,source:o}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{direct:{},nested:{}};const r=`${t}.`,o={},i={};for(const[t,s]of Object.entries(e))if(t.startsWith(r)){const e=t.slice(r.length);e.includes(".")?i[e]=s:o[e]=s}return{direct:o,nested:i}}_applyWithWhereConditions(e,t){for(const[r,o]of Object.entries(t)){const{joinType:t="AND",column:i}=this.parseWhereColumn(r),{operator:s,value:n}=this.parseWhereValue(o);"AND"===t?this._applyAndWhereCondition(e,i,s,n):this._applyOrWhereCondition(e,i,s,n)}}async fetchRelatedRows(e,t,r={}){if(e.through){const o=await this.db(e.through).whereIn(e.throughLocalKey,t),i=this.db(e.table).whereIn(e.foreignKey,o.map(t=>t[e.throughForeignKey]));return Object.keys(r).length>0&&this._applyWithWhereConditions(i,r),i}{const o=this.db(e.table).whereIn(e.foreignKey,t);return Object.keys(r).length>0&&this._applyWithWhereConditions(o,r),o}}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:o,withTree:i,relation:s,withWhere:n={}}=e;if(!s){const e=this.getHookService().getModelInstance(o),s=`get${r.charAt(0).toUpperCase()+r.slice(1)}Relation`;if("function"==typeof e[s]){const{direct:l,nested:a}=this._getWithWhereForRelation(n,r),h={rows:t,relName:r,model:o,withTree:i,controller:this.controllerWrapper,relation:o.hasRelations[r],qb:this,db:this.db,withWhere:l,nestedWithWhere:a};await e[s](h)}else logger.warn(`Method ${s} not found in model ${o.name}`);return t}const l=t.map(e=>e[s.localKey]);let a=[];const h="one"===s?.type,{direct:c,nested:p}=this._getWithWhereForRelation(n,r);let u={...c};try{const e=this.utils.getModel(this.controllerWrapper,r);if(this.controllerWrapper?.hookService){await this.controllerWrapper.hookService.executeHasSoftDeleteHook(e)&&(u={...u,deleted_at:null})}}catch(e){}a=await this.fetchRelatedRows(s,l,u);const y=new Map;for(const e of a){const t=e[s.foreignKey];y.has(t)||y.set(t,[]),y.get(t).push(e)}for(const e of t){const t=e[s.localKey];h&&1==y.get(t)?.length?e[r]=y.get(t)[0]:e[r]=y.get(t)||[]}const d=Object.keys(i);for(const e of d){const o=i[e],s=t.filter(e=>h?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:s,relName:e,model:n,withTree:o,relation:n.hasRelations[e],withWhere:p})}return t}filterWhere(e,t=""){return t?Object.keys(e).filter(e=>e.includes(t)).reduce((r,o)=>(r[o.replace(t,"")]=e[o],r),{}):Object.keys(e).filter(e=>!e.includes(".")).reduce((t,r)=>(t[r]=e[r],t),{})}async getQuery(e,t){try{const{where:r={},with:o,withWhere:i,select:s,orderBy:n={column:"id",direction:"asc"},limit:l=10,offset:a=0,page:h,groupBy:c,having:p,distinct:u,join:y,leftJoin:d,rightJoin:f,innerJoin:g,count:W=!1}=t,w=this.getQueryBuilder(e);s&&(Array.isArray(s)||"string"==typeof s)?w.select(s):w.select("*"),u&&(Array.isArray(u)||"string"==typeof u?w.distinct(u):w.distinct()),y&&this._applyJoins(w,y,"join"),d&&this._applyJoins(w,d,"leftJoin"),f&&this._applyJoins(w,f,"rightJoin"),g&&this._applyJoins(w,g,"innerJoin"),this._applyWhereClause(w,r,o),c&&(Array.isArray(c),w.groupBy(c)),p&&this._applyHavingClause(w,p),n&&this._applyOrderBy(w,n);let b=!1;const m=l;let _=a,A=1,j=0;h&&l>0&&(A=Math.max(1,parseInt(h)),_=(A-1)*l),l>0&&(w.limit(m),_>0&&w.offset(_));const k=[],C=w.toSQL();k.push(C.sql);const J=await w;if(o&&o.length>0){const t=this.buildWithTree(o);for(const r of Object.keys(t))await this.fetchAndAttachRelated({parentRows:J,relName:r,model:e,withTree:t[r],relation:e.hasRelations[r],withWhere:i||{}})}let R=null;if(l>0)try{const t=this.getQueryBuilder(e);r&&Object.keys(r).length>0&&this._applyWhereClause(t,r),y&&this._applyJoins(t,y,"join"),d&&this._applyJoins(t,d,"leftJoin"),f&&this._applyJoins(t,f,"rightJoin"),g&&this._applyJoins(t,g,"innerJoin");const o=t.count("* as cnt");k.push(o.toSQL().sql);R=(await o.first()).cnt}catch(e){logger.warn("Failed to get total count:",e.message),R=J.length}l>0&&null!==R&&(j=Math.ceil(R/l),b=A<j);const B=b?A+1:null,N=A>1?A-1:null;return{data:J,totalCount:R,...this.controllerWrapper.debug?{sqlDebug:k}:{},...l>0?{pagination:{page:A,limit:m,offset:_,totalPages:j,hasNext:b,hasPrev:A>1,nextPage:B,prevPage:N}}:{}}}catch(t){throw logger.error("QueryService.getQuery error:",t),new Error(`Failed to execute query: ${t.message} on model ${e.name}`)}}getSumQuery(e,t){const{where:r={},join:o,leftJoin:i,rightJoin:s,innerJoin:n}=t,l=t.data||t,a=l.sumColumn,h=l.sumFormula;if(!a&&!h)throw new Error("Sum action requires either data.sumColumn or data.sumFormula");const c=e=>"`"+String(e).replace(/[`\\]/g,"")+"`";let p;if(a){const e=String(a).trim().replace(/[^a-zA-Z0-9_]/g,"");if(!e)throw new Error("data.sumColumn must be a valid column name");p="SUM("+c(e)+")"}else{const e=String(h).trim();if(!/\{[a-zA-Z0-9_]+\}/.test(e))throw new Error("data.sumFormula must contain at least one {columnName}");let t=0;for(const r of e)if("("===r)t++;else if(")"===r&&(t--,t<0))break;if(0!==t)throw new Error("data.sumFormula has unbalanced or misordered parentheses ( and )");const r=e.replace(/\{[a-zA-Z0-9_]+\}/g,"@");if(!/^[\s0-9.+*\/\-()@]+$/.test(r))throw new Error("data.sumFormula may only use BODMAS: numbers, + - * /, parentheses, and {columnName}");p="SUM("+e.replace(/\{([a-zA-Z0-9_]+)\}/g,(e,t)=>c(t))+")"}const u=this.getQueryBuilder(e);return o&&this._applyJoins(u,o,"join"),i&&this._applyJoins(u,i,"leftJoin"),s&&this._applyJoins(u,s,"rightJoin"),n&&this._applyJoins(u,n,"innerJoin"),this._applyWhereClause(u,r,[]),u.select(this.db.raw(p+" as sum")),u}_applyWhereWithArray(e,t,r){for(const o of t)this._applyWhereClause(e,o,r)}_applyWhereClause(e,t,r=[]){if(Array.isArray(t))this._applyWhereWithArray(e,t,r);else{if(t&&Object.keys(t).length>0){let r=this.helperUtility.getDotWalkQuery(t);logger.debug({filteredWhere:r}),r=this.helperUtility.objectFilter(r,(e,t)=>!e.startsWith("!")&&("__exists__"!==e&&("object"!=typeof t||null===t)));for(const[t,o]of Object.entries(r)){const{joinType:r="AND",column:i}=this.parseWhereColumn(t),{operator:s,value:n}=this.parseWhereValue(o);"AND"===r?this._applyAndWhereCondition(e,i,s,n):this._applyOrWhereCondition(e,i,s,n)}}this._applyNestedWhere(e,t,r)}}_getNestedWhereConditions(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,o={};for(const[i,s]of Object.entries(e))if(i.startsWith(r)){o[i.slice(r.length)]=s}else i===t&&!0===s&&(o.__exists__=!0);return o}_getTopLevelRelationsFromWhere(e){if(!e||"object"!=typeof e)return[];const t=new Set;for(const r of Object.keys(e))if(r.includes(".")){const e=r.split(".")[0];t.add(e)}else r.startsWith("!")&&t.add(r);return[...t]}_applyNestedWhere(e,t,r){const o=this,i=e._getMyModel(),s=this._getTopLevelRelationsFromWhere(t);if(0!==s.length)for(const r of s){const s=r.startsWith("!"),n=s?r.slice(1):r,l=this._getNestedWhereConditions(t,r);if(0===Object.keys(l).length)continue;const a=i.hasRelations?.[n];if(!a){logger.warn(`Relation ${n} not found in model ${i.modelName}`);continue}let h;try{h=this.utils.getModel(this.controllerWrapper,a.table||n)}catch(e){try{h=this.utils.getModel(this.controllerWrapper,n)}catch(e){logger.warn(`Model for relation ${n} (table: ${a.table}) not found`);continue}}e[s?"whereNotExists":"whereExists"](function(){const e=o.getQueryBuilder(h,this.select("*").from(a.table));e.whereRaw(`\`${a.table}\`.\`${a.foreignKey}\` = \`${i.table}\`.\`${a.localKey}\``);if(!(!0===l.__exists__&&1===Object.keys(l).length)){const t={...l};delete t.__exists__,o._applyWhereClause(e,t,[])}})}}_applyJoins(e,t,r){const o=Array.isArray(t)?t:[t];for(const t of o)"string"==typeof t?e[r](t):"object"==typeof t&&(t.table&&t.on?e[r](t.table,t.on):t.table&&t.first&&t.operator&&t.second&&e[r](t.table,t.first,t.operator,t.second))}_applyWithWhere(e,t){try{if(Array.isArray(t))for(const r of t)"string"==typeof r?e.withWhere(r):"object"==typeof r&&e.withWhere(r.column,r.operator,r.value);else if("object"==typeof t)for(const[r,o]of Object.entries(t))e.withWhere(r,o)}catch(e){logger.warn("Failed to apply withWhere:",e.message)}}_applyHavingClause(e,t){for(const[r,o]of Object.entries(t))"object"==typeof o&&o.operator?e.having(r,o.operator,o.value):e.having(r,o)}_applyOrderBy(e,t){if(Array.isArray(t))for(const r of t)"string"==typeof r?e.orderBy(r):"object"==typeof r&&e.orderBy(r.column,r.direction||"asc");else"string"==typeof t?e.orderBy(t):"object"==typeof t&&e.orderBy(t.column,t.direction||"asc")}}module.exports=QueryBuilder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const QueryBuilder=require("./QueryBuilder");class QueryService{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.queryBuilder=new QueryBuilder(e,t,r)}async getQuery(e,t){return await this.queryBuilder.getQuery(e,t)}async getSoftDeleteQuery(e,t){return await this.queryBuilder.getQuery(e,{...t,where:{...t.where||{},deleted_at:null}})}async executeShowQuery(e,t){
|
|
1
|
+
const QueryBuilder=require("./QueryBuilder");class QueryService{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.queryBuilder=new QueryBuilder(e,t,r)}async getQuery(e,t){return await this.queryBuilder.getQuery(e,t)}async getSoftDeleteQuery(e,t){return await this.queryBuilder.getQuery(e,{...t,where:{...t.where||{},deleted_at:null}})}async executeShowQuery(e,t){const r=await this.getQuery(e,{...t,limit:1,offset:0});return r.data.length>0?r.data[0]:null}async executeCountQuery(e,t){const r=await this.db(e.table).count();return Object.values(r[0])[0]}async executeSumQuery(e,t){const r=await this.queryBuilder.getSumQuery(e,t).first(),a=r&&null!=r.sum?r.sum:0;return Number(a)}async executeCreateQuery(e,t){const r=await this.db(e.table).insert(t.data),a=Array.isArray(r)?r[0]:r;if(null==a)return[];const u=e.columns&&e.columns.find(e=>e.primary),i=u&&u.name?u.name:"id",n=await this.db(e.table).where(i,a).select("*");return Array.isArray(n)?n:[n]}async executeUpdateQuery(e,t){return await this.db.transaction(async r=>(await r(e.table).where(t.where).update(t.data),await r(e.table).where(t.where).first()))}async executeDeleteQuery(e,t){return await this.db(e.table).where(t.where).delete()}async executeSoftDeleteQuery(e,t){await this.db(e.table).where(t.where).update({deleted_at:new Date});const r=await this.db(e.table).where(t.where).select("*");return Array.isArray(r)?r:[r]}async executeUpsertQuery(e,t){return await this.db(e.table).insert(t.data).onConflict(t.conflict).update(t.data)}async executeReplaceQuery(e,t){return await this.db(e.table).replace(t.data)}async executeSyncQuery(e,t){return{insertOrUpdateQuery:await this.db(e.table).insert(t.data).onConflict(t.conflict).update(t.data),deleteQuery:await this.db(e.table).where(t.where).delete()}}}module.exports=QueryService;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const CurdTable=require("./CurdTable"),HelperUtility=require("./HelperUtility"),logger=require("../../Logger");class SyncTable{constructor(e,t,
|
|
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){const 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],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 getRelations(e){await this.db.raw(`SHOW CREATE TABLE ${e}`);return{}}async generateSchema(){const e=await this.getTablesOfDatabase(),t={};for(const n of e){const 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{getDefaultTypeSize:getDefaultTypeSize,getDbType:getDbType}=require("./DataTypeMap");class BaseUtility{isInternalDefault(e){if("string"!=typeof e)return!1;const t=/^CURRENT_TIMESTAMP$/i.test(e),n=/^[a-zA-Z_][a-zA-Z0-9_]*\s*\(.*\)$/.test(e),r=/::[a-zA-Z0-9_]+/.test(e);return t||n||r}getModel(e,t){
|
|
1
|
+
const{getDefaultTypeSize:getDefaultTypeSize,getDbType:getDbType}=require("./DataTypeMap");class BaseUtility{isInternalDefault(e){if("string"!=typeof e)return!1;const t=/^CURRENT_TIMESTAMP$/i.test(e),n=/^[a-zA-Z_][a-zA-Z0-9_]*\s*\(.*\)$/.test(e),r=/::[a-zA-Z0-9_]+/.test(e);return t||n||r}getModel(e,t){const n=e?.schema;let r=null;const i=Object.keys(n);if(i.forEach(e=>{e===t&&(r=n[e])}),r||i.forEach(e=>{const i=n[e];i.table===t&&(r=i)}),r)return{...r,name:t,columns:Object.keys(r.columns).map(e=>{const t=r.columns[e];return this.parseColumnString(e,t)})};throw new Error(`Model ${t} not found`)}map2DbDefault(e){return"now"===e?"CURRENT_TIMESTAMP":e}getCollenedValue(e,{splitChar:t=":",give:n="right",trimWrap:r="{}"}={}){if(null==e)return null;const i=String(e),l=i.indexOf(t),a=e=>{if(!r)return e;if("string"==typeof r&&2===r.length){const[t,n]=r;return e.startsWith(t)&&e.endsWith(n)?e.slice(1,-1):e}return"string"==typeof r&&r.length&&e.startsWith(r)&&e.endsWith(r)?e.slice(r.length,-r.length):e};if(l>=0){const e=i.slice(0,l).trim(),r=i.slice(l+t.length).trim();return"both"===n?{left:a(e),right:a(r)}:a("left"===n?e:r)}return a(i)}parseForeignKey(e){if(!e)return{foreignMapTables:[]};const t=this.getCollenedValue(e),[n,r="id"]=String(t||"").split(":"),i=(this.getCollenedValue(n)||"").split(",").filter(Boolean),l=(this.getCollenedValue(r)||"").split(",").filter(Boolean);return{foreignMapTables:i.map((e,t)=>({table:e,column:l[t]||l[0]||"id"}))}}parseColumnString(e,t){const[n,...r]=String(t).split("|"),i=e=>r.find(t=>t.startsWith(e+":")),l=e=>r.join("|").includes(e),a=e=>e&&e.includes(":")?e.slice(e.indexOf(":")+1):null,s=i("default"),u=i("onUpdate"),o=i("comment"),g=i("foreignKey"),p=i("size"),T=this.parseForeignKey(g),E=!!g,c=getDbType(n,p?a(p):null),m=p?a(p):getDefaultTypeSize(c);return{name:e,type:c,size:+m,isUnsigned:l("unsigned")||l("primaryKey")||E,columnType:`${n}${m?`(${m})`:""}`,nullable:!l("notNull"),primary:l("primaryKey"),autoIncrement:l("autoIncrement"),unique:l("unique"),default:this.map2DbDefault(a(s)),onUpdate:this.map2DbDefault(a(u)),comment:a(o)||"",hasForeignKey:E,...T}}formatColumnSchema(e,t){return this.parseColumnString(e,t)}formatColumnDef(e,t){return{name:e,type:t.DATA_TYPE,size:+(t.CHARACTER_MAXIMUM_LENGTH||getDefaultTypeSize(t.DATA_TYPE)),isUnsigned:String(t.COLUMN_TYPE||"").toLowerCase().includes("unsigned"),columnType:t.COLUMN_TYPE,nullable:"YES"===t.IS_NULLABLE,primary:"PRIMARY KEY"===t.CONSTRAINT_TYPE,unique:"UNIQUE"===t.CONSTRAINT_TYPE,autoIncrement:String(t.EXTRA||"").toLowerCase().includes("auto_increment"),hasForeignKey:"FOREIGN KEY"===t.CONSTRAINT_TYPE,comment:t.COMMENT||"",default:t.COLUMN_DEFAULT,onUpdate:((e="")=>{const t=String(e).match(/on update\s+([a-zA-Z_]+)/i);return t?t[1]:null})(t.EXTRA),foreignMapTables:"FOREIGN KEY"===t.CONSTRAINT_TYPE&&t.REFERENCED_TABLE_NAME?[{table:t.REFERENCED_TABLE_NAME,column:t.REFERENCED_COLUMN_NAME}]:[]}}escapeComment(e){return null==e?"":String(e).replace(/'/g,"\\'")}getDefaultTypeSize(e){return getDefaultTypeSize(e)}}module.exports=BaseUtility;
|
package/clients/pg/CurdTable.js
CHANGED
|
@@ -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=null){const o=this.controllerWrapper
|
|
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=null){const o=this.controllerWrapper,s=this.utils.getModel(this.controllerWrapper,r),c=e?.action||"list";let u=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":u=await this.queryService.executeCountQuery(s,e);break;case"sum":u=await this.queryService.executeSumQuery(s,e);break;case"list":u=await this.hookService.executeHasSoftDeleteHook(s)?await this.queryService.getSoftDeleteQuery(s,e):await this.queryService.getQuery(s,e);break;case"show":u=await this.queryService.executeShowQuery(s,e);break;case"create":u=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}`);u={message:"Record updated successfully",data:r,success:!0};break}case"replace":if(u=await this.queryService.executeReplaceQuery(s,e),!u)throw new Error(`Record not found or not replaced: ${r} returned ${u}`);u={message:"Record replaced successfully",data:u,success:!0};break;case"upsert":if(u=await this.queryService.executeUpsertQuery(s,e),!u)throw new Error(`Record not found or not upserted: ${r} returned ${u}`);u={message:"Record upserted successfully",data:u,success:!0};break;case"sync":if(u=await this.queryService.executeSyncQuery(s,e),!u)throw new Error(`Record not found or not synced: ${r} returned ${u}`);u={message:"Record synced successfully",data:u,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}`);u={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.`);u=await this.hookService.executeCustomAction({...a})}if(this.hookService?.executeAfterHook&&(u=await this.hookService.executeAfterHook({...a,data:u})),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);u.other_responses=r}return u}}module.exports=CurdTable;
|
|
@@ -1 +1 @@
|
|
|
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){
|
|
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){const t="string"==typeof e?e: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 +1 @@
|
|
|
1
|
-
const HelperUtility=require("./HelperUtility"),logger=require("../../Logger");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getHookService(){return this.controllerWrapper.hookService}getQueryBuilder(e,t=null){let r=this.db(e.table);return t&&(r=t),r._getMyModel=()=>e,r}parseValue(e){return this.helperUtility.parseValue(e)}parseWhereValue(e){return this.helperUtility.parseWhereValue(e)}parseWhereColumn(e){return this.helperUtility.parseWhereColumn(e)}_applyOrWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.orWhereIn(t,o);else switch(r){case"between":e.orWhereBetween(t,o);break;case"notBetween":e.orWhereNotBetween(t,o);break;case"in":e.orWhereIn(t,o);break;case"notIn":e.orWhereNotIn(t,o);break;case"like":e.orWhere(t,"like",o);break;default:e.orWhere(t,r,o)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.whereIn(t,o);else switch(r){case"between":e.whereBetween(t,o);break;case"notBetween":e.whereNotBetween(t,o);break;case"in":e.whereIn(t,o);break;case"notIn":e.whereNotIn(t,o);break;case"like":e.where(t,"like",o);break;default:e.where(t,r,o)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:o})=>t?t({current:e,part:r,source:o}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{direct:{},nested:{}};const r=`${t}.`,o={},i={};for(const[t,s]of Object.entries(e))if(t.startsWith(r)){const e=t.slice(r.length);e.includes(".")?i[e]=s:o[e]=s}return{direct:o,nested:i}}_applyWithWhereConditions(e,t){for(const[r,o]of Object.entries(t)){const{joinType:t="AND",column:i}=this.parseWhereColumn(r),{operator:s,value:n}=this.parseWhereValue(o);"AND"===t?this._applyAndWhereCondition(e,i,s,n):this._applyOrWhereCondition(e,i,s,n)}}async fetchRelatedRows(e,t,r={}){if(e.through){
|
|
1
|
+
const HelperUtility=require("./HelperUtility"),logger=require("../../Logger");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getHookService(){return this.controllerWrapper.hookService}getQueryBuilder(e,t=null){let r=this.db(e.table);return t&&(r=t),r._getMyModel=()=>e,r}parseValue(e){return this.helperUtility.parseValue(e)}parseWhereValue(e){return this.helperUtility.parseWhereValue(e)}parseWhereColumn(e){return this.helperUtility.parseWhereColumn(e)}_applyOrWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.orWhereIn(t,o);else switch(r){case"between":e.orWhereBetween(t,o);break;case"notBetween":e.orWhereNotBetween(t,o);break;case"in":e.orWhereIn(t,o);break;case"notIn":e.orWhereNotIn(t,o);break;case"like":e.orWhere(t,"like",o);break;default:e.orWhere(t,r,o)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.whereIn(t,o);else switch(r){case"between":e.whereBetween(t,o);break;case"notBetween":e.whereNotBetween(t,o);break;case"in":e.whereIn(t,o);break;case"notIn":e.whereNotIn(t,o);break;case"like":e.where(t,"like",o);break;default:e.where(t,r,o)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:o})=>t?t({current:e,part:r,source:o}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{direct:{},nested:{}};const r=`${t}.`,o={},i={};for(const[t,s]of Object.entries(e))if(t.startsWith(r)){const e=t.slice(r.length);e.includes(".")?i[e]=s:o[e]=s}return{direct:o,nested:i}}_applyWithWhereConditions(e,t){for(const[r,o]of Object.entries(t)){const{joinType:t="AND",column:i}=this.parseWhereColumn(r),{operator:s,value:n}=this.parseWhereValue(o);"AND"===t?this._applyAndWhereCondition(e,i,s,n):this._applyOrWhereCondition(e,i,s,n)}}async fetchRelatedRows(e,t,r={}){if(e.through){const o=await this.db(e.through).whereIn(e.throughLocalKey,t),i=this.db(e.table).whereIn(e.foreignKey,o.map(t=>t[e.throughForeignKey]));return Object.keys(r).length>0&&this._applyWithWhereConditions(i,r),i}{const o=this.db(e.table).whereIn(e.foreignKey,t);return Object.keys(r).length>0&&this._applyWithWhereConditions(o,r),o}}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:o,withTree:i,relation:s,withWhere:n={}}=e;if(!s){const e=this.getHookService().getModelInstance(o),s=`get${r.charAt(0).toUpperCase()+r.slice(1)}Relation`;if("function"==typeof e[s]){const{direct:l,nested:a}=this._getWithWhereForRelation(n,r),h={rows:t,relName:r,model:o,withTree:i,controller:this.controllerWrapper,relation:o.hasRelations[r],qb:this,db:this.db,withWhere:l,nestedWithWhere:a};await e[s](h)}else logger.warn(`Method ${s} not found in model ${o.name}`);return t}const l=t.map(e=>e[s.localKey]),a="one"===s?.type;let h=[];const{direct:c,nested:p}=this._getWithWhereForRelation(n,r);let u={...c};try{const e=this.utils.getModel(this.controllerWrapper,r);if(this.controllerWrapper?.hookService){await this.controllerWrapper.hookService.executeHasSoftDeleteHook(e)&&(u={...u,deleted_at:null})}}catch(e){}h=await this.fetchRelatedRows(s,l,u);const y=new Map;for(const e of h){const t=e[s.foreignKey];y.has(t)||y.set(t,[]),y.get(t).push(e)}for(const e of t){const t=e[s.localKey];a&&1==y.get(t)?.length?e[r]=y.get(t)[0]:e[r]=y.get(t)||[]}const d=Object.keys(i);for(const e of d){const o=i[e],s=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:s,relName:e,model:n,withTree:o,relation:n.hasRelations[e],withWhere:p})}return t}filterWhere(e,t=""){return t?Object.keys(e).filter(e=>e.includes(t)).reduce((r,o)=>(r[o.replace(t,"")]=e[o],r),{}):Object.keys(e).filter(e=>!e.includes(".")).reduce((t,r)=>(t[r]=e[r],t),{})}async getQuery(e,t){try{const{where:r={},with:o,withWhere:i,select:s,orderBy:n={column:"id",direction:"asc"},limit:l=10,offset:a=0,page:h,groupBy:c,having:p,distinct:u,join:y,leftJoin:d,rightJoin:f,innerJoin:g,count:W=!1}=t,w=this.getQueryBuilder(e);s&&(Array.isArray(s)||"string"==typeof s)?w.select(s):w.select("*"),u&&(Array.isArray(u)||"string"==typeof u?w.distinct(u):w.distinct()),y&&this._applyJoins(w,y,"join"),d&&this._applyJoins(w,d,"leftJoin"),f&&this._applyJoins(w,f,"rightJoin"),g&&this._applyJoins(w,g,"innerJoin"),this._applyWhereClause(w,r,o),c&&(Array.isArray(c),w.groupBy(c)),p&&this._applyHavingClause(w,p),n&&this._applyOrderBy(w,n);let b=!1;const m=l;let _=a,A=1,j=0;h&&l>0&&(A=Math.max(1,parseInt(h)),_=(A-1)*l),l>0&&(w.limit(m),_>0&&w.offset(_));const k=[],C=w.toSQL();k.push(C.sql);const J=await w;if(o&&o.length>0){const t=this.buildWithTree(o);for(const r of Object.keys(t))await this.fetchAndAttachRelated({parentRows:J,relName:r,model:e,withTree:t[r],relation:e.hasRelations[r],withWhere:i||{}})}let B=null;if(l>0)try{const t=this.getQueryBuilder(e);r&&Object.keys(r).length>0&&this._applyWhereClause(t,r),y&&this._applyJoins(t,y,"join"),d&&this._applyJoins(t,d,"leftJoin"),f&&this._applyJoins(t,f,"rightJoin"),g&&this._applyJoins(t,g,"innerJoin");const o=t.count("* as cnt");k.push(o.toSQL().sql);B=(await o.first()).cnt}catch(e){logger.warn("Failed to get total count:",e.message),B=J.length}l>0&&null!==B&&(j=Math.ceil(B/l),b=A<j);const N=b?A+1:null,O=A>1?A-1:null;return{data:J,totalCount:B,...this.controllerWrapper.debug?{sqlDebug:k}:{},...l>0?{pagination:{page:A,limit:m,offset:_,totalPages:j,hasNext:b,hasPrev:A>1,nextPage:N,prevPage:O}}:{}}}catch(t){throw logger.error("QueryService.getQuery error:",t),new Error(`Failed to execute query: ${t.message} on model ${e.name}`)}}getSumQuery(e,t){const{where:r={},join:o,leftJoin:i,rightJoin:s,innerJoin:n}=t,l=t.data||t,a=l.sumColumn,h=l.sumFormula;if(!a&&!h)throw new Error("Sum action requires either data.sumColumn or data.sumFormula");const c=e=>'"'+String(e).replace(/["\\]/g,"")+'"';let p;if(a){const e=String(a).trim().replace(/[^a-zA-Z0-9_]/g,"");if(!e)throw new Error("data.sumColumn must be a valid column name");p="SUM("+c(e)+")"}else{const e=String(h).trim();if(!/\{[a-zA-Z0-9_]+\}/.test(e))throw new Error("data.sumFormula must contain at least one {columnName}");let t=0;for(const r of e)if("("===r)t++;else if(")"===r&&(t--,t<0))break;if(0!==t)throw new Error("data.sumFormula has unbalanced or misordered parentheses ( and )");const r=e.replace(/\{[a-zA-Z0-9_]+\}/g,"@");if(!/^[\s0-9.+*\/\-()@]+$/.test(r))throw new Error("data.sumFormula may only use BODMAS: numbers, + - * /, parentheses, and {columnName}");p="SUM("+e.replace(/\{([a-zA-Z0-9_]+)\}/g,(e,t)=>c(t))+")"}const u=this.getQueryBuilder(e);return o&&this._applyJoins(u,o,"join"),i&&this._applyJoins(u,i,"leftJoin"),s&&this._applyJoins(u,s,"rightJoin"),n&&this._applyJoins(u,n,"innerJoin"),this._applyWhereClause(u,r,[]),u.select(this.db.raw(p+" as sum")),u}_applyWhereWithArray(e,t,r){for(const o of t)this._applyWhereClause(e,o,r)}_applyWhereClause(e,t,r=[]){if(Array.isArray(t))this._applyWhereWithArray(e,t,r);else{if(t&&Object.keys(t).length>0){let r=this.helperUtility.getDotWalkQuery(t);logger.debug({filteredWhere:r}),r=this.helperUtility.objectFilter(r,(e,t)=>!e.startsWith("!")&&("__exists__"!==e&&("object"!=typeof t||null===t)));for(const[t,o]of Object.entries(r)){const{joinType:r="AND",column:i}=this.parseWhereColumn(t),{operator:s,value:n}=this.parseWhereValue(o);"AND"===r?this._applyAndWhereCondition(e,i,s,n):this._applyOrWhereCondition(e,i,s,n)}}this._applyNestedWhere(e,t,r)}}_getNestedWhereConditions(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,o={};for(const[i,s]of Object.entries(e))if(i.startsWith(r)){o[i.slice(r.length)]=s}else i===t&&!0===s&&(o.__exists__=!0);return o}_getTopLevelRelationsFromWhere(e){if(!e||"object"!=typeof e)return[];const t=new Set;for(const r of Object.keys(e))if(r.includes(".")){const e=r.split(".")[0];t.add(e)}else r.startsWith("!")&&t.add(r);return[...t]}_applyNestedWhere(e,t,r){const o=this,i=e._getMyModel(),s=this._getTopLevelRelationsFromWhere(t);if(0!==s.length)for(const r of s){const s=r.startsWith("!"),n=s?r.slice(1):r,l=this._getNestedWhereConditions(t,r);if(0===Object.keys(l).length)continue;const a=i.hasRelations?.[n];if(!a){logger.warn(`Relation ${n} not found in model ${i.modelName}`);continue}let h;try{h=this.utils.getModel(this.controllerWrapper,a.table||n)}catch(e){try{h=this.utils.getModel(this.controllerWrapper,n)}catch(e){logger.warn(`Model for relation ${n} (table: ${a.table}) not found`);continue}}e[s?"whereNotExists":"whereExists"](function(){const e=o.getQueryBuilder(h,this.select("*").from(a.table));e.whereRaw(`"${a.table}"."${a.foreignKey}" = "${i.table}"."${a.localKey}"`);if(!(!0===l.__exists__&&1===Object.keys(l).length)){const t={...l};delete t.__exists__,o._applyWhereClause(e,t,[])}})}}_applyJoins(e,t,r){const o=Array.isArray(t)?t:[t];for(const t of o)"string"==typeof t?e[r](t):"object"==typeof t&&(t.table&&t.on?e[r](t.table,t.on):t.table&&t.first&&t.operator&&t.second&&e[r](t.table,t.first,t.operator,t.second))}_applyWithWhere(e,t){try{if(Array.isArray(t))for(const r of t)"string"==typeof r?e.withWhere(r):"object"==typeof r&&e.withWhere(r.column,r.operator,r.value);else if("object"==typeof t)for(const[r,o]of Object.entries(t))e.withWhere(r,o)}catch(e){logger.warn("Failed to apply withWhere:",e.message)}}_applyHavingClause(e,t){for(const[r,o]of Object.entries(t))"object"==typeof o&&o.operator?e.having(r,o.operator,o.value):e.having(r,o)}_applyOrderBy(e,t){if(Array.isArray(t))for(const r of t)"string"==typeof r?e.orderBy(r):"object"==typeof r&&e.orderBy(r.column,r.direction||"asc");else"string"==typeof t?e.orderBy(t):"object"==typeof t&&e.orderBy(t.column,t.direction||"asc")}}module.exports=QueryBuilder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const QueryBuilder=require("./QueryBuilder");class QueryService{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.queryBuilder=new QueryBuilder(e,t,r)}async getQuery(e,t){return await this.queryBuilder.getQuery(e,t)}async getSoftDeleteQuery(e,t){return await this.queryBuilder.getQuery(e,{...t,where:{...t.where||{},deleted_at:null}})}async executeShowQuery(e,t){
|
|
1
|
+
const QueryBuilder=require("./QueryBuilder");class QueryService{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.queryBuilder=new QueryBuilder(e,t,r)}async getQuery(e,t){return await this.queryBuilder.getQuery(e,t)}async getSoftDeleteQuery(e,t){return await this.queryBuilder.getQuery(e,{...t,where:{...t.where||{},deleted_at:null}})}async executeShowQuery(e,t){const r=await this.getQuery(e,{...t,limit:1,offset:0});return r.data.length>0?r.data[0]:null}async executeCountQuery(e,t){const r=await this.db(e.table).count();return Object.values(r[0])[0]}async executeSumQuery(e,t){const r=await this.queryBuilder.getSumQuery(e,t).first(),a=r&&null!=r.sum?r.sum:0;return Number(a)}async executeCreateQuery(e,t){return await this.db(e.table).insert(t.data).returning("*")}async executeUpdateQuery(e,t){return await this.db(e.table).where(t.where).update(t.data).returning("*")}async executeDeleteQuery(e,t){return await this.db(e.table).where(t.where).delete()}async executeSoftDeleteQuery(e,t){return await this.db(e.table).where(t.where).update({deleted_at:new Date}).returning("*")}async executeUpsertQuery(e,t){return await this.db(e.table).insert(t.data).onConflict(t.conflict).update(t.data)}async executeReplaceQuery(e,t){return await this.db(e.table).replace(t.data)}async executeSyncQuery(e,t){return{insertOrUpdateQuery:await this.db(e.table).insert(t.data).onConflict(t.conflict).update(t.data),deleteQuery:await this.db(e.table).where(t.where).delete()}}}module.exports=QueryService;
|
package/clients/pg/SyncTable.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
+
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 s=`"${e}"`;const l=i.type,c=i.size??this.utils.getDefaultTypeSize(l);if(c&&["character varying","varchar","char","character"].includes(l)?s+=` ${l}(${c})`:s+="numeric"===l&&c?` ${l}(${c})`:` ${l}`,i.autoIncrement&&("integer"===l||"int4"===l?s=`"${e}" SERIAL`:"bigint"!==l&&"int8"!==l||(s=`"${e}" BIGSERIAL`)),i.primary&&["CREATE","ADD_COLUMN"].includes(a)&&(s+=" PRIMARY KEY"),!1===i.nullable||void 0===i.nullable?s+=" NOT NULL":!0===i.nullable&&(s+=" NULL"),i.unique&&(s+=" UNIQUE"),void 0!==i.default&&null!==i.default&&!i.primary&&!i.autoIncrement){s+=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];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,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],s=`fk_${t}_${e}_${a}_${i}`;o.push(`CONSTRAINT "${s}" 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){const 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){const 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],o=a.type,i=a.size,s=a.isUnsigned,l=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}`),s&&(t[n]+="|unsigned"),l&&(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){const t=await this.getCurrentColumns(e),n=Object.keys(t).filter(e=>t[e].hasForeignKey).reduce((e,n)=>{const a=t[n];if(!a)return e;if(0===a.foreignMapTables?.length)return e;const o=a.foreignMapTables;for(const t of o){const 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){const 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
|
-
class BaseUtility{getModel(e,t){
|
|
1
|
+
class BaseUtility{getModel(e,t){const n=e?.schema;let r=null;const i=Object.keys(n);if(i.forEach(e=>{e===t&&(r=n[e])}),r||i.forEach(e=>{const i=n[e];i.table===t&&(r=i)}),r)return{...r,name:t,columns:Object.keys(r.columns).map(e=>{const t=r.columns[e];return this.parseColumnString(e,t)})};throw new Error(`Model ${t} not found`)}getDefaultTypeSize(e,t="toUpperCase"){let n=e;switch(n?.[t]&&"function"==typeof n[t]&&(n=n[t]()),n){case"VARCHAR":return 255;case"TINYINT":return 1;case"INT":return 11;default:return 0}}map2DbType(e,t="toLowerCase"){let n;switch(e){case"number":n="INT";break;case"string":default:n="VARCHAR";break;case"boolean":n="TINYINT";break;case"date":n="DATETIME";break;case"json":n="JSON"}return n?.[t]&&"function"==typeof n[t]&&(n=n[t]()),n}map2DbDefault(e){return"now"===e?"CURRENT_TIMESTAMP":e}getCollenedValue(e,{splitChar:t=":",give:n="right",trimWrap:r="{}"}={}){if(null==e)return null;const i=String(e),l=i.indexOf(t),a=e=>{if(!r)return e;if("string"==typeof r&&2===r.length){const[t,n]=r;return e.startsWith(t)&&e.endsWith(n)?e.slice(1,-1):e}return"string"==typeof r&&r.length&&e.startsWith(r)&&e.endsWith(r)?e.slice(r.length,-r.length):e};if(l>=0){const e=i.slice(0,l).trim(),r=i.slice(l+t.length).trim();return"both"===n?{left:a(e),right:a(r)}:a("left"===n?e:r)}return a(i)}parseForeignKey(e){if(!e)return{foreignMapTables:[]};const t=this.getCollenedValue(e),[n,r="id"]=String(t||"").split(":"),i=(this.getCollenedValue(n)||"").split(",").filter(Boolean),l=(this.getCollenedValue(r)||"").split(",").filter(Boolean);return{foreignMapTables:i.map((e,t)=>({table:e,column:l[t]||l[0]||"id"}))}}parseColumnString(e,t){const[n,...r]=String(t).split("|"),i=e=>r.find(t=>t.startsWith(e+":")),l=e=>r.join("|").includes(e),a=e=>e&&e.includes(":")?e.slice(e.indexOf(":")+1):null,s=i("default"),o=i("onUpdate"),u=i("comment"),c=i("foreignKey"),m=i("size"),p=this.parseForeignKey(c),g=!!c,f=this.map2DbType(n),E=m?a(m):this.getDefaultTypeSize(f);return{name:e,type:f,size:E,isUnsigned:l("unsigned")||l("primaryKey")||g,columnType:`${n}${E?`(${E})`:""}`,nullable:!l("notNull"),primary:l("primaryKey"),autoIncrement:l("autoIncrement"),unique:l("unique"),default:this.map2DbDefault(a(s)),onUpdate:this.map2DbDefault(a(o)),comment:a(u)||"",hasForeignKey:g,...p}}formatColumnSchema(e,t){return this.parseColumnString(e,t)}formatColumnDef(e,t){return{name:e,type:t.DATA_TYPE,size:t.CHARACTER_MAXIMUM_LENGTH||this.getDefaultTypeSize(t.DATA_TYPE),isUnsigned:String(t.COLUMN_TYPE||"").toLowerCase().includes("unsigned"),columnType:t.COLUMN_TYPE,nullable:"YES"===t.IS_NULLABLE,primary:"PRI"===t.COLUMN_KEY,unique:"UNI"===t.COLUMN_KEY,autoIncrement:String(t.EXTRA||"").toLowerCase().includes("auto_increment"),hasForeignKey:"MUL"===t.COLUMN_KEY,comment:t.COMMENT,default:t.COLUMN_DEFAULT,onUpdate:((e="")=>{const t=String(e).match(/on update\s+([a-zA-Z_]+)/i);return t?t[1]:null})(t.EXTRA),foreignMapTables:"MUL"===t.COLUMN_KEY&&t.REFERENCED_TABLE_NAME?[{table:t.REFERENCED_TABLE_NAME,column:t.REFERENCED_COLUMN_NAME}]:[]}}escapeComment(e){return null==e?"":String(e).replace(/'/g,"\\'")}}module.exports=BaseUtility;
|
|
@@ -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={}){const o=this.controllerWrapper
|
|
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,s=this.utils.getModel(this.controllerWrapper,r),c=e?.action||"list";let a=null;const i={model:s,action:c,request:e,ctx:t,controller:o};switch(this.hookService?.executeValidatorHook&&await this.hookService.executeValidatorHook({...i}),this.hookService?.executeBeforeHook&&(e.beforeActionData=await this.hookService.executeBeforeHook({...i})),c){case"count":a=await this.queryService.executeCountQuery(s,e);break;case"sum":a=await this.queryService.executeSumQuery(s,e);break;case"list":a=await this.hookService.executeHasSoftDeleteHook(s)?await this.queryService.getSoftDeleteQuery(s,e):await this.queryService.getQuery(s,e);break;case"show":a=await this.queryService.executeShowQuery(s,e);break;case"create":a=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}`);a={message:"Record updated successfully",data:r,success:!0};break}case"replace":if(a=await this.queryService.executeReplaceQuery(s,e),!a)throw new Error(`Record not found or not replaced: ${r} returned ${a}`);a={message:"Record replaced successfully",data:a,success:!0};break;case"upsert":if(a=await this.queryService.executeUpsertQuery(s,e),!a)throw new Error(`Record not found or not upserted: ${r} returned ${a}`);a={message:"Record upserted successfully",data:a,success:!0};break;case"sync":if(a=await this.queryService.executeSyncQuery(s,e),!a)throw new Error(`Record not found or not synced: ${r} returned ${a}`);a={message:"Record synced successfully",data:a,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}`);a={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.`);a=await this.hookService.executeCustomAction({...i})}if(this.hookService?.executeAfterHook&&(a=await this.hookService.executeAfterHook({...i,data:a})),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);a.other_responses=r}return a}}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.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){
|
|
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){const t="string"==typeof e?e:e.modelName||e.name,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"),logger=require("../../Logger");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getHookService(){return this.controllerWrapper.hookService}getQueryBuilder(e,t=null){let r=this.db(e.table);return t&&(r=t),r._getMyModel=()=>e,r}parseValue(e){return this.helperUtility.parseValue(e)}parseWhereValue(e){return this.helperUtility.parseWhereValue(e)}parseWhereColumn(e){return this.helperUtility.parseWhereColumn(e)}_applyOrWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.orWhereIn(t,o);else switch(r){case"between":e.orWhereBetween(t,o);break;case"notBetween":e.orWhereNotBetween(t,o);break;case"in":e.orWhereIn(t,o);break;case"notIn":e.orWhereNotIn(t,o);break;case"like":e.orWhere(t,"like",o);break;default:e.orWhere(t,r,o)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.whereIn(t,o);else switch(r){case"between":e.whereBetween(t,o);break;case"notBetween":e.whereNotBetween(t,o);break;case"in":e.whereIn(t,o);break;case"notIn":e.whereNotIn(t,o);break;case"like":e.where(t,"like",o);break;default:e.where(t,r,o)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:o})=>t?t({current:e,part:r,source:o}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{direct:{},nested:{}};const r=`${t}.`,o={},i={};for(const[t,s]of Object.entries(e))if(t.startsWith(r)){const e=t.slice(r.length);e.includes(".")?i[e]=s:o[e]=s}return{direct:o,nested:i}}_applyWithWhereConditions(e,t){for(const[r,o]of Object.entries(t)){const{joinType:t="AND",column:i}=this.parseWhereColumn(r),{operator:s,value:n}=this.parseWhereValue(o);"AND"===t?this._applyAndWhereCondition(e,i,s,n):this._applyOrWhereCondition(e,i,s,n)}}async fetchRelatedRows(e,t,r={}){if(e.through){
|
|
1
|
+
const HelperUtility=require("./HelperUtility"),logger=require("../../Logger");class QueryBuilder{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.helperUtility=new HelperUtility}getHookService(){return this.controllerWrapper.hookService}getQueryBuilder(e,t=null){let r=this.db(e.table);return t&&(r=t),r._getMyModel=()=>e,r}parseValue(e){return this.helperUtility.parseValue(e)}parseWhereValue(e){return this.helperUtility.parseWhereValue(e)}parseWhereColumn(e){return this.helperUtility.parseWhereColumn(e)}_applyOrWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.orWhereIn(t,o);else switch(r){case"between":e.orWhereBetween(t,o);break;case"notBetween":e.orWhereNotBetween(t,o);break;case"in":e.orWhereIn(t,o);break;case"notIn":e.orWhereNotIn(t,o);break;case"like":e.orWhere(t,"like",o);break;default:e.orWhere(t,r,o)}else e.orWhereNull(t)}_applyAndWhereCondition(e,t,r,o){if(null!==o)if(Array.isArray(o))e.whereIn(t,o);else switch(r){case"between":e.whereBetween(t,o);break;case"notBetween":e.whereNotBetween(t,o);break;case"in":e.whereIn(t,o);break;case"notIn":e.whereNotIn(t,o);break;case"like":e.where(t,"like",o);break;default:e.where(t,r,o)}else e.whereNull(t)}buildWithTree(e,t=null){return this.helperUtility.dotWalkTree(e,{resolver:({current:e,part:r,source:o})=>t?t({current:e,part:r,source:o}):{}})}_getWithWhereForRelation(e,t){if(!e||"object"!=typeof e)return{direct:{},nested:{}};const r=`${t}.`,o={},i={};for(const[t,s]of Object.entries(e))if(t.startsWith(r)){const e=t.slice(r.length);e.includes(".")?i[e]=s:o[e]=s}return{direct:o,nested:i}}_applyWithWhereConditions(e,t){for(const[r,o]of Object.entries(t)){const{joinType:t="AND",column:i}=this.parseWhereColumn(r),{operator:s,value:n}=this.parseWhereValue(o);"AND"===t?this._applyAndWhereCondition(e,i,s,n):this._applyOrWhereCondition(e,i,s,n)}}async fetchRelatedRows(e,t,r={}){if(e.through){const o=await this.db(e.through).whereIn(e.throughLocalKey,t),i=this.db(e.table).whereIn(e.foreignKey,o.map(t=>t[e.throughForeignKey]));return Object.keys(r).length>0&&this._applyWithWhereConditions(i,r),i}{const o=this.db(e.table).whereIn(e.foreignKey,t);return Object.keys(r).length>0&&this._applyWithWhereConditions(o,r),o}}async fetchAndAttachRelated(e){const{parentRows:t,relName:r,model:o,withTree:i,relation:s,withWhere:n={}}=e;if(!s){const e=this.getHookService().getModelInstance(o),s=`get${r.charAt(0).toUpperCase()+r.slice(1)}Relation`;if("function"==typeof e[s]){const{direct:l,nested:a}=this._getWithWhereForRelation(n,r),h={rows:t,relName:r,model:o,withTree:i,controller:this.controllerWrapper,relation:o.hasRelations[r],qb:this,db:this.db,withWhere:l,nestedWithWhere:a};await e[s](h)}else logger.warn(`Method ${s} not found in model ${o.name}`);return t}const l=t.map(e=>e[s.localKey]),a="one"===s?.type;let h=[];const{direct:c,nested:p}=this._getWithWhereForRelation(n,r);let u={...c};try{const e=this.utils.getModel(this.controllerWrapper,r);if(this.controllerWrapper?.hookService){await this.controllerWrapper.hookService.executeHasSoftDeleteHook(e)&&(u={...u,deleted_at:null})}}catch(e){}h=await this.fetchRelatedRows(s,l,u);const y=new Map;for(const e of h){const t=e[s.foreignKey];y.has(t)||y.set(t,[]),y.get(t).push(e)}for(const e of t){const t=e[s.localKey];a&&1==y.get(t)?.length?e[r]=y.get(t)[0]:e[r]=y.get(t)||[]}const d=Object.keys(i);for(const e of d){const o=i[e],s=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:s,relName:e,model:n,withTree:o,relation:n.hasRelations[e],withWhere:p})}return t}filterWhere(e,t=""){return t?Object.keys(e).filter(e=>e.includes(t)).reduce((r,o)=>(r[o.replace(t,"")]=e[o],r),{}):Object.keys(e).filter(e=>!e.includes(".")).reduce((t,r)=>(t[r]=e[r],t),{})}async getQuery(e,t){try{const{where:r={},with:o,withWhere:i,select:s,orderBy:n={column:"id",direction:"asc"},limit:l=10,offset:a=0,page:h,groupBy:c,having:p,distinct:u,join:y,leftJoin:d,rightJoin:f,innerJoin:g,count:W=!1}=t,w=this.getQueryBuilder(e);s&&(Array.isArray(s)||"string"==typeof s)?w.select(s):w.select("*"),u&&(Array.isArray(u)||"string"==typeof u?w.distinct(u):w.distinct()),y&&this._applyJoins(w,y,"join"),d&&this._applyJoins(w,d,"leftJoin"),f&&this._applyJoins(w,f,"rightJoin"),g&&this._applyJoins(w,g,"innerJoin"),this._applyWhereClause(w,r,o),c&&(Array.isArray(c),w.groupBy(c)),p&&this._applyHavingClause(w,p),n&&this._applyOrderBy(w,n);let m=!1;const b=l;let _=a,A=1,j=0;h&&l>0&&(A=Math.max(1,parseInt(h)),_=(A-1)*l),l>0&&(w.limit(b),_>0&&w.offset(_));const k=[],C=w.toSQL();k.push(C.sql);const J=await w;if(o&&o.length>0){const t=this.buildWithTree(o);for(const r of Object.keys(t))await this.fetchAndAttachRelated({parentRows:J,relName:r,model:e,withTree:t[r],relation:e.hasRelations[r],withWhere:i||{}})}let B=null;if(l>0)try{const t=this.getQueryBuilder(e);r&&Object.keys(r).length>0&&this._applyWhereClause(t,r),y&&this._applyJoins(t,y,"join"),d&&this._applyJoins(t,d,"leftJoin"),f&&this._applyJoins(t,f,"rightJoin"),g&&this._applyJoins(t,g,"innerJoin");const o=t.count("* as cnt");k.push(o.toSQL().sql);B=(await o.first()).cnt}catch(e){logger.warn("Failed to get total count:",e.message),B=J.length}l>0&&null!==B&&(j=Math.ceil(B/l),m=A<j);const N=m?A+1:null,O=A>1?A-1:null;return{data:J,totalCount:B,...this.controllerWrapper.debug?{sqlDebug:k}:{},...l>0?{pagination:{page:A,limit:b,offset:_,totalPages:j,hasNext:m,hasPrev:A>1,nextPage:N,prevPage:O}}:{}}}catch(t){throw logger.error("QueryService.getQuery error:",t),new Error(`Failed to execute query: ${t.message} on model ${e.name}`)}}getSumQuery(e,t){const{where:r={},join:o,leftJoin:i,rightJoin:s,innerJoin:n}=t,l=t.data||t,a=l.sumColumn,h=l.sumFormula;if(!a&&!h)throw new Error("Sum action requires either data.sumColumn or data.sumFormula");const c=e=>'"'+String(e).replace(/["\\]/g,"")+'"';let p;if(a){const e=String(a).trim().replace(/[^a-zA-Z0-9_]/g,"");if(!e)throw new Error("data.sumColumn must be a valid column name");p="SUM("+c(e)+")"}else{const e=String(h).trim();if(!/\{[a-zA-Z0-9_]+\}/.test(e))throw new Error("data.sumFormula must contain at least one {columnName}");let t=0;for(const r of e)if("("===r)t++;else if(")"===r&&(t--,t<0))break;if(0!==t)throw new Error("data.sumFormula has unbalanced or misordered parentheses ( and )");const r=e.replace(/\{[a-zA-Z0-9_]+\}/g,"@");if(!/^[\s0-9.+*\/\-()@]+$/.test(r))throw new Error("data.sumFormula may only use BODMAS: numbers, + - * /, parentheses, and {columnName}");p="SUM("+e.replace(/\{([a-zA-Z0-9_]+)\}/g,(e,t)=>c(t))+")"}const u=this.getQueryBuilder(e);return o&&this._applyJoins(u,o,"join"),i&&this._applyJoins(u,i,"leftJoin"),s&&this._applyJoins(u,s,"rightJoin"),n&&this._applyJoins(u,n,"innerJoin"),this._applyWhereClause(u,r,[]),u.select(this.db.raw(p+" as sum")),u}_applyWhereWithArray(e,t,r){for(const o of t)this._applyWhereClause(e,o,r)}_applyWhereClause(e,t,r=[]){if(Array.isArray(t))this._applyWhereWithArray(e,t,r);else{if(t&&Object.keys(t).length>0){let r=this.helperUtility.getDotWalkQuery(t);r=this.helperUtility.objectFilter(r,(e,t)=>!e.startsWith("!")&&("__exists__"!==e&&("object"!=typeof t||null===t)));for(const[t,o]of Object.entries(r)){const{joinType:r="AND",column:i}=this.parseWhereColumn(t),{operator:s,value:n}=this.parseWhereValue(o);"AND"===r?this._applyAndWhereCondition(e,i,s,n):this._applyOrWhereCondition(e,i,s,n)}}this._applyNestedWhere(e,t,r)}}_getNestedWhereConditions(e,t){if(!e||"object"!=typeof e)return{};const r=`${t}.`,o={};for(const[i,s]of Object.entries(e))if(i.startsWith(r)){o[i.slice(r.length)]=s}else i===t&&!0===s&&(o.__exists__=!0);return o}_getTopLevelRelationsFromWhere(e){if(!e||"object"!=typeof e)return[];const t=new Set;for(const r of Object.keys(e))if(r.includes(".")){const e=r.split(".")[0];t.add(e)}else r.startsWith("!")&&t.add(r);return[...t]}_applyNestedWhere(e,t,r){const o=this,i=e._getMyModel(),s=this._getTopLevelRelationsFromWhere(t);if(0!==s.length)for(const r of s){const s=r.startsWith("!"),n=s?r.slice(1):r,l=this._getNestedWhereConditions(t,r);if(0===Object.keys(l).length)continue;const a=i.hasRelations?.[n];if(!a){logger.warn(`Relation ${n} not found in model ${i.modelName}`);continue}let h;try{h=this.utils.getModel(this.controllerWrapper,a.table||n)}catch(e){try{h=this.utils.getModel(this.controllerWrapper,n)}catch(e){logger.warn(`Model for relation ${n} (table: ${a.table}) not found`);continue}}e[s?"whereNotExists":"whereExists"](function(){const e=o.getQueryBuilder(h,this.select("*").from(a.table));e.whereRaw(`"${a.table}"."${a.foreignKey}" = "${i.table}"."${a.localKey}"`);if(!(!0===l.__exists__&&1===Object.keys(l).length)){const t={...l};delete t.__exists__,o._applyWhereClause(e,t,[])}})}}_applyJoins(e,t,r){const o=Array.isArray(t)?t:[t];for(const t of o)"string"==typeof t?e[r](t):"object"==typeof t&&(t.table&&t.on?e[r](t.table,t.on):t.table&&t.first&&t.operator&&t.second&&e[r](t.table,t.first,t.operator,t.second))}_applyWithWhere(e,t){try{if(Array.isArray(t))for(const r of t)"string"==typeof r?e.withWhere(r):"object"==typeof r&&e.withWhere(r.column,r.operator,r.value);else if("object"==typeof t)for(const[r,o]of Object.entries(t))e.withWhere(r,o)}catch(e){logger.warn("Failed to apply withWhere:",e.message)}}_applyHavingClause(e,t){for(const[r,o]of Object.entries(t))"object"==typeof o&&o.operator?e.having(r,o.operator,o.value):e.having(r,o)}_applyOrderBy(e,t){if(Array.isArray(t))for(const r of t)"string"==typeof r?e.orderBy(r):"object"==typeof r&&e.orderBy(r.column,r.direction||"asc");else"string"==typeof t?e.orderBy(t):"object"==typeof t&&e.orderBy(t.column,t.direction||"asc")}}module.exports=QueryBuilder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const QueryBuilder=require("./QueryBuilder");class QueryService{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.queryBuilder=new QueryBuilder(e,t,r)}async getQuery(e,t){return await this.queryBuilder.getQuery(e,t)}async getSoftDeleteQuery(e,t){return await this.queryBuilder.getQuery(e,{...t,where:{...t.where||{},deleted_at:null}})}async executeShowQuery(e,t){
|
|
1
|
+
const QueryBuilder=require("./QueryBuilder");class QueryService{constructor(e,t,r=null){this.db=e,this.utils=t,this.controllerWrapper=r,this.queryBuilder=new QueryBuilder(e,t,r)}async getQuery(e,t){return await this.queryBuilder.getQuery(e,t)}async getSoftDeleteQuery(e,t){return await this.queryBuilder.getQuery(e,{...t,where:{...t.where||{},deleted_at:null}})}async executeShowQuery(e,t){const r=await this.getQuery(e,{...t,limit:1,offset:0});return r.data.length>0?r.data[0]:null}async executeCountQuery(e,t){const r=await this.db(e.table).count();return Object.values(r[0])[0]}async executeSumQuery(e,t){const r=await this.queryBuilder.getSumQuery(e,t).first(),a=r&&null!=r.sum?r.sum:0;return Number(a)}async executeCreateQuery(e,t){return await this.db(e.table).insert(t.data).returning("*")}async executeUpdateQuery(e,t){return await this.db(e.table).where(t.where).update(t.data).returning("*")}async executeDeleteQuery(e,t){return await this.db(e.table).where(t.where).delete()}async executeSoftDeleteQuery(e,t){return await this.db(e.table).where(t.where).update({deleted_at:new Date}).returning("*")}async executeUpsertQuery(e,t){return await this.db(e.table).insert(t.data).onConflict(t.conflict).update(t.data)}async executeReplaceQuery(e,t){return await this.db(e.table).replace(t.data)}async executeSyncQuery(e,t){return{insertOrUpdateQuery:await this.db(e.table).insert(t.data).onConflict(t.conflict).update(t.data),deleteQuery:await this.db(e.table).where(t.where).delete()}}}module.exports=QueryService;
|
|
@@ -1 +1 @@
|
|
|
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){
|
|
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){const 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],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){const 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;
|