Created
November 2, 2021 08:59
-
-
Save NiyongaboEric/d6a8423382e9f34e870d78da20b7da2c to your computer and use it in GitHub Desktop.
Convert sequerize model generated in JavaScript (.js) into Typescript (ts)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fs = require ('fs'); | |
const path = require ('path'); | |
const saveApiModelsPath = 'src/database/models'; // Path to your JS models folder | |
const saveDatabaseModelsPath = 'src/database/models_ts'; // Path to your TS models folder | |
const matchDataTypes = { | |
BOOLEAN: 'boolean', | |
BIGINT: 'number', | |
INTEGER: 'number', | |
DECIMAL: 'number', | |
DOUBLE: 'number', | |
FLOAT: 'number', | |
SMALLINT: 'number', | |
DATEONLY: 'Date', | |
DATE: 'Date', | |
STRING: 'string', | |
TEXT: 'string', | |
JSONB: 'string', | |
ARRAY: [], | |
ENUM: '', | |
}; | |
const dataTypesList = [ | |
'DataTypes.BOOLEAN', 'DataTypes.BIGINT', 'DataTypes.INTEGER', | |
'DataTypes.DECIMAL', 'DataTypes.STRING', 'DataTypes.DATEONLY', | |
'DataTypes.DATE', 'DataTypes.DOUBLE', 'DataTypes.TEXT', | |
'DataTypes.FLOAT', 'DataTypes.SMALLINT', 'DataTypes.JSONB', | |
]; | |
const filesIgnore = ['index.js']; | |
const generateMainFileTemplate = ( | |
modelAssociationImportText, | |
className, | |
generatedTypesPlainText, | |
relationshipKeyText, | |
associationObjectText, | |
modelAttributes, | |
tableInfo, | |
modelAssociationList, | |
) => { | |
const output = ` | |
import { Model, DataTypes, Association } from 'sequelize'; | |
import { sequelize } from './connection'; | |
${modelAssociationImportText} | |
/** | |
* ${className} Class Model | |
*/ | |
class ${className} extends Model { | |
${generatedTypesPlainText} | |
public readonly created_at!: Date; | |
public readonly updated_at!: Date; | |
${relationshipKeyText} | |
public static associations: { | |
${associationObjectText} | |
}; | |
} | |
${className}.init( | |
${modelAttributes}, | |
${tableInfo} | |
); | |
${modelAssociationList} | |
export default ${className}; | |
`; | |
return output; | |
}; | |
const saveTemplateAsFile = (dirPath, filename, fileContent) => { | |
/** | |
* If directory path not exist create it | |
* src/database/models_ts | |
* | |
*/ | |
if (!fs.existsSync(dirPath)){ | |
fs.mkdirSync(dirPath); | |
} | |
fs.writeFile(`${dirPath}/${filename}`, fileContent, (err) => { | |
if (err) throw error | |
}); | |
}; | |
/** | |
* Read Save API V3 models | |
* | |
*/ | |
fs.readdirSync(saveApiModelsPath).map(fileNameModels => { | |
/** | |
* Read a content inside each file name module | |
* Ignore config files | |
*/ | |
if (!filesIgnore.includes(fileNameModels)) { | |
const pathToEachFileModel = path.join(saveApiModelsPath, fileNameModels) | |
fs.readFile( | |
pathToEachFileModel, { | |
encoding:'utf8', | |
flag:'r' | |
}, | |
((err, data) => { | |
if (err) throw err; | |
/** | |
* Update Save Database model | |
* Create a file or update current file or overwite existing file | |
* | |
*/ | |
let modelAssociationList; | |
let associationModelAsList; | |
let className; | |
let tableInfo; | |
let modelAttributes; | |
let generatedTypesPlainText = ''; | |
let relationshipKeyText = ''; | |
let associationObjectText = ''; | |
let modelAssociationImportText=''; | |
const findClassName = data.replace(/\s\s+/g, '').match(/(?<=sequelize\.define\().+?(?=\,)/g); | |
if (findClassName) { | |
className = findClassName[0].replace(/['"]+/g, ''); | |
} | |
/** | |
* Grab Save api attributes | |
* Parse it with JSON.parse | |
* Convert result with JSON.stringfy | |
* Generates types | |
* | |
*/ | |
const findAttributeData = data | |
.replace(/\/\/.*/g, '') | |
.replace(/\"/g, "'") | |
.replace(/\s+/g, '').match(/(?<=sequelize.define\('\S+',).*(?=,{tableName)/g) | |
const findAttributesText = findAttributeData ? findAttributeData[0] : ''; | |
if (findAttributesText) { | |
const attributesTextFormatedKeys = findAttributesText.replace(/(?<=[{,])[a-zA-Z_]*(?=\:)/g, (matched) => { | |
return `"${matched}"` | |
}); | |
const attributesTextFormatedType = attributesTextFormatedKeys.replace(/(DataTypes)\.(BOOLEAN|BIGINT|INTEGER|DECIMAL|STRING|DATEONLY|DATE|DOUBLE|TEXT|FLOAT|SMALLINT|JSONB)/g, (matched) => { | |
return `"${matched}"` | |
}) | |
.replace(/(?<="get").*(?=;})/g, '') | |
.replace(/"get";}/g, '') | |
.replace(/DataTypes\.ARRAY\("DataTypes\.STRING"\)/g, '"DataTypes.ARRAY(DataTypes.STRING)"') | |
.replace(/DataTypes\.ARRAY\("DataTypes\.BIGINT"\)/g, '"DataTypes.ARRAY(DataTypes.BIGINT)"') | |
.replace(/,}/g, '}') | |
.replace(/'/g, '"') | |
.replace(/DataTypes\.ENUM\("blocked","unblocked"\)/g, '"DataTypes.ENUM(\'blocked\',\'unblocked\')"') | |
.replace(/moment\("\d{4}-\d{2}-\d{2}"\)\.format\(\)/g, null) | |
.replace(/moment\(\).format\(\)/g, null) | |
.replace(/uuid\.v4\(\)/g, null) | |
.replace(/DataTypes\.ENUM\("deactivated","activated"\)/g, '"DataTypes.ENUM(\'deactivated\',\'activated\')"') | |
const attributesObject = JSON.parse(attributesTextFormatedType.replace(',}', '}')); | |
modelAttributes = JSON.stringify(attributesObject, undefined, 2); | |
Object | |
.entries(attributesObject) | |
.forEach((item) => { | |
let foundType; | |
let actualType; | |
let listType | |
/** | |
* id | |
*/ | |
if (item[0] === 'id') { | |
generatedTypesPlainText += `\tpublic id!: number;\n`; | |
} | |
/** | |
* DataTypes.*(type) | |
*/ | |
if (dataTypesList.includes(item[1])) { | |
foundType = item[1].split('.')[1]; | |
actualType = matchDataTypes[foundType] | |
generatedTypesPlainText += `\t${item[0]}!: ${actualType};\n`; | |
} | |
/** | |
* Object | |
* { type: DataTypes.*(type) } | |
*/ | |
if (typeof item[1] === 'object' && item[0] !== 'id') { | |
foundType = item[1].type.split('.')[1]; | |
actualType = matchDataTypes[foundType]; | |
if (actualType) { | |
generatedTypesPlainText += `\t${item[0]}!: ${actualType};\n` | |
} | |
} | |
/** | |
* DataTypes ARRAYS || ENUM | |
* DataTypes.ARRAY(DataTypes.(type)) | |
*/ | |
const dataTypeSub = /(DataTypes\.ARRAY)|(DataTypes\.ENUM)/ | |
const findTypeArray = new RegExp(dataTypeSub); | |
if(typeof item[1] === "string" && findTypeArray.test(item[1])) { | |
listType = item[1].match(/(.*?)(?=\()/)[0]; | |
if (listType === 'DataTypes.ARRAY') { | |
foundType = item[1].match(/(?<=\()(.*?)(?=\))/g)[0].split('.')[1]; | |
actualType = matchDataTypes[foundType]; | |
generatedTypesPlainText += `\t${item[0]}!: ${actualType}[];\n`; | |
} | |
if (listType === 'DataTypes.ENUM') { | |
foundType = item[1].match(/(?<=\()(.*?)(?=\))/g)[0].replace(/\,/g, '|'); | |
generatedTypesPlainText += `\t${item[0]}!: ${foundType};\n`; | |
} | |
} | |
/** | |
* DataTypes ARRAYS || ENUM || ANY | |
* { type: DataTypes.*(type) } | |
*/ | |
if (typeof item[1] === 'object' ) { | |
listType = item[1].type.match(/(.*?)(?=\()/); | |
const isType = listType ? listType[0] : ''; | |
if (isType === 'DataTypes.ARRAY') { | |
foundType = item[1].type.match(/(?<=\()(.*?)(?=\))/g)[0].split('.')[1]; | |
actualType = matchDataTypes[foundType]; | |
generatedTypesPlainText += `\t${item[0]}!: ${actualType}[];\n`; | |
} | |
if (isType === 'DataTypes.ENUM') { | |
foundType = item[1].type.match(/(?<=\()(.*?)(?=\))/g)[0].replace(/\,/g, '|'); | |
generatedTypesPlainText += `\t${item[0]}!: ${foundType};\n`; | |
} | |
} | |
}) | |
} | |
/** | |
* Find associations used in save-api-v3 | |
* Generate import | |
* | |
*/ | |
const findModelToImport = data.replace(/\s+/g, '').match(/(?<=models\.).*?(?=\,|\.)/g); | |
if (findModelToImport) { | |
const removeModuleDuplicate = new Set(findModelToImport); | |
for (let item of removeModuleDuplicate) { | |
modelAssociationImportText += `import ${item} from './${item}';\n`; | |
} | |
} | |
/** | |
* Copy information of table | |
* Paste it | |
* | |
*/ | |
const findtableInfo = data.replace(/\s+/g, '').match(/(\{tableName\:).+?(\}\))/g); | |
if (findtableInfo) { | |
tableInfo = findtableInfo[0].replace(/\)/g, ''); | |
} | |
/** | |
* Find associations and relationship used in save-api-v3 | |
* Removed "models." | |
* Paste it | |
*/ | |
const findModelAssociation = data | |
.replace(/\/\/.*/g, '') | |
.match(/(.*?\(models\.)(.*)([\s\S]*)(\}\))/g); | |
if (findModelAssociation) { | |
const RemoveModelDot = findModelAssociation[0].replace(/(models\.)/g, ''); | |
modelAssociationList = RemoveModelDot; | |
} else { | |
modelAssociationList = ''; | |
} | |
/** | |
* Find Model key used | |
* Find foreign key used | |
* | |
*/ | |
const findAssociationUsed = data | |
.replace(/\s\s+/g, '') | |
.match(/(?<=\(models\.)(.+?)(as\:).*?(\}\))/g) | |
if (findAssociationUsed) { | |
associationModelAsList = findAssociationUsed.map((item, index) => { | |
const result = {}; | |
const findModel = item.replace(/\)/g, '').match(/.*?(?=\,)/g)[0]; | |
const findAsKey = item.replace(/\)/g, '').match(/(as:.*?(\w+))/)[2]; | |
const findForeignKey = item.replace(/\)/g, '').match(/(foreignKey:.*?(\w+))/)[2]; | |
const targetKey = item.replace(/\)/g, '').match(/(targetKey:.*?(\w+))/) | |
const throughKey = item.replace(/\)/g, '').match(/(through:.*?(\w+))/) | |
result['findModel'] = findModel; | |
result['findAsKey'] = findAsKey; | |
result['findForeignKey'] = findForeignKey; | |
relationshipKeyText += `\tpublic readonly ${findAsKey}?: ${findModel};\n`; | |
if (index !== 0) { | |
associationObjectText+='\n' | |
} | |
associationObjectText += `\t\t${findAsKey}: Association<${findModel}, ${className}>;`; | |
if (targetKey) { | |
result['targetKey']=targetKey[2]; | |
} | |
if (throughKey) { | |
result['throughKey']=throughKey[2]; | |
} | |
return result; | |
}); | |
} | |
const modelFilename = `${className}.ts`; | |
const outputMainFile = generateMainFileTemplate( | |
modelAssociationImportText, className, | |
generatedTypesPlainText, relationshipKeyText, | |
associationObjectText, modelAttributes, tableInfo, | |
modelAssociationList, | |
); | |
saveTemplateAsFile(saveDatabaseModelsPath, modelFilename, outputMainFile); | |
}) | |
); | |
} | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* InterventionAread Model | |
* JS file | |
* Example | |
*/ | |
'use strict'; | |
module.exports = (sequelize, DataTypes) => { | |
const InterventionArea = sequelize.define('InterventionArea', { | |
id: { | |
allowNull: false, | |
autoIncrement: true, | |
primaryKey: true, | |
type: DataTypes.INTEGER, | |
}, | |
resource_id: DataTypes.INTEGER, | |
resource: DataTypes.STRING, | |
province: DataTypes.STRING, | |
districts: DataTypes.ARRAY(DataTypes.STRING), | |
sectors: DataTypes.ARRAY(DataTypes.STRING), | |
cells: DataTypes.ARRAY(DataTypes.STRING), | |
villages: DataTypes.ARRAY(DataTypes.STRING) | |
}, { | |
tableName: 'intervention_areas', | |
underscore: true, | |
timestamp: true, | |
createdAt: 'created_at', | |
updatedAt: 'updated_at', | |
}); | |
InterventionArea.associate = function (models) { | |
InterventionArea.belongsTo(models.Organization, { | |
foreignKey: 'resource_id', | |
as: 'organization' | |
}); | |
InterventionArea.belongsTo(models.Project, { | |
foreignKey: 'resource_id', | |
as: 'project' | |
}); | |
}; | |
return InterventionArea; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* InterventionAread Model | |
* TS file | |
* Generated Example | |
*/ | |
import { Model, DataTypes, Association } from 'sequelize'; | |
import { sequelize } from './connection'; // Your connection | |
import Organization from './Organization'; // Association models used | |
import Project from './Project'; // Association models used | |
/** | |
* InterventionArea Class Model | |
*/ | |
class InterventionArea extends Model { | |
public id!: number; | |
resource_id!: number; | |
resource!: string; | |
province!: string; | |
districts!: string[]; | |
sectors!: string[]; | |
cells!: string[]; | |
villages!: string[]; | |
public readonly created_at!: Date; | |
public readonly updated_at!: Date; | |
public readonly organization?: Organization; | |
public readonly project?: Project; | |
public static associations: { | |
organization: Association<Organization, InterventionArea>; | |
project: Association<Project, InterventionArea>; | |
}; | |
} | |
InterventionArea.init( | |
{ | |
id: { | |
allowNull: false, | |
autoIncrement: true, | |
primaryKey: true, | |
type: 'DataTypes.INTEGER', | |
}, | |
resource_id: 'DataTypes.INTEGER', | |
resource: 'DataTypes.STRING', | |
province: 'DataTypes.STRING', | |
districts: 'DataTypes.ARRAY(DataTypes.STRING)', | |
sectors: 'DataTypes.ARRAY(DataTypes.STRING)', | |
cells: 'DataTypes.ARRAY(DataTypes.STRING)', | |
villages: 'DataTypes.ARRAY(DataTypes.STRING)', | |
}, | |
{ | |
tableName: 'intervention_areas', | |
underscore: true, | |
timestamp: true, | |
createdAt: 'created_at', | |
updatedAt: 'updated_at', | |
}, | |
); | |
InterventionArea.belongsTo(Organization, { | |
foreignKey: 'resource_id', | |
as: 'organization', | |
}); | |
InterventionArea.belongsTo(Project, { | |
foreignKey: 'resource_id', | |
as: 'project', | |
}); | |
export default InterventionArea; |
Author
NiyongaboEric
commented
Nov 2, 2021
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment