@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.
- package/ControllerWrapper.js +1 -1
- package/README.md +14 -2
- package/ai-skills/korm-js.md +11 -1
- package/bin/korm-mcp.js +1 -1
- package/package.json +1 -1
package/ControllerWrapper.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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:
|
|
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
|
|
package/ai-skills/korm-js.md
CHANGED
|
@@ -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.
|
|
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",
|