import fs from "fs" ; import _ from "lodash" ; import path from "path" ; import util from "util" ; import { FKSpec , TableData } from "." ; import { AutoOptions , CaseFileOption , CaseOption , LangOption , makeIndent , makeTableName , pluralize , qNameSplit , recase , Relation } from "./types" ; const mkdirp = require ( 'mkdirp' ) ; /** Writes text into files from TableData.text, and writes init-models */ export class AutoWriter { tableText : { [ name ] : string ; } ; foreignKeys : { [ tableName ] : { [ fieldName ] : FKSpec ; } ; } ; relations : Relation [ ] ; space : string [ ] ; options : { caseFile ? : CaseFileOption ; caseModel ? : CaseOption ; caseProp ? : CaseOption ; directory : string ; lang ? : LangOption ; noAlias ? : boolean ; noInitModels ? : boolean ; noWrite ? : boolean ; singularize ? : boolean ; useDefine ? : boolean ; spaces ? : boolean ; indentation ? : number ; } ; constructor ( tableData , options ) { this . tableText = tableData . text as { [ name ] : string ; } ; this . foreignKeys = tableData . foreignKeys ; this . relations = tableData . relations ; this . options = options ; this . space = makeIndent ( this . options . spaces , this . options . indentation ) ; } write ( ) { if ( this . options . noWrite ) { return Promise . resolve ( ) ; } mkdirp . sync ( path . resolve ( this . options . directory || "./models" ) ) ; const tables = _ . keys ( this . tableText ) ; const promises = tables . map ( t => { return this . createFile ( t ) ; } ) ; const isTypeScript = this . options . lang === 'ts' ; const assoc = this . createAssociations ( isTypeScript ) ; const tableNames = tables . map ( t => { const [ schemaName , tableName ] = qNameSplit ( t ) ; return tableName as string ; } ) . sort ( ) ; if ( ! this . options . noInitModels ) { const initString = this . createInitString ( tableNames , assoc , this . options . lang ) ; const initFilePath = path . join ( this . options . directory , "init-models" + ( isTypeScript ? '.ts' : '.js' ) ) ; const writeFile = util . promisify ( fs . writeFile ) ; const initPromise = writeFile ( path . resolve ( initFilePath ) , initString ) ; promises . push ( initPromise ) ; } return Promise . all ( promises ) ; } private createInitString ( tableNames , assoc , lang ? ) { switch ( lang ) { case 'ts' : return this . createTsInitString ( tableNames , assoc ) ; case 'esm' : return this . createESMInitString ( tableNames , assoc ) ; case 'es6' : return this . createES5InitString ( tableNames , assoc , "const" ) ; default : return this . createES5InitString ( tableNames , assoc , "var" ) ; } } private createFile ( table ) { const [ schemaName , tableName ] = qNameSplit ( table ) ; const fileName = recase ( this . options . caseFile , tableName , this . options . singularize ) ; const filePath = path . join ( this . options . directory , fileName + ( this . options . lang === 'ts' ? '.ts' : '.js' ) ) ; const writeFile = util . promisify ( fs . writeFile ) ; return writeFile ( path . resolve ( filePath ) , this . tableText [ table ] ) ; } /** Create the belongsToMany/belongsTo/hasMany/hasOne association strings */ private createAssociations ( typeScript ) { let strBelongs = "" ; let strBelongsToMany = "" ; const sp = this . space [ 1 ] ; const rels = this . relations ; rels . forEach ( rel => { if ( rel . isM2M ) { const asprop = recase ( this . options . caseProp , pluralize ( rel . childProp ) ) ; strBelongsToMany += `${ sp }${ rel . parentModel }.belongsToMany(${ rel . childModel }, { as: '${ asprop }', through: ${ rel . joinModel }, foreignKey: "${ rel . parentId }", otherKey: "${ rel . childId }" });\n` ; } else { const asParentProp = recase ( this . options . caseProp , rel . parentProp ) ; const bAlias = this . options . noAlias ? '' : `as: "${ asParentProp }", ` ; strBelongs += `${ sp }${ rel . childModel }.belongsTo(${ rel . parentModel }, { ${ bAlias }foreignKey: "${ rel . parentId }"});\n` ; const hasRel = rel . isOne ? "hasOne" : "hasMany" ; const asChildProp = recase ( this . options . caseProp , rel . childProp ) ; const hAlias = this . options . noAlias ? '' : `as: "${ asChildProp }", ` ; strBelongs += `${ sp }${ rel . parentModel }.${ hasRel }(${ rel . childModel }, { ${ hAlias }foreignKey: "${ rel . parentId }"});\n` ; } } ) ; return strBelongsToMany + strBelongs ; } private createTsInitString ( tables , assoc ) { let str = 'import type { Sequelize } from "sequelize";\n' ; const sp = this . space [ 1 ] ; const modelNames = [ ] ; tables . forEach ( t => { const fileName = recase ( this . options . caseFile , t , this . options . singularize ) ; const modelName = makeTableName ( this . options . caseModel , t , this . options . singularize , this . options . lang ) ; modelNames . push ( modelName ) ; str += `import { ${ modelName } as _${ modelName } } from "./${ fileName }";\n` ; str += `import type { ${ modelName }Attributes, ${ modelName }CreationAttributes } from "./${ fileName }";\n` ; } ) ; str += '\nexport {\n' ; modelNames . forEach ( m => { str += `${ sp }_${ m } as ${ m },\n` ; } ) ; str += '};\n' ; str += '\nexport type {\n' ; modelNames . forEach ( m => { str += `${ sp }${ m }Attributes,\n` ; str += `${ sp }${ m }CreationAttributes,\n` ; } ) ; str += '};\n\n' ; str += 'export function initModels(sequelize: Sequelize) {\n' ; modelNames . forEach ( m => { str += `${ sp }const ${ m } = _${ m }.initModel(sequelize);\n` ; } ) ; str += "\n" + assoc ; str += `\n${ sp }return {\n` ; modelNames . forEach ( m => { str += `${ this . space [ 2 ] }${ m }: ${ m },\n` ; } ) ; str += `${ sp }};\n` ; str += '}\n' ; return str ; } private createES5InitString ( tables , assoc , vardef ) { let str = `${ vardef } DataTypes = require("sequelize").DataTypes;\n` ; const sp = this . space [ 1 ] ; const modelNames = [ ] ; tables . forEach ( t => { const fileName = recase ( this . options . caseFile , t , this . options . singularize ) ; const modelName = makeTableName ( this . options . caseModel , t , this . options . singularize , this . options . lang ) ; modelNames . push ( modelName ) ; str += `${ vardef } _${ modelName } = require("./${ fileName }");\n` ; } ) ; str += '\nfunction initModels(sequelize) {\n' ; modelNames . forEach ( m => { str += `${ sp }${ vardef } ${ m } = _${ m }(sequelize, DataTypes);\n` ; } ) ; str += "\n" + assoc ; str += `\n${ sp }return {\n` ; modelNames . forEach ( m => { str += `${ this . space [ 2 ] }${ m },\n` ; } ) ; str += `${ sp }};\n` ; str += '}\n' ; str += 'module.exports = initModels;\n' ; str += 'module.exports.initModels = initModels;\n' ; str += 'module.exports.default = initModels;\n' ; return str ; } private createESMInitString ( tables , assoc ) { let str = 'import _sequelize from "sequelize";\n' ; str += 'const DataTypes = _sequelize.DataTypes;\n' ; const sp = this . space [ 1 ] ; const modelNames = [ ] ; tables . forEach ( t => { const fileName = recase ( this . options . caseFile , t , this . options . singularize ) ; const modelName = makeTableName ( this . options . caseModel , t , this . options . singularize , this . options . lang ) ; modelNames . push ( modelName ) ; str += `import _${ modelName } from  "./${ fileName }.js";\n` ; } ) ; str += '\nexport default function initModels(sequelize) {\n' ; modelNames . forEach ( m => { str += `${ sp }const ${ m } = _${ m }.init(sequelize, DataTypes);\n` ; } ) ; str += "\n" + assoc ; str += `\n${ sp }return {\n` ; modelNames . forEach ( m => { str += `${ this . space [ 2 ] }${ m },\n` ; } ) ; str += `${ sp }};\n` ; str += '}\n' ; return str ; } }