Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added typeOverrides option #547

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 95 additions & 14 deletions src/auto-generator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from "lodash";
import { ColumnDescription } from "sequelize/types";
import { DialectOptions, FKSpec } from "./dialects/dialect-options";
import { AutoOptions, CaseFileOption, CaseOption, Field, IndexSpec, LangOption, qNameJoin, qNameSplit, recase, Relation, TableData, TSField, singularize, pluralize } from "./types";
import { AutoOptions, CaseFileOption, CaseOption, Field, IndexSpec, LangOption, qNameJoin, qNameSplit, recase, Relation, TableData, TSField, singularize, pluralize, TypeOverrides, TableTypeOverride, ColumnTypeOverride, NullableFieldTypes } from "./types";

/** Generates text from each table in TableData */
export class AutoGenerator {
Expand All @@ -22,6 +22,7 @@ export class AutoGenerator {
additional?: any;
schema?: string;
singularize: boolean;
typeOverrides?: TypeOverrides;
};

constructor(tableData: TableData, dialect: DialectOptions, options: AutoOptions) {
Expand Down Expand Up @@ -103,8 +104,19 @@ export class AutoGenerator {
str += ` } from './${filename}';\n`;
});

const typeOverrides = this.options.typeOverrides;
let tableTypeOverride: TableTypeOverride | undefined;
if (typeOverrides) {
if (typeOverrides.tables && tableNameOrig) {
tableTypeOverride = typeOverrides.tables[tableNameOrig];
if (tableTypeOverride) {
str += this.getTypeScriptTableOverrideImports(tableTypeOverride);
}
}
}

str += "\nexport interface #TABLE#Attributes {\n";
str += this.addTypeScriptFields(table, true) + "}\n\n";
str += this.addTypeScriptFields(table, true, tableTypeOverride, typeOverrides?.nullableFieldType) + "}\n\n";

const primaryKeys = this.getTypeScriptPrimaryKeys(table);

Expand All @@ -123,7 +135,7 @@ export class AutoGenerator {
}

str += "export class #TABLE# extends Model<#TABLE#Attributes, #TABLE#CreationAttributes> implements #TABLE#Attributes {\n";
str += this.addTypeScriptFields(table, false);
str += this.addTypeScriptFields(table, false, tableTypeOverride, typeOverrides?.nullableFieldType);
str += "\n" + associations.str;
str += "\n" + this.space[1] + "static initModel(sequelize: Sequelize.Sequelize): typeof " + tableName + " {\n";
str += this.space[2] + tableName + ".init({\n";
Expand Down Expand Up @@ -546,7 +558,7 @@ export class AutoGenerator {
const fields = _.keys(this.tables[table]);
return fields.filter((field): boolean => {
const fieldObj = this.tables[table][field];
return fieldObj.allowNull || (!!fieldObj.defaultValue || fieldObj.defaultValue === "") || fieldObj.primaryKey;
return fieldObj.allowNull || (!!fieldObj.defaultValue || fieldObj.defaultValue === "") || fieldObj.autoIncrement;
});
}

Expand Down Expand Up @@ -594,7 +606,7 @@ export class AutoGenerator {
str += `${sp}${rel.childProp}!: ${rel.childModel};\n`;
str += `${sp}get${pchild}!: Sequelize.HasOneGetAssociationMixin<${rel.childModel}>;\n`;
str += `${sp}set${pchild}!: Sequelize.HasOneSetAssociationMixin<${rel.childModel}, ${rel.childModel}Id>;\n`;
str += `${sp}create${pchild}!: Sequelize.HasOneCreateAssociationMixin<${rel.childModel}CreationAttributes>;\n`;
str += `${sp}create${pchild}!: Sequelize.HasOneCreateAssociationMixin<${rel.childModel}>;\n`;
needed[rel.childTable].add(rel.childModel);
needed[rel.childTable].add(`${rel.childModel}Id`);
needed[rel.childTable].add(`${rel.childModel}CreationAttributes`);
Expand Down Expand Up @@ -654,24 +666,93 @@ export class AutoGenerator {
return { needed, str };
}

private addTypeScriptFields(table: string, isInterface: boolean) {
private getTypeScriptTableOverrideImports(tableTypeOverride: TableTypeOverride) {
const columnTypeOverridesByType: { [type: string]: ColumnTypeOverride } = {}
// type should only be imported once for each file
_.keys(tableTypeOverride).forEach((columnName) => {
const columnTypeOverride = tableTypeOverride![columnName]!;
if (columnTypeOverride.type) {
columnTypeOverridesByType[columnTypeOverride.type] = columnTypeOverride;
}
});

// import per source
const imports: { [source: string]: { default?: string, types: string[] } } = {};
_.keys(columnTypeOverridesByType).forEach((type) => {
const columnTypeOverride = columnTypeOverridesByType[type];
if (columnTypeOverride.source) {
const importData = imports[columnTypeOverride.source];
if (importData) {
if (columnTypeOverride.isDefault) {
importData.default = type;
} else {
importData.types.push(type);
}
} else {
let newImportData: { default?: string, types: string[] };
if (columnTypeOverride.isDefault) {
newImportData = {
default: type,
types: [],
}
} else {
newImportData = {
types: [type],
}
}
imports[columnTypeOverride.source] = newImportData;
}
}
});

const importStrArr: string[] = [];
_.keys(imports).forEach((source) => {
const importData = imports[source];
let importStr = "import";
if (importData.default) {
importStr += ` ${importData.default}`;
}

if (importData.types.length !== 0) {
importStr += (importData.default ? ", " : " ") + `{ ${importData.types.join(", ")} }`;
}

importStr += ` from '${source}';`;
importStrArr.push(importStr);
});

if (importStrArr.length !== 0) {
return importStrArr.join("\n") + "\n";
}

return "";
}

private addTypeScriptFields(table: string, isInterface: boolean, tableTypeOverride: TableTypeOverride | undefined, nullableFieldType: NullableFieldTypes | undefined) {
const sp = this.space[1];
const fields = _.keys(this.tables[table]);
const notNull = isInterface ? '' : '!';
let str = '';
const notOptional = isInterface ? '' : '!';
fields.forEach(field => {
let columnTypeOverride: ColumnTypeOverride | undefined;
if (tableTypeOverride) {
columnTypeOverride = tableTypeOverride[field];
}
const name = this.quoteName(recase(this.options.caseProp, field));
const isOptional = this.getTypeScriptFieldOptional(table, field);
str += `${sp}${name}${isOptional ? '?' : notNull}: ${this.getTypeScriptType(table, field)};\n`;
const fieldObj = this.tables[table][field];
let isOptional: boolean;
if (columnTypeOverride && columnTypeOverride.isOptional !== undefined) {
// override
isOptional = columnTypeOverride.isOptional;
} else {
isOptional = fieldObj.allowNull && nullableFieldType !== NullableFieldTypes.Null;
}
str += `${sp}${name}${isOptional ? '?' : notOptional}: ` +
`${columnTypeOverride && columnTypeOverride.type !== undefined ? columnTypeOverride.type : (this.getTypeScriptType(table, field) + (fieldObj.allowNull && nullableFieldType !== NullableFieldTypes.Optional ? " | null" : ""))};\n`;
});
return str;
}

private getTypeScriptFieldOptional(table: string, field: string) {
const fieldObj = this.tables[table][field];
return fieldObj.allowNull;
}

private getTypeScriptType(table: string, field: string) {
const fieldObj = this.tables[table][field] as TSField;
return this.getTypeScriptFieldType(fieldObj, "type");
Expand Down
39 changes: 39 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ export interface AutoOptions {
storage?: string;
/** Tables to export (default all) */
tables?: string[];
/** Override the types of generated typescript file */
typeOverrides?: TypeOverrides;
/** Database username */
username?: string;
/** Whether to export views (default false) */
Expand Down Expand Up @@ -221,3 +223,40 @@ export function recase(opt: CaseOption | CaseFileOption | undefined, val: string
return val;
}

/**
* @type Optional. Name of the type
* @source Optional. File path of the type relative to file in the directory.
* Leave undefined if overriding with primitive types
* @isDefault Optional. Whether the type is an export default. Default false
* @isOptional Optional. Override optionality
*/
export interface ColumnTypeOverride {
type?: string;
source?: string;
isDefault?: boolean;
isOptional?: boolean;
}
export type TableTypeOverride = { [columnName: string]: ColumnTypeOverride | undefined };
export type TableTypeOverrides = { [tableName: string]: TableTypeOverride | undefined }

export enum NullableFieldTypes {
Null = "NULL",
Optional = "OPTIONAL",
NullAndOptional = "NULL_AND_OPTIONAL"
}

/**
* @tables {
* roles: {
* name: {
* type: "RoleTypes",
* source: "../RoleTypes"
* }
* }
* }
* @nullableFieldType use "NULL", "OPTIONAL", OR "NULL_AND_OPTIONAL" for nullable table columns. Default "NULL_AND_OPTIONAL"
*/
export interface TypeOverrides {
tables?: TableTypeOverrides;
nullableFieldType?: NullableFieldTypes;
}