@dreamtree-org/korm-js 1.1.3 → 1.2.0

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.
@@ -1 +1 @@
1
- const mysqlWrapper=require("./clients/mysql"),sqliteWrapper=require("./clients/sqlite"),pgWrapper=require("./clients/pg"),KormError=require("./KormError"),AuthorizationService=require("./AuthorizationService"),{buildModelRequestSchema:buildModelRequestSchema}=require("./requestSchema"),{buildModelDescription:buildModelDescription,SCHEMA_API_VERSION:SCHEMA_API_VERSION}=require("./schemaDescribe"),InstanceMapper={mysql2:mysqlWrapper,sqlite:sqliteWrapper,pg:pgWrapper},dbClientMapper={mysql2:"mysql2",mysql:"mysql2",pg:"pg",postgresql:"pg",sqlite:"sqlite",sqlite3:"sqlite"};class ControllerWrapper{db=null;dbClient=null;dbClientClass=null;schema=null;resolverPath=null;dbInstance=null;debug=!1;_authz=new AuthorizationService;requestInstance=null;constructor({db:e,dbClient:t,schema:s,resolverPath:r=null,debug:n=!1}={}){this.requestInstance={},this.db=e,this.dbClient=t,this.schema=s,this.resolverPath=r,this.debug=n;const i=dbClientMapper[t];if(!i)throw new Error(`Database client ${t} not found`);const l=InstanceMapper[i];if(!l)throw new Error(`Database client ${t} not found`);this.dbClientClass=l,this.dbInstance=new l(this)}static initializeKORM(e){return new ControllerWrapper(e)}setSchema(e){this.schema=e;const t=this.dbClientClass;if(!t)throw new Error(`Database client ${this.dbClient} not found`);return this.dbInstance=new t(this),this}_resolveModelName(e){const t=this.schema||{};if(t[e])return e;return Object.keys(t).find(s=>t[s]&&t[s].table===e)||e}authorize(e,t,s){return this._authz.registerAuthorize(this._resolveModelName(e),t,s),this}scope(e,t){return this._authz.registerScope(this._resolveModelName(e),t),this}resetAuthorization(){return this._authz.reset(),this}async processRequest(e,t=null,s=null){let r=e;if(this._authz.hasRules()&&t){const n=this._resolveModelName(t),i=e&&e.action||"list";this._authz.enforce(n,i,e,s),r=this._authz.applyScope(n,i,e||{},s)}return await this.dbInstance.processRequest(r,t,s)}async processRequestWithOthers(e,t=null,s=null){return await this.dbInstance.processRequest(e,t,s)}async syncDatabase(e={}){return await this.dbInstance.syncDatabase(e)}async generateSchema(){return await this.dbInstance.generateSchema()}loadModelClass(e){return this.dbInstance.hookService.loadModelClass(e)}getModelInstance(e){return this.dbInstance.hookService.getModelInstance(e)}getRequestJsonSchema(e){const t=this.schema||{},s=t[e]||Object.values(t).find(t=>t&&t.table===e);if(!s)throw KormError.unknownModel({model:e,available:Object.keys(t)});return buildModelRequestSchema(s,{title:`KormRequest<${e}>`})}_modelHasSoftDelete(e){try{const t=this.dbInstance?.hookService?.getModelInstance?.(e);return!(!t||!0!==t.hasSoftDelete)}catch{return!1}}describeModel(e,t=null){const s=this.schema||{},r=Object.entries(s).find(([t,s])=>t===e||s&&s.table===e);if(!r)throw KormError.unknownModel({model:e,available:Object.keys(s)});const[n,i]=r,l=buildModelDescription(n,i,{softDelete:this._modelHasSoftDelete(n)});return null!=t&&this._authz.hasRules()&&(l.actions=this._authz.availableActions(n,l.actions,t)),l}describeSchema(){const e=this.schema||{},t=Object.entries(e).map(([e,t])=>buildModelDescription(e,t,{softDelete:this._modelHasSoftDelete(e)}));return{schemaApiVersion:SCHEMA_API_VERSION,models:t}}}module.exports=ControllerWrapper;
1
+ const fs=require("fs"),https=require("https"),http=require("http"),path=require("path"),{pathToFileURL:pathToFileURL}=require("url"),mysqlWrapper=require("./clients/mysql"),sqliteWrapper=require("./clients/sqlite"),pgWrapper=require("./clients/pg"),KormError=require("./KormError"),AuthorizationService=require("./AuthorizationService"),{buildModelRequestSchema:buildModelRequestSchema}=require("./requestSchema"),{buildModelDescription:buildModelDescription,SCHEMA_API_VERSION:SCHEMA_API_VERSION}=require("./schemaDescribe"),InstanceMapper={mysql2:mysqlWrapper,sqlite:sqliteWrapper,pg:pgWrapper},dbClientMapper={mysql2:"mysql2",mysql:"mysql2",pg:"pg",postgresql:"pg",sqlite:"sqlite",sqlite3:"sqlite"};class ControllerWrapper{db=null;dbClient=null;dbClientClass=null;schema=null;resolverPath=null;dbInstance=null;debug=!1;_authz=new AuthorizationService;requestInstance=null;constructor({db:e,dbClient:t,schema:r,resolverPath:s=null,debug:a=!1}={}){this.requestInstance={},this.db=e,this.dbClient=t,this.schema=r,this.resolverPath=s,this.debug=a;const n=dbClientMapper[t];if(!n)throw new Error(`Database client ${t} not found`);const o=InstanceMapper[n];if(!o)throw new Error(`Database client ${t} not found`);this.dbClientClass=o,this.dbInstance=new o(this)}static async _loadSchema(e){if("string"!=typeof e)return e;if(/^https?:\/\//i.test(e))return new Promise((t,r)=>{(e.startsWith("https")?https:http).get(e,s=>{if(s.statusCode<200||s.statusCode>=300)return void r(new KormError(`Schema URL returned ${s.statusCode}: ${e}`,{code:KormError.CODES.INTERNAL,context:{schemaSource:e,statusCode:s.statusCode}}));const a=[];s.on("data",e=>a.push(e)),s.on("end",()=>{try{t(JSON.parse(Buffer.concat(a).toString("utf8")))}catch(t){r(new KormError(`Schema URL returned invalid JSON: ${e}`,{code:KormError.CODES.INTERNAL,context:{schemaSource:e}}))}})}).on("error",t=>r(new KormError(`Schema URL fetch failed (${t.message}): ${e}`,{code:KormError.CODES.INTERNAL,context:{schemaSource:e}})))});const t=path.extname(e).toLowerCase();if(".js"===t)return require(path.resolve(e));if(".mjs"===t)return import(pathToFileURL(path.resolve(e)).href);const r=fs.readFileSync(path.resolve(e),"utf8");return JSON.parse(r)}static async initializeKORM(e){const t={...e};return"string"==typeof e.schema&&(t.schema=await ControllerWrapper._loadSchema(e.schema)),new ControllerWrapper(t)}setSchema(e){this.schema=e;const t=this.dbClientClass;if(!t)throw new Error(`Database client ${this.dbClient} not found`);return this.dbInstance=new t(this),this}_resolveModelName(e){const t=this.schema||{};if(t[e])return e;return Object.keys(t).find(r=>t[r]&&t[r].table===e)||e}authorize(e,t,r){return this._authz.registerAuthorize(this._resolveModelName(e),t,r),this}scope(e,t){return this._authz.registerScope(this._resolveModelName(e),t),this}resetAuthorization(){return this._authz.reset(),this}async processRequest(e,t=null,r=null){let s=e;if(this._authz.hasRules()&&t){const a=this._resolveModelName(t),n=e&&e.action||"list";this._authz.enforce(a,n,e,r),s=this._authz.applyScope(a,n,e||{},r)}return await this.dbInstance.processRequest(s,t,r)}async processRequestWithOthers(e,t=null,r=null){return await this.dbInstance.processRequest(e,t,r)}async syncDatabase(e={}){return await this.dbInstance.syncDatabase(e)}async generateSchema(){return await this.dbInstance.generateSchema()}loadModelClass(e){return this.dbInstance.hookService.loadModelClass(e)}getModelInstance(e){return this.dbInstance.hookService.getModelInstance(e)}getRequestJsonSchema(e){const t=this.schema||{},r=t[e]||Object.values(t).find(t=>t&&t.table===e);if(!r)throw KormError.unknownModel({model:e,available:Object.keys(t)});return buildModelRequestSchema(r,{title:`KormRequest<${e}>`})}_modelHasSoftDelete(e){try{const t=this.dbInstance?.hookService?.getModelInstance?.(e);return!(!t||!0!==t.hasSoftDelete)}catch{return!1}}describeModel(e,t=null){const r=this.schema||{},s=Object.entries(r).find(([t,r])=>t===e||r&&r.table===e);if(!s)throw KormError.unknownModel({model:e,available:Object.keys(r)});const[a,n]=s,o=buildModelDescription(a,n,{softDelete:this._modelHasSoftDelete(a)});return null!=t&&this._authz.hasRules()&&(o.actions=this._authz.availableActions(a,o.actions,t)),o}describeSchema(){const e=this.schema||{},t=Object.entries(e).map(([e,t])=>buildModelDescription(e,t,{softDelete:this._modelHasSoftDelete(e)}));return{schemaApiVersion:SCHEMA_API_VERSION,models:t}}}module.exports=ControllerWrapper;
package/README.md CHANGED
@@ -1753,14 +1753,26 @@ Seed data is automatically inserted when `syncDatabase()` is called and the tabl
1753
1753
  ```javascript
1754
1754
  const { initializeKORM } = require('@dreamtree-org/korm-js');
1755
1755
 
1756
- const korm = initializeKORM({
1756
+ // Schema as an inline object (existing behaviour):
1757
+ const korm = await initializeKORM({
1757
1758
  db: db, // Knex database instance
1758
1759
  dbClient: 'mysql', // 'mysql', 'mysql2', 'pg', 'postgresql', 'sqlite', 'sqlite3'
1759
- schema: null, // Optional: initial schema object
1760
+ schema: null, // Optional: schema object, or a string (file path / URL — see below)
1760
1761
  resolverPath: null, // Optional: path to models directory (default: process.cwd())
1761
1762
  debug: false, // Optional: enable SQL debugging (default: false)
1762
1763
  });
1763
1764
 
1765
+ // Schema auto-resolved from a string (path or URL):
1766
+ // "./schema.json" → read & JSON.parse
1767
+ // "./schema.js" → require() (CJS: module.exports = {…})
1768
+ // "./schema.mjs" → dynamic import() (ESM: export default {…})
1769
+ // "https://api.example.com/schema" → fetch & JSON.parse
1770
+ const korm2 = await initializeKORM({
1771
+ db, dbClient: 'sqlite',
1772
+ schema: './schema.json',
1773
+ });
1774
+ ```
1775
+
1764
1776
  // Process any CRUD request (automatically handles other_requests if present)
1765
1777
  const result = await korm.processRequest(requestBody, modelName, context);
1766
1778
 
@@ -23,12 +23,22 @@ const db = knex({
23
23
  },
24
24
  });
25
25
 
26
- const korm = initializeKORM({
26
+ const korm = await initializeKORM({
27
27
  db,
28
28
  dbClient: 'mysql', // 'mysql' | 'pg' | 'sqlite'
29
29
  debug: false,
30
+ schema: null, // optional: schema object, file path, or URL
31
+ resolverPath: null, // optional: path to models directory
30
32
  });
31
33
 
34
+ // `schema` accepts four forms — auto-detected:
35
+ // object → used as-is
36
+ // ".json" → readFileSync + JSON.parse
37
+ // ".js" → require() (CJS: module.exports = {…})
38
+ // ".mjs" → dynamic import() (ESM: export default {…})
39
+ // "http(s)"→ fetch + JSON.parse
40
+ // Invalid sources throw KormError.
41
+
32
42
  const result = await korm.processRequest(requestObject, 'ModelName');
33
43
  ```
34
44
 
package/bin/korm-mcp.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";const path=require("path"),{initializeKORM:initializeKORM}=require("../index"),{createServer:createServer}=require("../src/mcp/server"),{McpConfigError:McpConfigError}=require("../src/mcp/errors");function parseArgv(e){const r={config:null,help:!1};for(let o=2;o<e.length;o++){const t=e[o];"--help"===t||"-h"===t?r.help=!0:"--config"===t||"-c"===t?r.config=e[++o]:t.startsWith("--config=")?r.config=t.slice(9):(process.stderr.write(`korm-mcp: unknown argument "${t}"\n`),r.help=!0)}return r}function printHelp(){process.stderr.write("korm-mcp — Model Context Protocol server for @dreamtree-org/korm-js\n\nUsage:\n korm-mcp --config <path-to-config.js>\n\nOptions:\n -c, --config <path> Path to a Node CJS module exporting { db, dbClient, schema, mcp }.\n -h, --help Show this help.\n\nSee docs/agents/11-mcp-server.md for the config shape.\n")}function requireConfigModule(e){try{const r=require(e);return r&&r.default?r.default:r}catch(r){throw new McpConfigError(`Failed to load config at ${e}: ${r.message}`)}}function assertConfigFields(e,r){if(!e||"object"!=typeof e)throw new McpConfigError(`Config at ${r} must export an object (got ${typeof e}).`);const o=["db","dbClient","schema"];for(const r of o)if(!e[r])throw new McpConfigError(`Config: \`${r}\` is required.`);if(!e.mcp||"object"!=typeof e.mcp)throw new McpConfigError("Config: `mcp` object is required (see spec §6).")}function loadConfig(e){if(!e)throw new McpConfigError("--config is required. See `korm-mcp --help`.");const r=path.resolve(process.cwd(),e),o=requireConfigModule(r);return assertConfigFields(o,r),o}const stderrLogger={info:(...e)=>process.stderr.write(`[korm-mcp] ${e.join(" ")}\n`),error:(...e)=>process.stderr.write(`[korm-mcp:error] ${e.join(" ")}\n`)};function installShutdownHandlers(e){const r=async r=>{stderrLogger.info(`received ${r}, shutting down`);try{await e.stop()}catch(e){stderrLogger.error(`stop error: ${e.message}`)}process.exit(0)};process.on("SIGINT",()=>r("SIGINT")),process.on("SIGTERM",()=>r("SIGTERM"))}async function main(e=process.argv){const r=parseArgv(e);let o;r.help&&(printHelp(),process.exit(0));try{o=loadConfig(r.config)}catch(e){process.stderr.write(`korm-mcp: ${e.message}\n`),process.exit(2)}const t=initializeKORM({db:o.db,dbClient:o.dbClient,schema:o.schema,resolverPath:o.resolverPath||null,debug:o.debug||!1}),n=require("../package.json"),s=createServer({controller:t,schema:o.schema,mcpConfig:o.mcp,packageInfo:{name:n.name,version:n.version}});installShutdownHandlers(s);try{await s.start({logger:stderrLogger}),stderrLogger.info(`started; ${s.tools.length} tools exposed (mode=${o.mcp.mode||"ro"})`)}catch(e){stderrLogger.error(`failed to start: ${e.message}`),process.exit(1)}}require.main===module&&main().catch(e=>{process.stderr.write(`korm-mcp: fatal: ${e.message}\n`),process.exit(1)}),module.exports={parseArgv:parseArgv,loadConfig:loadConfig,main:main};
2
+ "use strict";const path=require("path"),{initializeKORM:initializeKORM}=require("../index"),{createServer:createServer}=require("../src/mcp/server"),{McpConfigError:McpConfigError}=require("../src/mcp/errors");function parseArgv(e){const r={config:null,help:!1};for(let o=2;o<e.length;o++){const t=e[o];"--help"===t||"-h"===t?r.help=!0:"--config"===t||"-c"===t?r.config=e[++o]:t.startsWith("--config=")?r.config=t.slice(9):(process.stderr.write(`korm-mcp: unknown argument "${t}"\n`),r.help=!0)}return r}function printHelp(){process.stderr.write("korm-mcp — Model Context Protocol server for @dreamtree-org/korm-js\n\nUsage:\n korm-mcp --config <path-to-config.js>\n\nOptions:\n -c, --config <path> Path to a Node CJS module exporting { db, dbClient, schema, mcp }.\n -h, --help Show this help.\n\nSee docs/agents/11-mcp-server.md for the config shape.\n")}function requireConfigModule(e){try{const r=require(e);return r&&r.default?r.default:r}catch(r){throw new McpConfigError(`Failed to load config at ${e}: ${r.message}`)}}function assertConfigFields(e,r){if(!e||"object"!=typeof e)throw new McpConfigError(`Config at ${r} must export an object (got ${typeof e}).`);const o=["db","dbClient","schema"];for(const r of o)if(!e[r])throw new McpConfigError(`Config: \`${r}\` is required.`);if(!e.mcp||"object"!=typeof e.mcp)throw new McpConfigError("Config: `mcp` object is required (see spec §6).")}function loadConfig(e){if(!e)throw new McpConfigError("--config is required. See `korm-mcp --help`.");const r=path.resolve(process.cwd(),e),o=requireConfigModule(r);return assertConfigFields(o,r),o}const stderrLogger={info:(...e)=>process.stderr.write(`[korm-mcp] ${e.join(" ")}\n`),error:(...e)=>process.stderr.write(`[korm-mcp:error] ${e.join(" ")}\n`)};function installShutdownHandlers(e){const r=async r=>{stderrLogger.info(`received ${r}, shutting down`);try{await e.stop()}catch(e){stderrLogger.error(`stop error: ${e.message}`)}process.exit(0)};process.on("SIGINT",()=>r("SIGINT")),process.on("SIGTERM",()=>r("SIGTERM"))}async function main(e=process.argv){const r=parseArgv(e);let o;r.help&&(printHelp(),process.exit(0));try{o=loadConfig(r.config)}catch(e){process.stderr.write(`korm-mcp: ${e.message}\n`),process.exit(2)}const t=await initializeKORM({db:o.db,dbClient:o.dbClient,schema:o.schema,resolverPath:o.resolverPath||null,debug:o.debug||!1}),n=require("../package.json"),s=createServer({controller:t,schema:o.schema,mcpConfig:o.mcp,packageInfo:{name:n.name,version:n.version}});installShutdownHandlers(s);try{await s.start({logger:stderrLogger}),stderrLogger.info(`started; ${s.tools.length} tools exposed (mode=${o.mcp.mode||"ro"})`)}catch(e){stderrLogger.error(`failed to start: ${e.message}`),process.exit(1)}}require.main===module&&main().catch(e=>{process.stderr.write(`korm-mcp: fatal: ${e.message}\n`),process.exit(1)}),module.exports={parseArgv:parseArgv,loadConfig:loadConfig,main:main};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamtree-org/korm-js",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "Knowledge Object-Relational Mapping - A powerful, modular ORM system for Node.js with dynamic database operations, complex queries, relationships, and nested requests",
5
5
  "author": {
6
6
  "name": "Partha Preetham Krishna",