diff --git a/package.json b/package.json index d247130..75e3422 100755 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "clean": "rm -fr build", "lint": "eslint --fix --ext .ts output/*.ts", "build": "npm run clean && tsc", + "prepare": "npm run build", "audit": "npm audit", "docker-remove-containers": "./src/tests/integration/docker-remove-containers.sh", "docker-start-mysql": "./src/tests/integration/mysql/docker-start-mysql.sh", diff --git a/src/builders/ModelBuilder.ts b/src/builders/ModelBuilder.ts index ed1ce68..eb0d566 100644 --- a/src/builders/ModelBuilder.ts +++ b/src/builders/ModelBuilder.ts @@ -45,6 +45,10 @@ export class ModelBuilder extends Builder { ...col.dataType && { type: col.dataType }, ...col.comment && { comment: col.comment }, ...col.defaultValue && { defaultValue: col.defaultValue }, + ...col.onUpdate && { onUpdate: col.onUpdate }, + ...col.onDelete && { onDelete: col.onDelete }, + // @TODO fix this by creating a typescript definition for model + // ...col.references && { references: col.references as ModelAttributeColumnReferencesOptions }, }; return props; @@ -179,6 +183,14 @@ export class ModelBuilder extends Builder { 'sequelize-typescript' )); + generatedCode += '\n'; + generatedCode += nodeToString(generateNamedImports( + [ + 'Deferrable', + ], + 'sequelize' + )); + generatedCode += '\n'; // Named imports for associations diff --git a/src/builders/utils.ts b/src/builders/utils.ts index 5ac8756..31c1d08 100644 --- a/src/builders/utils.ts +++ b/src/builders/utils.ts @@ -25,7 +25,9 @@ export const nodeToString = (node: ts.Node): string => { * @param {string} moduleSpecifier * @returns {string} Named import code */ -export const generateNamedImports = (importsSpecifier: string[], moduleSpecifier: string): ts.ImportDeclaration => { +export const generateNamedImports = ( + importsSpecifier: string[], + moduleSpecifier: string): ts.ImportDeclaration => { return ts.createImportDeclaration( /* decorators */ undefined, /* modifiers */ undefined, @@ -34,7 +36,10 @@ export const generateNamedImports = (importsSpecifier: string[], moduleSpecifier ts.createNamedImports( [ ...importsSpecifier - .map(is => ts.createImportSpecifier(undefined, ts.createIdentifier(is))) + .map(is => ts.createImportSpecifier( + undefined, + ts.createIdentifier(is) + )) ] ) ), @@ -71,13 +76,16 @@ export const generateObjectLiteralDecorator = ( ts.createIdentifier(decoratorIdentifier), undefined, [ - ts.createObjectLiteral( - [ + ts.createObjectLiteral([ ...Object.entries(props) - .map(e => ts.createPropertyAssignment(e[0], + .map(e => ts.createPropertyAssignment( + e[0], typeof e[1] === 'string' && ( - e[1].startsWith('DataType.') || e[1].startsWith('Sequelize.') - ) ? ts.createIdentifier(e[1]) : ts.createLiteral(e[1]))) + e[1].startsWith('DataType.') || + e[1].startsWith('Sequelize.') || + e[1].startsWith('Deferrable.') + ) ? ts.createIdentifier(e[1]) : ts.createLiteral(e[1]) + )) ] ) ] diff --git a/src/dialects/Dialect.ts b/src/dialects/Dialect.ts index bb6f7c3..47783fa 100644 --- a/src/dialects/Dialect.ts +++ b/src/dialects/Dialect.ts @@ -1,10 +1,10 @@ import { IndexType, IndexMethod, AbstractDataTypeConstructor } from 'sequelize'; import { Sequelize } from 'sequelize-typescript'; +import { Deferrable } from 'sequelize/types/lib/deferrable'; import { IConfig } from '../config'; import { createConnection } from "../connection"; -import { AssociationsParser, IAssociationsParsed, IAssociationMetadata } from './AssociationsParser' +import { AssociationsParser, IAssociationMetadata } from './AssociationsParser' import { caseTransformer } from './utils'; -import {parse} from "@typescript-eslint/parser"; export interface ITablesMetadata { [tableName: string]: ITableMetadata; @@ -22,6 +22,20 @@ export interface ITableMetadata { comment?: string; } +export enum CONSTRAINT_TYPES { + c = 'CASCADE', + d = 'SET DEFAULT', + n = 'SET NULL', + r = 'RESTRICT', + a = 'NO ACTION', +} + +export interface IColumnMetadataReference { + model: string | null; + key: string | null; + deferrable: string | Deferrable | null; +} + export interface IColumnMetadata { name: string; // Model field name originName: string; // Database column name @@ -38,6 +52,9 @@ export interface IColumnMetadata { indices?: IIndexMetadata[], comment?: string; defaultValue?: any; + onDelete?: CONSTRAINT_TYPES; + onUpdate?: CONSTRAINT_TYPES; + references?: IColumnMetadataReference; } export interface IIndexMetadata { diff --git a/src/dialects/DialectPostgres.ts b/src/dialects/DialectPostgres.ts index 9f4172d..0422232 100644 --- a/src/dialects/DialectPostgres.ts +++ b/src/dialects/DialectPostgres.ts @@ -1,7 +1,13 @@ import { QueryTypes, AbstractDataTypeConstructor } from 'sequelize'; import { Sequelize, DataType } from 'sequelize-typescript'; import { IConfig } from '../config'; -import { IColumnMetadata, IIndexMetadata, Dialect, ITable } from './Dialect'; +import { + IColumnMetadata, + IIndexMetadata, + Dialect, + ITable, + CONSTRAINT_TYPES +} from './Dialect'; import { generatePrecisionSignature, warnUnknownMappingForDataType } from './utils'; interface ITableRow { @@ -57,6 +63,12 @@ interface IColumnMetadataPostgres { generation_expression: string; is_updatable: string; description: string | null; + confdeltype: string | null; + confupdtype: string | null; + condeferrable: boolean; + condeferred: boolean; + foreign_table_name: string | null; + foreign_column_name: string | null; } interface IIndexMetadataPostgres { @@ -235,6 +247,12 @@ export class DialectPostgres extends Dialect { WHERE a.attrelid = '${config.metadata!.schema}.${table}'::regclass AND a.attnum > 0 AND c.ordinal_position = a.attnum AND x.indisprimary IS TRUE ) AS is_primary, + isc_f.table_name as foreign_table_name, + isc_f.column_name as foreign_column_name, + pgc.confdeltype, + pgc.confupdtype, + pgc.condeferrable, + pgc.condeferred, c.*, pgd.description FROM information_schema.columns c @@ -242,6 +260,12 @@ export class DialectPostgres extends Dialect { ON c.table_schema = st.schemaname AND c.table_name = st.relname LEFT OUTER JOIN pg_catalog.pg_description pgd ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position + LEFT OUTER JOIN pg_catalog.pg_constraint pgc + ON pgc.conrelid = st.relid AND pgc.conkey[1] = c.ordinal_position + LEFT OUTER JOIN pg_catalog.pg_class pgc_f + ON pgc_f.oid = pgc.confrelid AND pgc.conkey[1] = c.ordinal_position + LEFT OUTER JOIN information_schema.columns isc_f ON isc_f.table_name = pgc_f.relname + AND isc_f.ordinal_position = pgc.confkey[1] LEFT OUTER JOIN ( -- Sequences (auto increment) metadata SELECT seqclass.relname AS sequence_name, pn.nspname AS schema_name, @@ -290,12 +314,35 @@ export class DialectPostgres extends Dialect { this.mapDbTypeToSequelize(column.udt_name).key .split(' ')[0], // avoids 'DOUBLE PRECISION' key to include PRECISION in the mapping }, - allowNull: !!column.is_nullable && !column.is_primary, + allowNull: column.is_nullable === 'YES' && !column.is_primary, primaryKey: column.is_primary, autoIncrement: column.is_sequence, indices: [], comment: column.description ?? undefined, }; + + if (CONSTRAINT_TYPES[column.confdeltype as keyof typeof CONSTRAINT_TYPES]) { + columnMetadata.onDelete = CONSTRAINT_TYPES[column.confdeltype as keyof typeof CONSTRAINT_TYPES]; + } + if (CONSTRAINT_TYPES[column.confupdtype as keyof typeof CONSTRAINT_TYPES]) { + columnMetadata.onUpdate = CONSTRAINT_TYPES[column.confupdtype as keyof typeof CONSTRAINT_TYPES]; + } + + if (column.condeferrable || column.condeferred) { + let deferrable = 'Deferrable.NOT()'; + if (column.condeferrable && column.condeferred) { + deferrable = 'Deferrable.INITIALLY_DEFERRED()'; + } + if (column.condeferrable && !column.condeferred) { + deferrable = 'Deferrable.INITIALLY_IMMEDIATE()'; + } + columnMetadata.references = { + model: column.foreign_table_name, + key: column.foreign_column_name, + deferrable, + } + } + if (column.column_default) { columnMetadata.defaultValue = `Sequelize.literal("${column.column_default}")`; }