@dreamtree-org/korm-js 1.0.54 → 1.0.55

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 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;
1
+ const BaseSyncTable=require("../BaseSyncTable"),logger=require("../../Logger");class SQLiteSyncTable extends BaseSyncTable{_getClientName(){return"sqlite"}async _listTables(){return await this.db("sqlite_master").where({type:"table"}).whereNot("name","like","sqlite_%").pluck("name")}_supportsUnsigned(){return!1}async _applyExtras(e){for(const[t,s]of Object.entries(e.columns)){("string"==typeof s?this.utils.formatColumnSchema(t,s):s).onUpdate&&this._warnOnUnsupportedModifier("onUpdate",e.table,t)}}async _getRelations(e){return{}}async getTablesOfDatabase(){return this._listTables()}async executeSql(e,t=[]){return logger.debug("executeSql (legacy)",{sql:e,params:t}),this.db.raw(e,t)}}module.exports=SQLiteSyncTable;
@@ -0,0 +1 @@
1
+ "use strict";const DB_TYPE_TO_JSON={tinyint:{type:"integer"},smallint:{type:"integer"},mediumint:{type:"integer"},int:{type:"integer"},integer:{type:"integer"},bigint:{type:"integer"},decimal:{type:"number"},numeric:{type:"number"},float:{type:"number"},double:{type:"number"},real:{type:"number"},bit:{type:"integer"},varchar:{type:"string"},char:{type:"string"},text:{type:"string"},tinytext:{type:"string"},mediumtext:{type:"string"},longtext:{type:"string"},enum:{type:"string"},set:{type:"string"},uuid:{type:"string",format:"uuid"},json:{},jsonb:{},boolean:{type:"boolean"},date:{type:"string",format:"date"},datetime:{type:"string",format:"date-time"},timestamp:{type:"string",format:"date-time"},time:{type:"string",format:"time"},year:{type:"integer"},binary:{type:"string",contentEncoding:"base64"},varbinary:{type:"string",contentEncoding:"base64"},tinyblob:{type:"string",contentEncoding:"base64"},blob:{type:"string",contentEncoding:"base64"},mediumblob:{type:"string",contentEncoding:"base64"},longblob:{type:"string",contentEncoding:"base64"}},KNOWN_FLAGS=new Set(["primaryKey","autoIncrement","notNull","unique","nullable","index"]);function emptyFlags(){return{primaryKey:!1,autoIncrement:!1,notNull:!1,unique:!1,nullable:!0,hasDefault:!1,defaultValue:void 0,size:null,baseType:null}}function applySegmentToFlags(e,t){if(e.startsWith("size:")){const n=Number(e.slice(5));return void(Number.isFinite(n)&&(t.size=n))}if(e.startsWith("default:"))return t.hasDefault=!0,void(t.defaultValue=e.slice(8));KNOWN_FLAGS.has(e)&&("nullable"===e?t.nullable=!0:t[e]=!0)}function jsonSchemaForBase(e,t){const n={...DB_TYPE_TO_JSON[e]||{}};return"string"!==n.type||null==t.size||n.format||(n.maxLength=t.size),n}function parseColumnDef(e){if("string"!=typeof e||0===e.length)return{jsonSchema:{},flags:emptyFlags()};const t=e.split("|").map(e=>e.trim()).filter(Boolean),n=(t.shift()||"").toLowerCase(),i={...emptyFlags(),baseType:n};for(const e of t)applySegmentToFlags(e,i);return i.notNull&&(i.nullable=!1),{jsonSchema:jsonSchemaForBase(n,i),flags:i}}function isWritableOnCreate(e){return!e.flags.autoIncrement}function isRequiredOnCreate(e){return!e.flags.autoIncrement&&(!e.flags.hasDefault&&!0===e.flags.notNull)}function applyNullability(e,t){return!t.flags.notNull&&e.type?{...e,type:[e.type,"null"]}:e}module.exports={parseColumnDef:parseColumnDef,isWritableOnCreate:isWritableOnCreate,isRequiredOnCreate:isRequiredOnCreate,applyNullability:applyNullability,DB_TYPE_TO_JSON:DB_TYPE_TO_JSON};
package/index.d.ts ADDED
@@ -0,0 +1,213 @@
1
+ export interface InitializeOptions {
2
+ db: any;
3
+ dbClient: string;
4
+ schema?: any;
5
+ resolverPath?: string;
6
+ debug?: boolean;
7
+ }
8
+
9
+ /**
10
+ * Response when a request is sent with `dryRun: true`: the SQL that
11
+ * would run, without executing it. See docs/agents/06-request-contract.md §9.
12
+ */
13
+ export interface DryRunResult {
14
+ success: true;
15
+ dryRun: true;
16
+ action: string;
17
+ model: string;
18
+ sql: string;
19
+ bindings: any[];
20
+ statements: Array<{ sql: string; bindings: any[] }>;
21
+ }
22
+
23
+ /** One column in a ModelDescription (issue #15). */
24
+ export interface ColumnDescription {
25
+ name: string;
26
+ type: string | null;
27
+ nullable: boolean;
28
+ primaryKey: boolean;
29
+ autoIncrement: boolean;
30
+ unique: boolean;
31
+ size?: number;
32
+ default?: string;
33
+ }
34
+
35
+ /** One relation in a ModelDescription. */
36
+ export interface RelationDescription {
37
+ name: string;
38
+ type: string | null;
39
+ table: string | null;
40
+ localKey: string | null;
41
+ foreignKey: string | null;
42
+ through?: string;
43
+ throughLocalKey?: string | null;
44
+ throughForeignKey?: string | null;
45
+ }
46
+
47
+ /** Pure-data description of one model (korm.describeModel). */
48
+ export interface ModelDescription {
49
+ schemaApiVersion: number;
50
+ model: string;
51
+ table: string | null;
52
+ alias: string;
53
+ columns: ColumnDescription[];
54
+ relations: RelationDescription[];
55
+ softDelete: boolean;
56
+ actions: string[];
57
+ }
58
+
59
+ /** Pure-data description of all models (korm.describeSchema). */
60
+ export interface SchemaDescription {
61
+ schemaApiVersion: number;
62
+ models: ModelDescription[];
63
+ }
64
+
65
+ export interface KormInstance {
66
+ processRequest(requestBody: any, modelName: string, context?: any): Promise<any | DryRunResult>;
67
+ syncDatabase?(options?: any): Promise<any>;
68
+ generateSchema?(options?: any): Promise<any>;
69
+ /**
70
+ * Draft-2020-12 JSON Schema for every valid processRequest body for
71
+ * `modelName` (an action-discriminated `oneOf`). For OpenAI/Anthropic
72
+ * tool definitions + client-side prevalidation. Throws KormError
73
+ * (code 'UNKNOWN_MODEL') for an unregistered model.
74
+ */
75
+ getRequestJsonSchema(modelName: string): Record<string, any>;
76
+ /** Pure-data description of all registered models (issue #15). */
77
+ describeSchema(): SchemaDescription;
78
+ /**
79
+ * Pure-data description of one model. Throws KormError (code
80
+ * 'UNKNOWN_MODEL') for an unregistered model.
81
+ */
82
+ describeModel(modelName: string): ModelDescription;
83
+ setSchema(schema: any): void;
84
+ loadModelClass?(name: string): any;
85
+ getModelInstance?(name: string): any;
86
+ }
87
+
88
+ export function initializeKORM(opts: InitializeOptions): KormInstance;
89
+ export function validate(body: any, rules: any, opts?: any): Promise<any>;
90
+ export const helperUtility: any;
91
+ export const emitter: any;
92
+ export const logger: any;
93
+
94
+ // ---- Structured errors --------------------------------------------------
95
+
96
+ export type KormErrorCode =
97
+ | 'NO_MATCHING_ROW'
98
+ | 'UNKNOWN_ACTION'
99
+ | 'VALIDATION_FAILED'
100
+ | 'UNKNOWN_MODEL'
101
+ | 'NO_CUSTOM_ACTION_HOOK'
102
+ | 'INTERNAL';
103
+
104
+ export interface KormErrorContext {
105
+ action?: string;
106
+ model?: string;
107
+ validActions?: string[];
108
+ closest?: string | null;
109
+ available?: string[];
110
+ source?: string | null;
111
+ fields?: Array<{ field?: string; message?: string; value?: unknown; rule?: unknown }>;
112
+ [key: string]: unknown;
113
+ }
114
+
115
+ export interface KormErrorJSON {
116
+ name: 'KormError';
117
+ code: KormErrorCode;
118
+ message: string;
119
+ hint: string | null;
120
+ context: KormErrorContext;
121
+ suggestedFixes: Array<{ description: string; request?: object }> | null;
122
+ }
123
+
124
+ /**
125
+ * Structured error thrown by processRequest / validate. Extends the
126
+ * native Error, so `catch (e) { e.message }` keeps working; `e.code`
127
+ * and `e.context` let callers (and agents) branch programmatically.
128
+ */
129
+ export class KormError extends Error {
130
+ name: 'KormError';
131
+ code: KormErrorCode;
132
+ hint: string | null;
133
+ context: KormErrorContext;
134
+ suggestedFixes: Array<{ description: string; request?: object }> | null;
135
+ /** Present when code === 'VALIDATION_FAILED' (back-compat alias). */
136
+ errors?: any[];
137
+ toJSON(): KormErrorJSON;
138
+
139
+ static CODES: Record<KormErrorCode, KormErrorCode>;
140
+ static ACTIONS: readonly string[];
141
+ static closestAction(input: string, candidates?: string[]): string | null;
142
+ static noMatchingRow(opts: { action: string; model: string }): KormError;
143
+ static unknownAction(opts: { action: string; model?: string; hasCustomHook?: boolean }): KormError;
144
+ static unknownModel(opts: { model: string; available?: string[] }): KormError;
145
+ static validationFailed(opts: { errors?: any[]; source?: string | null }): KormError;
146
+ }
147
+
148
+ export const LibClasses: { Emitter: any; KormError: typeof KormError };
149
+ export const lib: {
150
+ createValidationMiddleware(...args: any[]): any;
151
+ validateEmail(...args: any[]): any;
152
+ validatePassword(...args: any[]): any;
153
+ validatePhone(...args: any[]): any;
154
+ validatePAN(...args: any[]): any;
155
+ validateAadhaar(...args: any[]): any;
156
+ };
157
+
158
+ // ---- MCP (Model Context Protocol) optional surface ----------------------
159
+
160
+ export type McpMode = 'ro' | 'rw' | 'rw-sync';
161
+
162
+ export interface McpCustomAction {
163
+ table: string;
164
+ action: string;
165
+ schema?: any;
166
+ description?: string;
167
+ }
168
+
169
+ export interface McpConfig {
170
+ mode?: McpMode;
171
+ allowlist?: string[] | '*';
172
+ blocklist?: string[];
173
+ metaTools?: boolean;
174
+ allowNestedRequests?: boolean;
175
+ customActions?: McpCustomAction[];
176
+ rateLimit?: { perMinute?: number };
177
+ logLevel?: string;
178
+ }
179
+
180
+ export interface McpToolResult {
181
+ content: Array<{ type: string; text: string }>;
182
+ isError?: boolean;
183
+ }
184
+
185
+ export interface McpTool {
186
+ name: string;
187
+ description: string;
188
+ inputSchema: any;
189
+ handler: (input: any) => Promise<McpToolResult>;
190
+ }
191
+
192
+ export interface McpServer {
193
+ tools: McpTool[];
194
+ toolsByName: Map<string, McpTool>;
195
+ start(opts?: { logger?: any }): Promise<any>;
196
+ stop(): Promise<void>;
197
+ }
198
+
199
+ export interface CreateMcpServerOptions {
200
+ controller: any;
201
+ schema: any;
202
+ mcpConfig: McpConfig;
203
+ packageInfo?: { name?: string; version?: string };
204
+ }
205
+
206
+ export const mcp: {
207
+ createServer(opts: CreateMcpServerOptions): McpServer;
208
+ generateTools(opts: {
209
+ controller: any;
210
+ schema: any;
211
+ mcpConfig: McpConfig;
212
+ }): McpTool[];
213
+ };
package/index.js CHANGED
@@ -1 +1 @@
1
- const ControllerWrapper=require("./ControllerWrapper"),BaseHelperUtility=require("./BaseHelperUtility"),RequestValidator=require("./RequestValidator"),Emitter=require("./Emitter"),logger=require("./Logger");module.exports={LibClasses:{Emitter:Emitter},initializeKORM(){return ControllerWrapper.initializeKORM(...arguments)},helperUtility:new BaseHelperUtility,emitter:new Emitter,logger:logger,validate:RequestValidator.validate,lib:{createValidationMiddleware:RequestValidator.createValidationMiddleware,validateEmail:RequestValidator.validateEmail,validatePassword:RequestValidator.validatePassword,validatePhone:RequestValidator.validatePhone,validatePAN:RequestValidator.validatePAN,validateAadhaar:RequestValidator.validateAadhaar}};
1
+ const ControllerWrapper=require("./ControllerWrapper"),BaseHelperUtility=require("./BaseHelperUtility"),RequestValidator=require("./RequestValidator"),Emitter=require("./Emitter"),logger=require("./Logger"),KormError=require("./KormError"),{createServer:createMcpServer}=require("./src/mcp/server"),{generateTools:generateMcpTools}=require("./src/mcp/toolGenerator");module.exports={LibClasses:{Emitter:Emitter,KormError:KormError},KormError:KormError,initializeKORM(){return ControllerWrapper.initializeKORM(...arguments)},helperUtility:new BaseHelperUtility,emitter:new Emitter,logger:logger,validate:RequestValidator.validate,lib:{createValidationMiddleware:RequestValidator.createValidationMiddleware,validateEmail:RequestValidator.validateEmail,validatePassword:RequestValidator.validatePassword,validatePhone:RequestValidator.validatePhone,validatePAN:RequestValidator.validatePAN,validateAadhaar:RequestValidator.validateAadhaar},mcp:{createServer:createMcpServer,generateTools:generateMcpTools}};
package/jest.config.js CHANGED
@@ -1 +1 @@
1
- module.exports={testEnvironment:"node",testMatch:["**/test/**/*.test.js","**/test/**/*.spec.js","**/__tests__/**/*.js"],testPathIgnorePatterns:["/node_modules/","<rootDir>/test/mysql/","<rootDir>/test/postgres/","<rootDir>/test/sqlite/sqlite-crud.test.js","<rootDir>/test/sqlite/sqlite-sync-all-tables.test.js","<rootDir>/test/sqlite/sqlite-table-sync.test.js","<rootDir>/test/sqlite/sqlite.test.js","<rootDir>/test/sqlite/crud-operations-coverage.test.js"],collectCoverage:!0,coverageDirectory:"coverage",coverageReporters:["text","lcov","html"],collectCoverageFrom:["**/*.js","!**/node_modules/**","!**/test/**","!**/coverage/**","!jest.config.js","!**/dist/**","!build.js","!version-manager.js"],testTimeout:1e4,clearMocks:!0,verbose:!0};
1
+ module.exports={testEnvironment:"node",testMatch:["**/test/**/*.test.js","**/test/**/*.spec.js","**/__tests__/**/*.js"],testPathIgnorePatterns:["/node_modules/"],collectCoverage:!1,coverageDirectory:"coverage",coverageReporters:["text","lcov","html"],collectCoverageFrom:["clients/sqlite/**/*.js","clients/Base*.js","ControllerWrapper.js","RequestValidator.js","BaseHelperUtility.js","Logger.js","Emitter.js","index.js","cli.js","helpers/**/*.js","src/mcp/**/*.js","bin/korm-mcp.js","!**/node_modules/**","!**/test/**","!**/coverage/**","!**/dist/**"],coverageThreshold:{global:{statements:60,branches:50,functions:60,lines:61}},testTimeout:1e4,moduleNameMapper:{"^@modelcontextprotocol/sdk/(.*)$":"<rootDir>/node_modules/@modelcontextprotocol/sdk/dist/cjs/$1"},clearMocks:!0,verbose:!0};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamtree-org/korm-js",
3
- "version": "1.0.54",
3
+ "version": "1.0.55",
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",
@@ -10,7 +10,8 @@
10
10
  "main": "index.js",
11
11
  "types": "index.d.ts",
12
12
  "bin": {
13
- "korm-js": "./cli.js"
13
+ "korm-js": "./cli.js",
14
+ "korm-mcp": "./bin/korm-mcp.js"
14
15
  },
15
16
  "scripts": {
16
17
  "test": "jest",
@@ -67,6 +68,9 @@
67
68
  "peerDependencies": {
68
69
  "knex": "^3.0.0"
69
70
  },
71
+ "optionalDependencies": {
72
+ "@modelcontextprotocol/sdk": "^1.0.0"
73
+ },
70
74
  "directories": {
71
75
  "doc": "Documentation"
72
76
  },
@@ -0,0 +1 @@
1
+ "use strict";const{parseColumnDef:parseColumnDef,isWritableOnCreate:isWritableOnCreate,isRequiredOnCreate:isRequiredOnCreate,applyNullability:applyNullability}=require("./columnSchema"),READ_ACTIONS=new Set(["list","show","count","sum"]),WRITE_ACTIONS=new Set(["create","update","delete","replace","upsert","sync"]),KNOWN_ACTIONS=new Set([...READ_ACTIONS,...WRITE_ACTIONS]);function buildColumnsMap(e){const t={},r=e&&e.columns||{};for(const[e,i]of Object.entries(r))t[e]=parseColumnDef(i);return t}function buildDataSchemaForCreate(e){const t={},r=[];for(const[i,o]of Object.entries(e))isWritableOnCreate(o)&&(t[i]=applyNullability(o.jsonSchema,o),isRequiredOnCreate(o)&&r.push(i));const i={type:"object",properties:t,additionalProperties:!1};return r.length&&(i.required=r),i}function buildDataSchemaForUpdate(e){const t={};for(const[r,i]of Object.entries(e))isWritableOnCreate(i)&&(t[r]=applyNullability(i.jsonSchema,i));return{type:"object",properties:t,additionalProperties:!1}}function buildWhereSchema(e){const t={};for(const r of Object.keys(e))t[r]={};return{type:"object",properties:t,additionalProperties:!0}}function buildSelectSchema(e){const t=Object.keys(e);return{oneOf:[{type:"string"},{type:"array",items:t.length?{type:"string",enum:t}:{type:"string"}}]}}function buildOrderBySchema(e){const t=Object.keys(e),r={type:"object",properties:{column:t.length?{type:"string",enum:t}:{type:"string"},order:{type:"string",enum:["asc","desc","ASC","DESC"]}},required:["column"],additionalProperties:!1};return{oneOf:[{type:"string"},{type:"array",items:{type:"string"}},r,{type:"array",items:r}]}}function buildWithSchema(e){const t=Object.keys(e||{}),r={type:"string"};return t.length&&(r.description=`Top-level relations available: ${t.join(", ")}. Use dot-paths for deeper traversal, e.g. "${t[0]}.NestedRel".`),{type:"array",items:r}}function buildSumDataSchema(e){const t=Object.keys(e);return{type:"object",properties:{sumColumn:t.length?{type:"string",enum:t}:{type:"string"},sumFormula:{type:"string",description:"Arithmetic expression over column placeholders. Allowed chars: digits, . + - * / ( ), {column} placeholders, whitespace. See docs/agents/06-request-contract.md §5."}},additionalProperties:!1}}function buildConflictSchema(e){const t=Object.keys(e);return{type:"array",items:t.length?{type:"string",enum:t}:{type:"string"},minItems:1}}function commonReadSelectors(e,t){return{where:buildWhereSchema(e),select:buildSelectSchema(e),orderBy:buildOrderBySchema(e),limit:{type:"integer",minimum:1},offset:{type:"integer",minimum:0},page:{type:"integer",minimum:1},with:buildWithSchema(t),withWhere:buildWhereSchema(e),groupBy:{oneOf:[{type:"string"},{type:"array",items:{type:"string"}}]},having:buildWhereSchema(e),distinct:{oneOf:[{type:"boolean"},{type:"string"},{type:"array",items:{type:"string"}}]}}}const ACTION_BUILDERS={list:(e,t)=>({type:"object",properties:commonReadSelectors(e,t),additionalProperties:!1}),show:(e,t)=>({type:"object",properties:{where:buildWhereSchema(e),select:buildSelectSchema(e),with:buildWithSchema(t),withWhere:buildWhereSchema(e)},required:["where"],additionalProperties:!1}),count:e=>({type:"object",properties:{where:buildWhereSchema(e),distinct:{oneOf:[{type:"boolean"},{type:"string"},{type:"array",items:{type:"string"}}]}},additionalProperties:!1}),sum:e=>({type:"object",properties:{data:buildSumDataSchema(e),where:buildWhereSchema(e),groupBy:{oneOf:[{type:"string"},{type:"array",items:{type:"string"}}]}},required:["data"],additionalProperties:!1}),create(e){const t=buildDataSchemaForCreate(e);return{type:"object",properties:{data:{oneOf:[t,{type:"array",items:t,minItems:1}]}},required:["data"],additionalProperties:!1}},update:e=>({type:"object",properties:{where:buildWhereSchema(e),data:buildDataSchemaForUpdate(e)},required:["where","data"],additionalProperties:!1}),delete:e=>({type:"object",properties:{where:buildWhereSchema(e)},required:["where"],additionalProperties:!1}),replace(e){const t=buildDataSchemaForCreate(e);return{type:"object",properties:{data:{oneOf:[t,{type:"array",items:t,minItems:1}]}},required:["data"],additionalProperties:!1}},upsert(e){const t=buildDataSchemaForCreate(e);return{type:"object",properties:{data:{oneOf:[t,{type:"array",items:t,minItems:1}]},conflict:buildConflictSchema(e)},required:["data","conflict"],additionalProperties:!1}},sync(e){const t=buildDataSchemaForCreate(e);return{type:"object",properties:{data:{oneOf:[t,{type:"array",items:t,minItems:1}]},where:buildWhereSchema(e),conflict:buildConflictSchema(e)},required:["data","where","conflict"],additionalProperties:!1}}};function buildRequestSchema({action:e,model:t}){if(!t||"object"!=typeof t)throw new TypeError("buildRequestSchema: `model` is required");if(!KNOWN_ACTIONS.has(e))throw new RangeError(`buildRequestSchema: unknown action "${e}". Known: ${[...KNOWN_ACTIONS].join(", ")}`);const r=buildColumnsMap(t),i=t.hasRelations||{};return ACTION_BUILDERS[e](r,i)}function modelTitle(e){return e.modelName||e.alias||e.table||"Model"}function buildModelRequestSchema(e,t={}){if(!e||"object"!=typeof e)throw new TypeError("buildModelRequestSchema: `model` is required");const r=modelTitle(e),i=[...KNOWN_ACTIONS].map(t=>{const r=buildRequestSchema({action:t,model:e});return{type:"object",title:t,properties:{action:{type:"string",const:t,description:`The "${t}" operation.`},...r.properties,dryRun:{type:"boolean",description:"If true, return the SQL that would run without executing it."}},required:["action",...r.required||[]],additionalProperties:!1}});return{$schema:"https://json-schema.org/draft/2020-12/schema",title:t.title||`KormRequest<${r}>`,description:`Valid processRequest(body, "${r}") shapes. Exactly one action branch applies.`,oneOf:i}}module.exports={buildRequestSchema:buildRequestSchema,buildModelRequestSchema:buildModelRequestSchema,buildColumnsMap:buildColumnsMap,READ_ACTIONS:READ_ACTIONS,WRITE_ACTIONS:WRITE_ACTIONS,KNOWN_ACTIONS:KNOWN_ACTIONS};
@@ -0,0 +1 @@
1
+ "use strict";const{parseColumnDef:parseColumnDef}=require("./columnSchema"),{KNOWN_ACTIONS:KNOWN_ACTIONS}=require("./requestSchema"),SCHEMA_API_VERSION=1;function describeColumn(e,l){const{flags:n}=parseColumnDef(l),t=!0===n.nullable&&!0!==n.primaryKey,u={name:e,type:n.baseType||null,nullable:t,primaryKey:!0===n.primaryKey,autoIncrement:!0===n.autoIncrement,unique:!0===n.unique};return null!=n.size&&(u.size=n.size),n.hasDefault&&(u.default=n.defaultValue),u}function describeRelations(e){return Object.entries(e||{}).map(([e,l])=>{const n={name:e,type:l.type||null,table:l.table||null,localKey:l.localKey||null,foreignKey:l.foreignKey||null};return l.through&&(n.through=l.through,n.throughLocalKey=l.throughLocalKey||null,n.throughForeignKey=l.throughForeignKey||null),n})}function buildModelDescription(e,l,n={}){return{schemaApiVersion:1,model:e,table:l.table||null,alias:l.alias||e,columns:Object.entries(l.columns||{}).map(([e,l])=>describeColumn(e,l)),relations:describeRelations(l.hasRelations),softDelete:!0===n.softDelete,actions:[...KNOWN_ACTIONS]}}module.exports={buildModelDescription:buildModelDescription,describeColumn:describeColumn,describeRelations:describeRelations,SCHEMA_API_VERSION:1};
@@ -0,0 +1 @@
1
+ "use strict";class McpConfigError extends Error{constructor(r){super(r),this.name="McpConfigError"}}function sanitizeDbErrorMessage(r){return`${r&&r.code?`[${r.code}] `:""}${(r&&r.message?String(r.message):"Unknown error").split("\n",1)[0]}`.slice(0,500)}function toolErrorResult(r){return{isError:!0,content:[{type:"text",text:r&&("ValidationError"===r.name||!0===r.isValidation)?`Validation failed: ${r.message}`:`Tool execution failed: ${sanitizeDbErrorMessage(r)}`}]}}function toolSuccessResult(r){let e;try{e=JSON.stringify(r,null,2)}catch(t){e=String(r)}return{content:[{type:"text",text:e}]}}module.exports={McpConfigError:McpConfigError,sanitizeDbErrorMessage:sanitizeDbErrorMessage,toolErrorResult:toolErrorResult,toolSuccessResult:toolSuccessResult};
@@ -0,0 +1 @@
1
+ "use strict";const{toolErrorResult:toolErrorResult,toolSuccessResult:toolSuccessResult}=require("./errors"),{resolveTables:resolveTables}=require("./toolGenerator");function describeColumns(e){const o={};for(const[t,r]of Object.entries(e.columns||{}))o[t]=r;return o}function buildListTablesTool({schema:e,visibleTables:o}){return{name:"korm.list_tables",description:"List the tables (models) currently exposed by this MCP server, with their column counts.",inputSchema:{type:"object",properties:{},additionalProperties:!1},handler:async()=>{try{const t=o().map(o=>{const t=e[o];return{modelName:o,table:t.table,columnCount:Object.keys(t.columns||{}).length,relationCount:Object.keys(t.hasRelations||{}).length}});return toolSuccessResult({tables:t})}catch(e){return toolErrorResult(e)}}}}function buildDescribeSchemaTool({schema:e,visibleTables:o}){return{name:"korm.describe_schema",description:"Describe the columns and relations of a single allowlisted table.",inputSchema:{type:"object",properties:{table:{type:"string",description:'Model name (matches the key in the KORM schema, e.g. "User" — not the SQL table name).'}},required:["table"],additionalProperties:!1},handler:async t=>{try{const r=t&&t.table,s=o();if(!s.includes(r))return toolErrorResult(new Error(`Table "${r}" is not exposed by this MCP server. Available: ${s.join(", ")||"(none)"}.`));const n=e[r];return toolSuccessResult({modelName:r,table:n.table,columns:describeColumns(n),relations:n.hasRelations||{}})}catch(e){return toolErrorResult(e)}}}}async function pingDb(e){if(!e||!e.db||"function"!=typeof e.db.raw)return{ok:!1,error:"controller.db not present or not a Knex instance"};try{return await e.db.raw("SELECT 1"),{ok:!0}}catch(e){return{ok:!1,error:e.message}}}function buildHealthTool({controller:e,mcpConfig:o,packageInfo:t,visibleTables:r}){return{name:"korm.health",description:"Report MCP server health: engine, library version, allowlist size, DB ping.",inputSchema:{type:"object",properties:{},additionalProperties:!1},handler:async()=>{try{const s=e&&(e.dbClient||e.engine)||"unknown",n=r(),l=await pingDb(e);return toolSuccessResult({status:l.ok?"ok":"degraded",engine:s,version:t.version||"unknown",mode:o.mode,allowedTableCount:n.length,dbPing:l.ok,...l.error?{dbError:l.error}:{}})}catch(e){return toolErrorResult(e)}}}}function buildMetaTools({controller:e,schema:o,mcpConfig:t,packageInfo:r={}}){if(!1===t.metaTools)return[];const s=()=>resolveTables(o,t);return[buildListTablesTool({schema:o,visibleTables:s}),buildDescribeSchemaTool({schema:o,visibleTables:s}),buildHealthTool({controller:e,mcpConfig:t,packageInfo:r,visibleTables:s})]}module.exports={buildMetaTools:buildMetaTools};
@@ -0,0 +1 @@
1
+ "use strict";const{generateTools:generateTools}=require("./toolGenerator"),{buildMetaTools:buildMetaTools}=require("./schemaIntrospect"),{toolErrorResult:toolErrorResult}=require("./errors");function loadSdk(){try{const e=require("@modelcontextprotocol/sdk/server/index.js"),o=require("@modelcontextprotocol/sdk/server/stdio.js"),r=require("@modelcontextprotocol/sdk/types.js");return{Server:e.Server,StdioServerTransport:o.StdioServerTransport,CallToolRequestSchema:r.CallToolRequestSchema,ListToolsRequestSchema:r.ListToolsRequestSchema}}catch(e){throw new Error(`The @modelcontextprotocol/sdk package is required to start the KORM MCP server.\nInstall it with: npm install @modelcontextprotocol/sdk\nUnderlying require error: ${e.message}`)}}function buildAllTools({controller:e,schema:o,mcpConfig:r,packageInfo:t}){const l=[];return l.push(...buildMetaTools({controller:e,schema:o,mcpConfig:r,packageInfo:t})),l.push(...generateTools({controller:e,schema:o,mcpConfig:r})),l}function createServer({controller:e,schema:o,mcpConfig:r,packageInfo:t={}}){const l=buildAllTools({controller:e,schema:o,mcpConfig:r,packageInfo:t}),n=new Map(l.map(e=>[e.name,e]));let s=null,a=null;return{start:async function({logger:e=console}={}){const o=loadSdk();return s=new o.Server({name:t.name||"@dreamtree-org/korm-js mcp",version:t.version||"0.0.0"},{capabilities:{tools:{}}}),s.setRequestHandler(o.ListToolsRequestSchema,async()=>({tools:l.map(({name:e,description:o,inputSchema:r})=>({name:e,description:o,inputSchema:r}))})),s.setRequestHandler(o.CallToolRequestSchema,async o=>{const{name:r,arguments:t}=o.params||{},l=n.get(r);if(!l)return toolErrorResult(new Error(`Unknown tool: ${r}`));try{return await l.handler(t||{})}catch(o){return e.error&&e.error(`[korm-mcp] tool "${r}" threw:`,o),toolErrorResult(o)}}),a=new o.StdioServerTransport,await s.connect(a),s},stop:async function(){s&&"function"==typeof s.close&&await s.close(),s=null,a=null},tools:l,toolsByName:n}}module.exports={createServer:createServer,buildAllTools:buildAllTools};
@@ -0,0 +1 @@
1
+ "use strict";const{buildRequestSchema:buildRequestSchema,READ_ACTIONS:READ_ACTIONS,WRITE_ACTIONS:WRITE_ACTIONS}=require("../../requestSchema"),{McpConfigError:McpConfigError,toolErrorResult:toolErrorResult,toolSuccessResult:toolSuccessResult}=require("./errors"),VALID_MODES=new Set(["ro","rw","rw-sync"]);function actionsForMode(e){switch(e){case"ro":return new Set([...READ_ACTIONS]);case"rw":return new Set([...READ_ACTIONS,"create","update","delete","replace","upsert"]);case"rw-sync":return new Set([...READ_ACTIONS,...WRITE_ACTIONS]);default:throw new McpConfigError(`Invalid mcp.mode "${e}". Valid: ${[...VALID_MODES].join(", ")}.`)}}function resolveTables(e,o){const t=Object.keys(e||{}),r=o.allowlist,n=new Set(o.blocklist||[]);if(!r||"*"===r||Array.isArray(r)&&r.includes("*")){if("ro"!==o.mode)throw new McpConfigError(`mcp.allowlist of "*" is only permitted in mode "ro". Current mode: "${o.mode}".`);return t.filter(e=>!n.has(e))}if(!Array.isArray(r))throw new McpConfigError('mcp.allowlist must be an array of model names, or "*" (ro mode only).');const s=r.filter(o=>!e[o]);if(s.length)throw new McpConfigError(`mcp.allowlist references unknown model(s): ${s.join(", ")}`);return r.filter(e=>!n.has(e))}function toolNameFor(e,o){return`${e.table}.${o}`}function descriptionFor({modelName:e,action:o,model:t}){const r={list:`Paginated list of ${e} rows.`,show:`Fetch a single ${e} row matching \`where\`.`,count:`Count ${e} rows matching \`where\`.`,sum:`Sum a numeric column or arithmetic formula over ${e} rows.`,create:`Create one or more ${e} rows.`,update:`Update ${e} rows matching \`where\` with the supplied \`data\`.`,delete:`Delete ${e} rows matching \`where\`.`,replace:`Replace ${e} rows (insert-or-replace semantics).`,upsert:`Upsert ${e} rows; \`conflict\` lists the unique columns.`,sync:`Sync ${e} rows: insert/update from \`data\`, delete others matching \`where\`.`}[o]||`${o} on ${e}.`,n=Object.keys(t.hasRelations||{});return`${r}${n.length?` Available relations: ${n.join(", ")}.`:""}`}function buildHandler({controller:e,modelName:o,action:t,mcpConfig:r}){return async function(n){try{const s={action:t,...n||{}};s.other_requests&&!r.allowNestedRequests&&delete s.other_requests;const c=await e.processRequest(s,o,null);return toolSuccessResult(c)}catch(e){return toolErrorResult(e)}}}function validateCustomActionEntry(e,o){if(!e||"object"!=typeof e)throw new McpConfigError("mcp.customActions entries must be objects: { table, action, schema?, description? }");if(!e.table||!o[e.table])throw new McpConfigError(`mcp.customActions: unknown model "${e.table}"`);if("string"!=typeof e.action||!e.action)throw new McpConfigError('mcp.customActions: each entry needs a string "action"')}function buildCustomActionTools({controller:e,schema:o,mcpConfig:t}){return(t.customActions||[]).map(r=>{validateCustomActionEntry(r,o);const{table:n,action:s,schema:c,description:i}=r;return{name:`${o[n].table}.${s}`,description:i||`Custom action "${s}" on ${n}.`,inputSchema:c||{type:"object",additionalProperties:!0},handler:buildHandler({controller:e,modelName:n,action:s,mcpConfig:t})}})}function generateTools({controller:e,schema:o,mcpConfig:t}){if(!e||"function"!=typeof e.processRequest)throw new McpConfigError("generateTools: `controller` must expose processRequest(request, modelName, ctx)");if(!o||"object"!=typeof o)throw new McpConfigError("generateTools: `schema` is required");const r=t.mode||"ro",n=actionsForMode(r),s=resolveTables(o,{...t,mode:r}),c=[];for(const i of s){const s=o[i];for(const o of n)c.push({name:toolNameFor(s,o),description:descriptionFor({modelName:i,action:o,model:s}),inputSchema:buildRequestSchema({action:o,model:s}),handler:buildHandler({controller:e,modelName:i,action:o,mcpConfig:{...t,mode:r}})})}return c.push(...buildCustomActionTools({controller:e,schema:o,mcpConfig:{...t,mode:r}})),c}module.exports={generateTools:generateTools,actionsForMode:actionsForMode,resolveTables:resolveTables,toolNameFor:toolNameFor,VALID_MODES:VALID_MODES};
@@ -1 +0,0 @@
1
- const MySQLTableSync=require("./clients/mysql/tableSync"),PostgreSQLTableSync=require("./clients/pg/tableSync"),SQLiteTableSync=require("./clients/sqlite/tableSync");class TableSchemaSync{static _db=null;static _schemaTable=null;static setDb(e){TableSchemaSync._db=e}static setSchemaTable(e){TableSchemaSync._schemaTable=e}static getDb(){if(!TableSchemaSync._db)throw new Error("Database connection not set. Call TableSchemaSync.setDb(db) first.");return TableSchemaSync._db}static getSchemaTable(){if(!TableSchemaSync._schemaTable)throw new Error("Schema table not set. Call TableSchemaSync.setSchemaTable(schemaTable) first.");return TableSchemaSync._schemaTable}static getClientName(){try{return TableSchemaSync._db?.client?.config?.client||TableSchemaSync._db?.config?.client||""}catch{return""}}static getDatabaseSync(){const e=TableSchemaSync.getDb(),a=TableSchemaSync.getClientName();if(TableSchemaSync._isPostgres(a))return new PostgreSQLTableSync(e);if(TableSchemaSync._isMySQL(a))return new MySQLTableSync(e);if(TableSchemaSync._isSQLite(a))return new SQLiteTableSync(e);throw new Error(`Unsupported database client: ${a}`)}static mapTypeToDatabase(e){return TableSchemaSync.getDatabaseSync().mapTypeToDatabase(e)}static async getTableStructure(e){const a=TableSchemaSync.getDatabaseSync(),t=await a.getTableStructure(e);return null===t?{columns:[],primaryKeys:[],uniqueConstraints:[],indexes:[],foreignKeys:[]}:t}static _isPostgres(e){return e.includes("pg")||e.includes("postgres")}static _isMySQL(e){return e.includes("mysql")}static _isSQLite(e){return e.includes("sqlite")}static async tableExists(e){try{const a=TableSchemaSync.getDatabaseSync();return await a.tableExists(e)}catch(e){return!1}}static async createTable(e,a={}){const t=TableSchemaSync.getSchemaTable();TableSchemaSync.getDb(),TableSchemaSync.getClientName();if(!t[e])throw new Error(`Model "${e}" not found in schema table`);const c=t[e],n=c.table;if(await TableSchemaSync.tableExists(n)){if(a.ifNotExists)return!0;throw new Error(`Table "${n}" already exists`)}const s=c.columns||{},l={};for(const[e,a]of Object.entries(s)){const t=TableSchemaSync.mapTypeToDatabase(a);l[e]=t}const r=TableSchemaSync.getDatabaseSync();return await r.createTable(n,l,a),await r.addTableConstraints(n,c,a),!0}static async deleteTable(e,a={}){const t=TableSchemaSync.getSchemaTable(),c=TableSchemaSync.getDb();if(!t[e])throw new Error(`Model "${e}" not found in schema table`);const n=t[e].table;if(!await TableSchemaSync.tableExists(n)){if(a.ifExists)return!0;throw new Error(`Table "${n}" does not exist`)}if(!a.force)throw new Error("Table deletion requires force: true option for safety");return await c.schema.dropTable(n),!0}static async alterTable(e,a={}){const t=TableSchemaSync.getSchemaTable();TableSchemaSync.getDb();if(!t[e])throw new Error(`Model "${e}" not found in schema table`);const c=t[e].table;if(!await TableSchemaSync.tableExists(c))throw new Error(`Table "${c}" does not exist`);const n=await TableSchemaSync.getTableStructure(c),s=t[e].columns||{},l=new Set(n.columns.map(e=>e.name)),r=new Set(Object.keys(s)),o=[...r].filter(e=>!l.has(e)),i=[...l].filter(e=>!r.has(e)),S=[...l].filter(e=>r.has(e));for(const e of o)await TableSchemaSync._addColumn(c,e,s[e]);if(a.allowColumnRemoval)for(const e of i)await TableSchemaSync._removeColumn(c,e);for(const e of S)await TableSchemaSync._modifyColumn(c,e,s[e],n);const b=TableSchemaSync.getDatabaseSync();return await b.addTableConstraints(c,t[e],a),!0}static async _addColumn(e,a,t,c={}){const n=TableSchemaSync.getDatabaseSync();await n.addColumn(e,a,t,c)}static async _removeColumn(e,a){const t=TableSchemaSync.getDatabaseSync();await t.removeColumn(e,a)}static async _modifyColumn(e,a,t,c){const n=TableSchemaSync.getDatabaseSync(),s=n.mapTypeToDatabase(t),l=c.columns.find(e=>e.name===a);l&&l.type!==s&&await n.modifyColumn(e,a,t)}static async syncAllTables(e={}){const a=TableSchemaSync.getSchemaTable(),t={created:[],altered:[],deleted:[],errors:[]},c=TableSchemaSync._sortTablesByDependencies(a);for(const[a,n]of c)try{const c=n.table;await TableSchemaSync.tableExists(c)?e.alterExisting&&(await TableSchemaSync.alterTable(a,e),t.altered.push(c)):e.createMissing&&(await TableSchemaSync.createTable(a,{ifNotExists:!0}),t.created.push(c))}catch(e){t.errors.push({model:a,table:n.table,error:e.message})}return t}static _sortTablesByDependencies(e){const a=Object.entries(e),t=[],c=new Set,n=new Set,s=(e,l)=>{if(!n.has(e)&&!c.has(e)){if(n.add(e),l.foreignKeys)for(const e of l.foreignKeys)if(e.references&&e.references.table){const t=e.references.table,c=a.find(([e,a])=>a.table===t);c&&s(c[0],c[1])}n.delete(e),c.add(e),t.push([e,l])}};for(const[e,t]of a)c.has(e)||s(e,t);return t}static async getSyncStatus(){const e=TableSchemaSync.getSchemaTable(),a={inSync:[],outOfSync:[],missing:[]};for(const[t,c]of Object.entries(e)){const e=c.table;if(await TableSchemaSync.tableExists(e))try{const n=await TableSchemaSync.getTableStructure(e),s=c.columns||{},l=new Set(n.columns.map(e=>e.name)),r=new Set(Object.keys(s));l.size===r.size&&[...l].every(e=>r.has(e))?a.inSync.push({model:t,table:e}):a.outOfSync.push({model:t,table:e})}catch(c){a.outOfSync.push({model:t,table:e,error:c.message})}else a.missing.push({model:t,table:e})}return a}}module.exports=TableSchemaSync;