sequelize-typescript-generator
Version:
Automatically generates typescript models compatible with sequelize-typescript library (https://www.npmjs.com/package/sequelize-typescript) directly from your source database.
296 lines (295 loc) • 13.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModelBuilder = void 0;
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const ts = __importStar(require("typescript"));
const pluralize_1 = __importDefault(require("pluralize"));
const lint_1 = require("../lint");
const Builder_1 = require("./Builder");
const utils_1 = require("./utils");
const foreignKeyDecorator = 'ForeignKey';
/**
* @class ModelGenerator
* @constructor
* @param {Dialect} dialect
*/
class ModelBuilder extends Builder_1.Builder {
constructor(config, dialect) {
super(config, dialect);
}
/**
* Build column class member
* @param {IColumnMetadata} col
* @param {Dialect} dialect
*/
static buildColumnPropertyDecl(col, dialect) {
var _a;
const buildColumnDecoratorProps = (col) => {
const props = {
...col.originName && col.name !== col.originName && { field: col.originName },
...col.primaryKey && { primaryKey: col.primaryKey },
...col.autoIncrement && { autoIncrement: col.autoIncrement },
...col.allowNull && { allowNull: col.allowNull },
...col.dataType && { type: col.dataType },
...col.comment && { comment: col.comment },
...col.defaultValue !== undefined && { defaultValue: dialect.mapDefaultValueToSequelize(col.defaultValue) },
};
return props;
};
const buildIndexDecoratorProps = (index) => {
const props = {
name: index.name,
...index.using && { using: index.using },
...index.collation && { order: index.collation === 'A' ? 'ASC' : 'DESC' },
unique: index.unique,
};
return props;
};
return ts.factory.createPropertyDeclaration([
...(col.foreignKey ?
[(0, utils_1.generateArrowDecorator)(foreignKeyDecorator, [col.foreignKey.targetModel])]
: []),
(0, utils_1.generateObjectLiteralDecorator)('Column', buildColumnDecoratorProps(col)),
...(col.indices && col.indices.length ?
col.indices.map(index => (0, utils_1.generateObjectLiteralDecorator)('Index', buildIndexDecoratorProps(index)))
: [])
], col.name, (col.autoIncrement || col.allowNull || col.defaultValue !== undefined) ?
ts.factory.createToken(ts.SyntaxKind.QuestionToken) : ts.factory.createToken(ts.SyntaxKind.ExclamationToken), ts.factory.createTypeReferenceNode((_a = dialect.mapDbTypeToJs(col.type)) !== null && _a !== void 0 ? _a : 'any', undefined), undefined);
}
/**
* Build association class member
* @param {IAssociationMetadata} association
*/
static buildAssociationPropertyDecl(association) {
const { associationName, targetModel, joinModel } = association;
const targetModels = [targetModel];
joinModel && targetModels.push(joinModel);
return ts.factory.createPropertyDeclaration([
...(association.sourceKey ?
[
(0, utils_1.generateArrowDecorator)(associationName, targetModels, { sourceKey: association.sourceKey })
]
: [
(0, utils_1.generateArrowDecorator)(associationName, targetModels)
]),
], associationName.includes('Many') ?
pluralize_1.default.plural(targetModel) : pluralize_1.default.singular(targetModel), ts.factory.createToken(ts.SyntaxKind.QuestionToken), associationName.includes('Many') ?
ts.factory.createArrayTypeNode(ts.factory.createTypeReferenceNode(targetModel, undefined)) :
ts.factory.createTypeReferenceNode(targetModel, undefined), undefined);
}
/**
* Build table class declaration
* @param {ITableMetadata} tableMetadata
* @param {Dialect} dialect
* @param {boolean} strict
*/
static buildTableClassDeclaration(tableMetadata, dialect, strict = true) {
var _a, _b;
const { originName: tableName, name, columns } = tableMetadata;
let generatedCode = '';
// Named imports from sequelize-typescript
generatedCode += (0, utils_1.nodeToString)((0, utils_1.generateNamedImports)([
'Model',
'Table',
'Column',
'DataType',
'Index',
'Sequelize',
foreignKeyDecorator,
...new Set((_a = tableMetadata.associations) === null || _a === void 0 ? void 0 : _a.map(a => a.associationName)),
], 'sequelize-typescript'));
generatedCode += '\n';
// Named imports for associations
const importModels = new Set();
// Add models for associations
(_b = tableMetadata.associations) === null || _b === void 0 ? void 0 : _b.forEach(a => {
importModels.add(a.targetModel);
a.joinModel && importModels.add(a.joinModel);
});
// Add models for foreign keys
Object.values(tableMetadata.columns).forEach(col => {
col.foreignKey && importModels.add(col.foreignKey.targetModel);
});
[...importModels].forEach(modelName => {
generatedCode += (0, utils_1.nodeToString)((0, utils_1.generateNamedImports)([modelName], `./${modelName}`));
generatedCode += '\n';
});
const attributesInterfaceName = `${name}Attributes`;
if (strict) {
generatedCode += '\n';
const attributesInterface = ts.factory.createInterfaceDeclaration([
ts.factory.createToken(ts.SyntaxKind.ExportKeyword),
], ts.factory.createIdentifier(attributesInterfaceName), undefined, undefined, [
...(Object.values(columns).map(c => {
var _a;
return ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier(c.name), c.autoIncrement || c.allowNull || c.defaultValue !== undefined ?
ts.factory.createToken(ts.SyntaxKind.QuestionToken) : undefined, ts.factory.createTypeReferenceNode((_a = dialect.mapDbTypeToJs(c.type)) !== null && _a !== void 0 ? _a : 'any', undefined));
}))
]);
generatedCode += (0, utils_1.nodeToString)(attributesInterface);
generatedCode += '\n';
}
const classDecl = ts.factory.createClassDeclaration([
// @Table decorator
(0, utils_1.generateObjectLiteralDecorator)('Table', {
tableName: tableName,
...tableMetadata.schema && { schema: tableMetadata.schema },
timestamps: tableMetadata.timestamps,
...tableMetadata.comment && { comment: tableMetadata.comment },
}),
// Export modifier
ts.factory.createToken(ts.SyntaxKind.ExportKeyword),
], name, undefined, !strict ? [
ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
ts.factory.createExpressionWithTypeArguments(ts.factory.createIdentifier('Model'), [])
])
] : [
ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
ts.factory.createExpressionWithTypeArguments(ts.factory.createIdentifier('Model'), [
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(attributesInterfaceName), undefined),
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(attributesInterfaceName), undefined)
])
]),
ts.factory.createHeritageClause(ts.SyntaxKind.ImplementsKeyword, [
ts.factory.createExpressionWithTypeArguments(ts.factory.createIdentifier(attributesInterfaceName), undefined)
])
],
// Class members
[
...Object.values(columns).map(col => this.buildColumnPropertyDecl(col, dialect)),
...tableMetadata.associations && tableMetadata.associations.length ?
tableMetadata.associations.map(a => this.buildAssociationPropertyDecl(a)) : []
]);
generatedCode += '\n';
generatedCode += (0, utils_1.nodeToString)(classDecl);
return generatedCode;
}
/**
* Build main index file
* @param {ITableMetadata[]} tablesMetadata
* @returns {string}
*/
static buildIndexExports(tablesMetadata) {
return Object.values(tablesMetadata)
.map(t => (0, utils_1.nodeToString)((0, utils_1.generateIndexExport)(t.name)))
.join('\n');
}
/**
* Build models files using the given configuration and dialect
* @returns {Promise<void>}
*/
async build() {
const { clean, outDir } = this.config.output;
const writePromises = [];
if (this.config.connection.logging) {
console.log('CONFIGURATION', this.config);
}
console.log(`Fetching metadata from source`);
const tablesMetadata = await this.dialect.buildTablesMetadata(this.config);
if (Object.keys(tablesMetadata).length === 0) {
console.warn(`Couldn't find any table for database ${this.config.connection.database} and provided filters`);
return;
}
// Check if output dir exists
try {
await fs_1.promises.access(outDir);
}
catch (err) {
if (err.code && err.code === 'ENOENT') {
await fs_1.promises.mkdir(outDir, { recursive: true });
}
else {
console.error(err);
process.exit(1);
}
}
// Clean files if required
if (clean) {
console.log(`Cleaning output dir`);
for (const file of await fs_1.promises.readdir(outDir)) {
await fs_1.promises.unlink(path_1.default.join(outDir, file));
}
}
// Build model files
for (const tableMetadata of Object.values(tablesMetadata)) {
console.log(`Processing table ${tableMetadata.originName}`);
const tableClassDecl = ModelBuilder.buildTableClassDeclaration(tableMetadata, this.dialect, this.config.strict);
writePromises.push((async () => {
const outPath = path_1.default.join(outDir, `${tableMetadata.name}.ts`);
await fs_1.promises.writeFile(outPath, tableClassDecl, { flag: 'w' });
console.log(`Generated model file at ${outPath}`);
})());
}
// Build index file
writePromises.push((async () => {
const indexPath = path_1.default.join(outDir, 'index.ts');
const indexContent = ModelBuilder.buildIndexExports(tablesMetadata);
await fs_1.promises.writeFile(indexPath, indexContent);
console.log(`Generated index file at ${indexPath}`);
})());
await Promise.all(writePromises);
// Lint files
try {
let linter;
if (this.config.lintOptions) {
linter = new lint_1.Linter(this.config.lintOptions);
}
else {
linter = new lint_1.Linter();
}
console.log(`Linting files`);
await linter.lintFiles([path_1.default.join(outDir, '*.ts')]);
}
catch (err) {
// Handle unsupported global eslint usage
if (err.code && err.code === 'MODULE_NOT_FOUND') {
let msg = `\n[WARNING] Linting models skipped: dependency not found.\n`;
msg += `Linting models globally is not supported (eslint library does not support global plugins).\n`;
msg += `If you have installed the library globally (--global flag) and you want to automatically lint your generated models,\n`;
msg += `please install the following packages locally: npm install -S typescript eslint @typescript-eslint/parser\n`;
console.warn(msg);
}
else {
throw err;
}
}
}
}
exports.ModelBuilder = ModelBuilder;