@dreamtree-org/korm-js 1.0.60 → 1.1.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 +37 -1
- package/clients/BaseSyncTable.js +1 -1
- package/clients/SyncRunner.js +1 -0
- package/clients/mysql/index.js +1 -1
- package/clients/pg/index.js +1 -1
- package/clients/sqlite/index.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{static db=null;static dbClient=null;static dbClientClass=null;static schema=null;static resolverPath=null;static dbInstance=null;static debug=!1;static _authz=new AuthorizationService;requestInstance=null;constructor(){this.requestInstance={}}static initializeKORM({db:e,dbClient:t,schema:s,resolverPath:i=null,debug:r=!1}){this.db=e,this.dbClient=t,this.schema=s,this.resolverPath=i,this.debug=r;const a=dbClientMapper[t];if(!a)throw new Error(`Database client ${t} not found`);const n=InstanceMapper[a];if(!n)throw new Error(`Database client ${t} not found`);return this.dbClientClass=n,this.dbInstance=new n(this),this}static 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}static _resolveModelName(e){const t=this.schema||{};if(t[e])return e;return Object.keys(t).find(s=>t[s]&&t[s].table===e)||e}static authorize(e,t,s){return this._authz.registerAuthorize(this._resolveModelName(e),t,s),this}static scope(e,t){return this._authz.registerScope(this._resolveModelName(e),t),this}static resetAuthorization(){return this._authz.reset(),this}static async processRequest(e,t=null,s=null){let i=e;if(this._authz.hasRules()&&t){const r=this._resolveModelName(t),a=e&&e.action||"list";this._authz.enforce(r,a,e,s),i=this._authz.applyScope(r,a,e||{},s)}return await this.dbInstance.processRequest(i,t,s)}static async processRequestWithOthers(e,t=null,s=null){return await this.dbInstance.processRequest(e,t,s)}static async syncDatabase(){return await this.dbInstance.syncDatabase()}static async generateSchema(){return await this.dbInstance.generateSchema()}static loadModelClass(e){return this.dbInstance.hookService.loadModelClass(e)}static getModelInstance(e){return this.dbInstance.hookService.getModelInstance(e)}static 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}>`})}static _modelHasSoftDelete(e){try{const t=this.dbInstance?.hookService?.getModelInstance?.(e);return!(!t||!0!==t.hasSoftDelete)}catch{return!1}}static describeModel(e,t=null){const s=this.schema||{},i=Object.entries(s).find(([t,s])=>t===e||s&&s.table===e);if(!i)throw KormError.unknownModel({model:e,available:Object.keys(s)});const[r,a]=i,n=buildModelDescription(r,a,{softDelete:this._modelHasSoftDelete(r)});return null!=t&&this._authz.hasRules()&&(n.actions=this._authz.availableActions(r,n.actions,t)),n}static 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 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{static db=null;static dbClient=null;static dbClientClass=null;static schema=null;static resolverPath=null;static dbInstance=null;static debug=!1;static _authz=new AuthorizationService;requestInstance=null;constructor(){this.requestInstance={}}static initializeKORM({db:e,dbClient:t,schema:s,resolverPath:i=null,debug:r=!1}){this.db=e,this.dbClient=t,this.schema=s,this.resolverPath=i,this.debug=r;const a=dbClientMapper[t];if(!a)throw new Error(`Database client ${t} not found`);const n=InstanceMapper[a];if(!n)throw new Error(`Database client ${t} not found`);return this.dbClientClass=n,this.dbInstance=new n(this),this}static 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}static _resolveModelName(e){const t=this.schema||{};if(t[e])return e;return Object.keys(t).find(s=>t[s]&&t[s].table===e)||e}static authorize(e,t,s){return this._authz.registerAuthorize(this._resolveModelName(e),t,s),this}static scope(e,t){return this._authz.registerScope(this._resolveModelName(e),t),this}static resetAuthorization(){return this._authz.reset(),this}static async processRequest(e,t=null,s=null){let i=e;if(this._authz.hasRules()&&t){const r=this._resolveModelName(t),a=e&&e.action||"list";this._authz.enforce(r,a,e,s),i=this._authz.applyScope(r,a,e||{},s)}return await this.dbInstance.processRequest(i,t,s)}static async processRequestWithOthers(e,t=null,s=null){return await this.dbInstance.processRequest(e,t,s)}static async syncDatabase(e={}){return await this.dbInstance.syncDatabase(e)}static async generateSchema(){return await this.dbInstance.generateSchema()}static loadModelClass(e){return this.dbInstance.hookService.loadModelClass(e)}static getModelInstance(e){return this.dbInstance.hookService.getModelInstance(e)}static 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}>`})}static _modelHasSoftDelete(e){try{const t=this.dbInstance?.hookService?.getModelInstance?.(e);return!(!t||!0!==t.hasSoftDelete)}catch{return!1}}static describeModel(e,t=null){const s=this.schema||{},i=Object.entries(s).find(([t,s])=>t===e||s&&s.table===e);if(!i)throw KormError.unknownModel({model:e,available:Object.keys(s)});const[r,a]=i,n=buildModelDescription(r,a,{softDelete:this._modelHasSoftDelete(r)});return null!=t&&this._authz.hasRules()&&(n.actions=this._authz.availableActions(r,n.actions,t)),n}static 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
|
@@ -1664,6 +1664,40 @@ type|modifier1|modifier2|...
|
|
|
1664
1664
|
|
|
1665
1665
|
> **Orphan-row guard.** Adding a `notNull` foreign-key column to a table that **already has rows** makes the database back-fill those rows (with `0` for a numeric column, or the column default). If that value has no matching parent row, MySQL/PostgreSQL reject the new constraint with a cryptic driver error (MySQL `errno 1452`, `ER_NO_REFERENCED_ROW_2`). `syncDatabase()` now detects this **before** altering the table and throws a clear `KormError` (`code: 'SYNC_FK_ORPHAN'`) naming the child column, the parent it points at, the affected row count, and the offending back-fill value — **no data is mutated**. Resolve it by making the column `nullable` (existing rows back-fill `NULL`, which is FK-safe), giving it a `default` that exists in the parent, populating the column for existing rows before syncing, or inserting the missing parent rows.
|
|
1666
1666
|
|
|
1667
|
+
#### `syncDatabase(options)` — additive-only, resilient, dry-run
|
|
1668
|
+
|
|
1669
|
+
`syncDatabase()` accepts an optional `options` object that controls how it reconciles drift between your schema and the live database. The defaults are **safe**: it never drops data and never half-migrates silently.
|
|
1670
|
+
|
|
1671
|
+
| Option | Default | Effect |
|
|
1672
|
+
| ----------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1673
|
+
| `dropColumns` | `false` | When `false` (default), a column that exists in the database but is **absent from the schema** is **left in place** (reported as drift, never dropped). Set `true` to drop such columns. |
|
|
1674
|
+
| `prune` | — | Alias for `dropColumns`. |
|
|
1675
|
+
| `continueOnError` | `false` | When `false` (default), the first failing statement throws and aborts the run (fail-fast). When `true`, a failing statement is **skipped and collected**, and the rest of the schema still applies. |
|
|
1676
|
+
| `dryRun` | `false` | When `true`, walks the same diff **without executing anything** and returns the planned operations (destructive drops flagged). The database is not touched. |
|
|
1677
|
+
|
|
1678
|
+
**Why the additive-only default matters.** Previously `syncDatabase()` dropped any column not declared in the schema — silently destroying data for drifted/legacy columns, and aborting the entire multi-table sync if the drop was blocked (e.g. the column was referenced by a foreign key). The default is now **additive** (create tables, add columns); destructive drops are opt-in.
|
|
1679
|
+
|
|
1680
|
+
**Return value.** A default sync resolves to `undefined` (unchanged). When you pass `dryRun: true` **or** `continueOnError: true`, it resolves to a **report**:
|
|
1681
|
+
|
|
1682
|
+
```javascript
|
|
1683
|
+
// Preview what a sync would do — change nothing
|
|
1684
|
+
const plan = await korm.syncDatabase({ dryRun: true });
|
|
1685
|
+
// {
|
|
1686
|
+
// dryRun: true,
|
|
1687
|
+
// plan: [{ table: 'users', operation: 'add', column: 'bio', destructive: false }, …],
|
|
1688
|
+
// skippedDrops: [{ table: 'products', column: 'warehouse' }], // drift left in place
|
|
1689
|
+
// applied: [], errors: [],
|
|
1690
|
+
// }
|
|
1691
|
+
|
|
1692
|
+
// Drop drifted columns too
|
|
1693
|
+
await korm.syncDatabase({ dropColumns: true });
|
|
1694
|
+
|
|
1695
|
+
// Keep going past failures, then inspect what was skipped
|
|
1696
|
+
const report = await korm.syncDatabase({ continueOnError: true });
|
|
1697
|
+
// report.errors → [{ table, operation, column, message }]
|
|
1698
|
+
// report.applied → [{ table, operation, column }]
|
|
1699
|
+
```
|
|
1700
|
+
|
|
1667
1701
|
**Special Default Values:**
|
|
1668
1702
|
|
|
1669
1703
|
- `now` or `now()` → `CURRENT_TIMESTAMP`
|
|
@@ -1729,7 +1763,9 @@ const result = await korm.processRequestWithOthers(requestBody, modelName, conte
|
|
|
1729
1763
|
// Set schema manually
|
|
1730
1764
|
korm.setSchema(schemaObject);
|
|
1731
1765
|
|
|
1732
|
-
// Sync database with schema (creates
|
|
1766
|
+
// Sync database with schema (additive-only by default: creates tables, adds
|
|
1767
|
+
// columns; never drops drifted columns unless you opt in). Options:
|
|
1768
|
+
// { dropColumns | prune, continueOnError, dryRun } — see "syncDatabase(options)".
|
|
1733
1769
|
await korm.syncDatabase();
|
|
1734
1770
|
|
|
1735
1771
|
// Generate schema from existing database
|
package/clients/BaseSyncTable.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const logger=require("../Logger"),KormError=require("../KormError"),ENGINE_WARNINGS=new Set;function warnOnce(e,t){ENGINE_WARNINGS.has(e)||(ENGINE_WARNINGS.add(e),logger.warn(t))}const BASE_TYPE_DISPATCHER={VARCHAR:(e,t,n)=>e.string(t,n.size||255),CHAR:(e,t,n)=>e.string(t,n.size||255),TEXT:(e,t)=>e.text(t),MEDIUMTEXT:(e,t)=>e.text(t),LONGTEXT:(e,t)=>e.text(t),INT:(e,t)=>e.integer(t),INTEGER:(e,t)=>e.integer(t),MEDIUMINT:(e,t)=>e.integer(t),SMALLINT:(e,t)=>e.integer(t),BIGINT:(e,t)=>e.bigInteger(t),TINYINT:(e,t,n)=>e.tinyint?e.tinyint(t):e.specificType(t,n.size?`TINYINT(${n.size})`:"TINYINT"),BOOLEAN:(e,t)=>e.boolean(t),BOOL:(e,t)=>e.boolean(t),DATE:(e,t)=>e.date(t),DATETIME:(e,t)=>e.dateTime(t),TIMESTAMP:(e,t)=>e.timestamp(t),TIME:(e,t)=>e.time(t),JSON:(e,t)=>e.json(t),FLOAT:(e,t)=>e.float(t),DOUBLE:(e,t)=>e.double?e.double(t):e.float(t),REAL:(e,t)=>e.double?e.double(t):e.float(t),DECIMAL:(e,t)=>e.decimal(t),NUMERIC:(e,t)=>e.decimal(t),BINARY:(e,t)=>e.binary(t),VARBINARY:(e,t)=>e.binary(t),BLOB:(e,t)=>e.binary(t),UUID:(e,t)=>e.uuid?e.uuid(t):e.string(t,36)},NUMERIC_TYPES=new Set(["INT","INTEGER","MEDIUMINT","SMALLINT","BIGINT","TINYINT","FLOAT","DOUBLE","REAL","DECIMAL","NUMERIC"]),COLUMN_STRING_SUFFIXES=[e=>e.size?`|size:${e.size}`:"",e=>e.isUnsigned?"|unsigned":"",e=>e.primary?"|primaryKey":"",e=>e.autoIncrement?"|autoIncrement":"",e=>e.nullable?"":"|notNull",e=>e.unique?"|unique":"",e=>null!=e.default&&""!==e.default?`|default:${e.default}`:"",e=>e.onUpdate?`|onUpdate:${e.onUpdate}`:"",e=>e.comment?`|comment:${e.comment}`:"",e=>e.hasForeignKey&&e.foreignMapTables?.[0]?`|foreignKey:${e.foreignMapTables[0].table}:${e.foreignMapTables[0].column}`:""];class BaseSyncTable{constructor(e,t,n=null){this.db=e,this.utils=t,this.controllerWrapper=n}_getClientName(){throw new Error("_getClientName must be overridden by engine subclass")}async existsTable(e){return this.db.schema.hasTable(e)}async syncTable(e
|
|
1
|
+
const logger=require("../Logger"),KormError=require("../KormError"),SyncRunner=require("./SyncRunner"),ENGINE_WARNINGS=new Set;function warnOnce(e,t){ENGINE_WARNINGS.has(e)||(ENGINE_WARNINGS.add(e),logger.warn(t))}const BASE_TYPE_DISPATCHER={VARCHAR:(e,t,n)=>e.string(t,n.size||255),CHAR:(e,t,n)=>e.string(t,n.size||255),TEXT:(e,t)=>e.text(t),MEDIUMTEXT:(e,t)=>e.text(t),LONGTEXT:(e,t)=>e.text(t),INT:(e,t)=>e.integer(t),INTEGER:(e,t)=>e.integer(t),MEDIUMINT:(e,t)=>e.integer(t),SMALLINT:(e,t)=>e.integer(t),BIGINT:(e,t)=>e.bigInteger(t),TINYINT:(e,t,n)=>e.tinyint?e.tinyint(t):e.specificType(t,n.size?`TINYINT(${n.size})`:"TINYINT"),BOOLEAN:(e,t)=>e.boolean(t),BOOL:(e,t)=>e.boolean(t),DATE:(e,t)=>e.date(t),DATETIME:(e,t)=>e.dateTime(t),TIMESTAMP:(e,t)=>e.timestamp(t),TIME:(e,t)=>e.time(t),JSON:(e,t)=>e.json(t),FLOAT:(e,t)=>e.float(t),DOUBLE:(e,t)=>e.double?e.double(t):e.float(t),REAL:(e,t)=>e.double?e.double(t):e.float(t),DECIMAL:(e,t)=>e.decimal(t),NUMERIC:(e,t)=>e.decimal(t),BINARY:(e,t)=>e.binary(t),VARBINARY:(e,t)=>e.binary(t),BLOB:(e,t)=>e.binary(t),UUID:(e,t)=>e.uuid?e.uuid(t):e.string(t,36)},NUMERIC_TYPES=new Set(["INT","INTEGER","MEDIUMINT","SMALLINT","BIGINT","TINYINT","FLOAT","DOUBLE","REAL","DECIMAL","NUMERIC"]),COLUMN_STRING_SUFFIXES=[e=>e.size?`|size:${e.size}`:"",e=>e.isUnsigned?"|unsigned":"",e=>e.primary?"|primaryKey":"",e=>e.autoIncrement?"|autoIncrement":"",e=>e.nullable?"":"|notNull",e=>e.unique?"|unique":"",e=>null!=e.default&&""!==e.default?`|default:${e.default}`:"",e=>e.onUpdate?`|onUpdate:${e.onUpdate}`:"",e=>e.comment?`|comment:${e.comment}`:"",e=>e.hasForeignKey&&e.foreignMapTables?.[0]?`|foreignKey:${e.foreignMapTables[0].table}:${e.foreignMapTables[0].column}`:""];class BaseSyncTable{constructor(e,t,n=null){this.db=e,this.utils=t,this.controllerWrapper=n}_getClientName(){throw new Error("_getClientName must be overridden by engine subclass")}async existsTable(e){return this.db.schema.hasTable(e)}async syncTable(e,t={}){return new SyncRunner(this,t).runTable(e)}async syncDatabase(e={}){if(!this.controllerWrapper?.schema)throw new Error("controllerWrapper.schema not set.");return new SyncRunner(this,e).runDatabase(this.controllerWrapper.schema)}_orderTablesByDependency(e){const t=Object.keys(e),n=this._buildFkDependencyMap(e,t),r=this._topoSort(t,n);if(r.length<t.length){const e=new Set(r),n=t.filter(t=>!e.has(t));logger.warn("syncDatabase: circular foreign-key dependency among",n,"— creating in declaration order; FK constraints may need a second pass."),r.push(...n)}return r}_buildFkDependencyMap(e,t){const n=new Map;for(const r of t){n.set(r,r);const t=e[r]?.table;t&&n.set(t,r)}const r=new Map;for(const a of t){const t=new Set;for(const r of this._foreignTargetsOf(e[a])){const e=n.get(r);e&&e!==a&&t.add(e)}r.set(a,t)}return r}_topoSort(e,t){const n=[],r=new Set;let a=!0;for(;n.length<e.length&&a;){a=!1;for(const s of e)!r.has(s)&&this._depsSatisfied(t.get(s),r)&&(n.push(s),r.add(s),a=!0)}return n}_depsSatisfied(e,t){for(const n of e)if(!t.has(n))return!1;return!0}_foreignTargetsOf(e){const t=[];for(const[n,r]of Object.entries(e?.columns||{})){const e="string"==typeof r?this.utils.formatColumnSchema(n,r):r;if(e?.hasForeignKey&&Array.isArray(e.foreignMapTables))for(const n of e.foreignMapTables)n?.table&&t.push(n.table)}return t}async syncSeedData(e,t){if(!e.seed||!Array.isArray(e.seed)||0===e.seed.length)return;const n=await this.db(e.table).count("* as n").first();Number(n?.n)>0?logger.info("Seed data already synced for",t):(await this.db(e.table).insert(e.seed),logger.info("Seed data synced for",t))}async generateSchema(){const e=await this._listTables(),t={},n=this._getHelperUtility();for(const r of e){const e=n?n.modelName(r):r;t[e]={table:r,alias:e,modelName:e,columns:this.getColumnString(await this.getCurrentColumns(r)),seed:[],hasRelations:await this._getRelations(r),indexes:[]}}return t}async createTable(e){await this.db.schema.createTable(e.table,t=>{for(const[n,r]of Object.entries(e.columns))this._applyColumnToBuilder(t,this._resolveColumnFrm(n,r))})}async alterTable(e,t){if(!t||"object"!=typeof t)throw new Error("alterations must be an object");(t.add?.length||0)+(t.drop?.length||0)+(t.modify?.length||0)>0?await this.db.schema.alterTable(e,e=>{for(const n of t.add||[])this._applyColumnToBuilder(e,n);for(const n of t.drop||[])e.dropColumn(n.name);for(const n of t.modify||[]){const t=this._applyColumnToBuilder(e,n);t&&"function"==typeof t.alter&&t.alter()}}):logger.info("No alterations to apply for",e)}async dropTable(e){await this.db.schema.dropTableIfExists(e)}async _assertNoOrphanForeignKeys(e,t){const n=[];for(const r of t?.add||[]){const t=await this._detectOrphanForNewFkColumn(e,r);t&&n.push(t)}if(n.length)throw KormError.foreignKeyOrphans({table:e,offenders:n})}async _detectOrphanForNewFkColumn(e,t){const n=this._fkOrphanProbe(t);if(!n)return null;const r=await this._tableRowCount(e);return 0===r?null:await this.existsTable(n.parentTable)?await this._parentHasValue(n.parentTable,n.parentColumn,n.backfillValue)?null:{column:t.name,parentTable:n.parentTable,parentColumn:n.parentColumn,rowsAffected:r,backfillValue:n.backfillValue}:null}_fkOrphanProbe(e){if(!e?.hasForeignKey)return null;const t=e.foreignMapTables?.[0];if(!t?.table)return null;const n=null!=e.default&&""!==e.default;return e.nullable&&!n?null:{parentTable:t.table,parentColumn:t.column||"id",backfillValue:n?e.default:this._impliedBackfillValue(e)}}_impliedBackfillValue(e){const t=String(e.type||"").toUpperCase(),n=String(e.columnType||"").toUpperCase().match(/^[A-Z]+/);return NUMERIC_TYPES.has(t)||n&&NUMERIC_TYPES.has(n[0])?0:""}async _tableRowCount(e){const t=await this.db(e).count("* as n").first();return Number(t?.n)||0}async _parentHasValue(e,t,n){return!!await this.db(e).where(t,n).first()}async getCurrentColumns(e){const t=await this.db(e).columnInfo(),n={};for(const[e,r]of Object.entries(t))n[e]=this._formatColumnInfo(e,r);return n}_formatColumnInfo(e,t){const n=String(t.type||"").toLowerCase(),r=n.match(/^([a-z_]+)(?:\((\d+)(?:,\s*\d+)?\))?/);return{name:e,type:(r?r[1]:n).toUpperCase(),size:(r&&r[2]?Number(r[2]):t.maxLength||null)||null,nullable:!1!==t.nullable,default:this._parseDefault(t.defaultValue),primary:!1,unique:!1,autoIncrement:!1,isUnsigned:!1,hasForeignKey:!1,foreignMapTables:[],onUpdate:null,comment:""}}_parseDefault(e){if(null==e)return null;const t=String(e).trim();return""===t?null:t.replace(/^'+|'+$/g,"")}hasColumnChanged(e,t){return!1}async getAlterations(e){const t={add:[],drop:[],modify:[]},n=await this.getCurrentColumns(e.table);for(const[r,a]of Object.entries(e.columns)){const e=this._resolveColumnFrm(r,a),s=n[r];s?this.hasColumnChanged(s,e)&&t.modify.push(e):t.add.push(e)}for(const r of Object.keys(n))e.columns[r]||t.drop.push({name:r});return t}getColumnString(e){return Object.keys(e).reduce((t,n)=>{const r=e[n],a=String(r.type||"").toLowerCase();return t[n]=COLUMN_STRING_SUFFIXES.reduce((e,t)=>e+t(r),a),t},{})}_resolveColumnFrm(e,t){const n="string"==typeof t?this.utils.formatColumnSchema(e,t):t;return this._alignForeignKeyType(n)}_alignForeignKeyType(e){if(!e?.hasForeignKey)return e;const t=e.foreignMapTables?.[0];if(!t?.table)return e;const n=this._resolveParentColumnFrm(t);return n?{...e,...this._matchedForeignKeyType(n)}:e}_resolveParentColumnFrm(e){const t=this._findSchemaColumns(e.table);if(!t)return null;const n=t[e.column||"id"];return null==n?null:"string"==typeof n?this.utils.formatColumnSchema(e.column||"id",n):n}_findSchemaColumns(e){const t=this.controllerWrapper?.schema;if(!t)return null;for(const n of Object.keys(t)){const r=t[n];if(r&&(r.table===e||n===e))return r.columns||null}return null}_matchedForeignKeyType(e){return e.autoIncrement&&e.primary?{type:"BIGINT",size:null,columnType:"BIGINT",isUnsigned:!0}:{type:e.type,size:e.size,columnType:e.columnType,isUnsigned:e.isUnsigned}}_applyColumnToBuilder(e,t){if(t.autoIncrement&&t.primary)return this._buildIncrementsColumn(e,t);const n=this._typeBuilder(e,t.name,t);return this._applyColumnModifiers(n,t),n}_buildIncrementsColumn(e,t){const n=e.increments(t.name);return t.comment&&n.comment(t.comment),n}_applyColumnModifiers(e,t){if(this._applyConstraintModifiers(e,t),this._applyNullabilityAndDefault(e,t),t.comment&&e.comment(t.comment),t.hasForeignKey&&t.foreignMapTables?.[0]){const n=t.foreignMapTables[0];e.references(n.column||"id").inTable(n.table)}}_applyConstraintModifiers(e,t){t.primary&&e.primary(),t.unique&&e.unique(),t.isUnsigned&&this._supportsUnsigned()&&e.unsigned()}_applyNullabilityAndDefault(e,t){t.nullable?e.nullable():e.notNullable(),null!=t.default&&""!==t.default&&e.defaultTo(this._renderDefault(t.default))}_typeBuilder(e,t,n){const r=String(n.type||"").toUpperCase(),a=this._typeDispatcher()[r];return a?a(e,t,n):e.specificType(t,n.columnType||(n.size?`${r}(${n.size})`:r))}_typeDispatcher(){return BASE_TYPE_DISPATCHER}_renderDefault(e){const t=String(e).trim();return"CURRENT_TIMESTAMP"===t.toUpperCase()||"NOW()"===t.toUpperCase()?this.db.fn.now():/^-?\d+(\.\d+)?$/.test(t)?Number(t):"true"===t||"false"===t?"true"===t:t}_supportsUnsigned(){return!0}_getHelperUtility(){try{return new(require(`./${this._getClientName()}/HelperUtility`))}catch{return null}}async _applyExtras(e){}async _getRelations(e){return{}}async _listTables(){throw new Error("_listTables must be overridden by engine subclass")}_warnOnUnsupportedModifier(e,t,n){warnOnce(`${this._getClientName()}.${e}`,`[${this._getClientName()}] '${e}' modifier is not supported on this engine (seen on ${t}.${n}). See docs/agents/05-multi-db-parity.md.`)}}module.exports=BaseSyncTable;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const logger=require("../Logger");function normalizeOptions(t={}){return{dropColumns:!!(null!=t.dropColumns?t.dropColumns:t.prune),continueOnError:!!t.continueOnError,dryRun:!!t.dryRun}}class SyncRunner{constructor(t,o={}){this.sync=t,this.opts=normalizeOptions(o),this.report={dryRun:this.opts.dryRun,applied:[],plan:[],skippedDrops:[],errors:[]}}result(){return this.opts.dryRun||this.opts.continueOnError?this.report:void 0}async runDatabase(t){for(const o of this.sync._orderTablesByDependency(t))await this._syncTable(t[o]);return this.opts.dryRun||logger.info("Database synced by SyncTable..."),this.result()}async runTable(t){return await this._syncTable(t),this.result()}async _syncTable(t){const o=t.table;(await this.sync.existsTable(o)?await this._alter(t,o):await this._create(t,o))&&!this.opts.dryRun&&await this._extrasAndSeed(t,o)}async _create(t,o){if(this.opts.dryRun)return this.report.plan.push({table:o,operation:"createTable",column:null,destructive:!1}),!0;const r=await this._attempt({table:o,operation:"createTable",column:null},()=>this.sync.createTable(t));return r&&this.report.applied.push({table:o,operation:"createTable",column:null}),r}async _alter(t,o){const r=await this.sync.getAlterations(t);if(this._recordDrops(o,r),this.opts.dryRun)return this._planAlter(o,r),!0;const n={add:await this._screenOrphans(o,r.add||[]),drop:this.opts.dropColumns&&r.drop||[],modify:r.modify||[]};return await this._applyWork(o,n),!0}_recordDrops(t,o){if(!this.opts.dropColumns)for(const r of o.drop||[])this.report.skippedDrops.push({table:t,column:r.name}),logger.warn(`syncDatabase: column "${t}.${r.name}" exists in the database but not in the schema; left in place (additive-only). Pass { dropColumns: true } to drop it.`)}_planAlter(t,o){for(const r of o.add||[])this.report.plan.push({table:t,operation:"add",column:r.name,destructive:!1});for(const r of o.modify||[])this.report.plan.push({table:t,operation:"modify",column:r.name,destructive:!1});if(this.opts.dropColumns)for(const r of o.drop||[])this.report.plan.push({table:t,operation:"drop",column:r.name,destructive:!0})}async _screenOrphans(t,o){if(!this.opts.continueOnError)return await this.sync._assertNoOrphanForeignKeys(t,{add:o}),o;const r=[];for(const n of o){const o=await this.sync._detectOrphanForNewFkColumn(t,n);o?this.report.errors.push({table:t,operation:"add",column:n.name,message:`adding FK column would orphan ${o.rowsAffected} row(s) with no matching ${o.parentTable}.${o.parentColumn}`}):r.push(n)}return r}async _applyWork(t,o){const r=this._ops(o);if(0!==r.length)if(this.opts.continueOnError)for(const o of r){await this._attempt({table:t,operation:o.operation,column:o.column},()=>this.sync.alterTable(t,this._single(o)))&&this.report.applied.push({table:t,operation:o.operation,column:o.column})}else{await this.sync.alterTable(t,o);for(const o of r)this.report.applied.push({table:t,operation:o.operation,column:o.column})}}_ops(t){const o=[];for(const r of t.add||[])o.push({operation:"add",column:r.name,frm:r});for(const r of t.drop||[])o.push({operation:"drop",column:r.name,name:r.name});for(const r of t.modify||[])o.push({operation:"modify",column:r.name,frm:r});return o}_single(t){return"add"===t.operation?{add:[t.frm],drop:[],modify:[]}:"drop"===t.operation?{add:[],drop:[{name:t.name}],modify:[]}:{add:[],drop:[],modify:[t.frm]}}async _extrasAndSeed(t,o){await this._attempt({table:o,operation:"extras",column:null},()=>this.sync._applyExtras(t)),await this._attempt({table:o,operation:"seed",column:null},()=>this.sync.syncSeedData(t,o))}async _attempt(t,o){try{return await o(),!0}catch(o){if(!this.opts.continueOnError)throw o;return this.report.errors.push({...t,message:o.message}),!1}}}module.exports=SyncRunner;
|
package/clients/mysql/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const BaseWrapper=require("../BaseWrapper"),BaseUtility=require("./BaseUtility"),SyncTable=require("./SyncTable"),CurdTable=require("./CurdTable");class MySQLWrapper extends BaseWrapper{constructor(e){super(e),this.db=e.db,this.utils=new BaseUtility,this.sync=new SyncTable(this.db,this.utils,e),this.curd=new CurdTable(this.db,this.utils,e)}async syncDatabase(){return this.sync.syncDatabase()}async processRequest(...e){return this.curd.processRequest(...e)}async generateSchema(){return this.sync.generateSchema()}}module.exports=MySQLWrapper;
|
|
1
|
+
const BaseWrapper=require("../BaseWrapper"),BaseUtility=require("./BaseUtility"),SyncTable=require("./SyncTable"),CurdTable=require("./CurdTable");class MySQLWrapper extends BaseWrapper{constructor(e){super(e),this.db=e.db,this.utils=new BaseUtility,this.sync=new SyncTable(this.db,this.utils,e),this.curd=new CurdTable(this.db,this.utils,e)}async syncDatabase(e={}){return this.sync.syncDatabase(e)}async processRequest(...e){return this.curd.processRequest(...e)}async generateSchema(){return this.sync.generateSchema()}}module.exports=MySQLWrapper;
|
package/clients/pg/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const BaseWrapper=require("../BaseWrapper"),BaseUtility=require("./BaseUtility"),SyncTable=require("./SyncTable"),CurdTable=require("./CurdTable");class PostgresWrapper extends BaseWrapper{constructor(e){super(e),this.db=e.db,this.utils=new BaseUtility,this.sync=new SyncTable(this.db,this.utils,e),this.curd=new CurdTable(this.db,this.utils,e)}async syncDatabase(){return this.sync.syncDatabase()}async processRequest(...e){return this.curd.processRequest(...e)}async generateSchema(){return this.sync.generateSchema()}}module.exports=PostgresWrapper;
|
|
1
|
+
const BaseWrapper=require("../BaseWrapper"),BaseUtility=require("./BaseUtility"),SyncTable=require("./SyncTable"),CurdTable=require("./CurdTable");class PostgresWrapper extends BaseWrapper{constructor(e){super(e),this.db=e.db,this.utils=new BaseUtility,this.sync=new SyncTable(this.db,this.utils,e),this.curd=new CurdTable(this.db,this.utils,e)}async syncDatabase(e={}){return this.sync.syncDatabase(e)}async processRequest(...e){return this.curd.processRequest(...e)}async generateSchema(){return this.sync.generateSchema()}}module.exports=PostgresWrapper;
|
package/clients/sqlite/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const BaseWrapper=require("../BaseWrapper"),BaseUtility=require("./BaseUtility"),SyncTable=require("./SyncTable"),CurdTable=require("./CurdTable");class SQLiteWrapper extends BaseWrapper{constructor(e){super(e),this.db=e.db,this.utils=new BaseUtility,this.sync=new SyncTable(this.db,this.utils,e),this.curd=new CurdTable(this.db,this.utils,e)}async syncDatabase(){return this.sync.syncDatabase()}async processRequest(...e){return this.curd.processRequest(...e)}async generateSchema(){return this.sync.generateSchema()}}module.exports=SQLiteWrapper;
|
|
1
|
+
const BaseWrapper=require("../BaseWrapper"),BaseUtility=require("./BaseUtility"),SyncTable=require("./SyncTable"),CurdTable=require("./CurdTable");class SQLiteWrapper extends BaseWrapper{constructor(e){super(e),this.db=e.db,this.utils=new BaseUtility,this.sync=new SyncTable(this.db,this.utils,e),this.curd=new CurdTable(this.db,this.utils,e)}async syncDatabase(e={}){return this.sync.syncDatabase(e)}async processRequest(...e){return this.curd.processRequest(...e)}async generateSchema(){return this.sync.generateSchema()}}module.exports=SQLiteWrapper;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dreamtree-org/korm-js",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.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",
|