From bbf90f628ccf56387061589686383dd100184859 Mon Sep 17 00:00:00 2001 From: Joe Tsindos Date: Wed, 30 Oct 2024 16:03:28 +1100 Subject: [PATCH 01/10] add support for association methods --- src/builders/ModelBuilder.ts | 142 ++++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 4 deletions(-) diff --git a/src/builders/ModelBuilder.ts b/src/builders/ModelBuilder.ts index be874ea..b6eebbb 100644 --- a/src/builders/ModelBuilder.ts +++ b/src/builders/ModelBuilder.ts @@ -1,3 +1,4 @@ +import { pascalCase } from 'change-case'; import { promises as fs } from 'fs'; import path from 'path'; import * as ts from 'typescript'; @@ -92,13 +93,13 @@ export class ModelBuilder extends Builder { * Build association class member * @param {IAssociationMetadata} association */ - private static buildAssociationPropertyDecl(association: IAssociationMetadata): ts.PropertyDeclaration { + private static buildAssociationPropertyDecl(association: IAssociationMetadata, tablesMetadata: ITablesMetadata): ts.PropertyDeclaration[] { const { associationName, targetModel, joinModel } = association; const targetModels = [ targetModel ]; joinModel && targetModels.push(joinModel); - return ts.factory.createPropertyDeclaration( + const mainProperty = ts.factory.createPropertyDeclaration( [ ...(association.sourceKey ? [ @@ -121,6 +122,99 @@ export class ModelBuilder extends Builder { ts.factory.createTypeReferenceNode(targetModel, undefined), undefined, ); + + const mixinDeclarations = this.generateAssociationMixins(association, tablesMetadata); + + return [mainProperty, ...mixinDeclarations]; + } + + private static createMixinDeclaration(name: string, type: string): ts.PropertyDeclaration { + return ts.factory.createPropertyDeclaration( + [ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword)], + name, + undefined, + ts.factory.createTypeReferenceNode(type, undefined), + undefined + ); + } + + private static generateAssociationMixins(association: IAssociationMetadata, tablesMetadata: ITablesMetadata): ts.PropertyDeclaration[] { + const { associationName, targetModel } = association; + const singularTarget = pluralize.singular(targetModel); + const pluralTarget = pluralize.plural(targetModel); + + // Get the primary key type from the target table's metadata + const targetTable = tablesMetadata[targetModel]; + const primaryKeyColumn = Object.values(targetTable.columns).find(col => col.primaryKey); + const primaryKeyType = primaryKeyColumn ? + this.getPrimaryKeyType(primaryKeyColumn.type) : + 'number'; // fallback to number if not found + + switch (associationName) { + case 'HasMany': + return [ + this.createMixinDeclaration(`get${pascalCase(pluralTarget)}`, `HasManyGetAssociationsMixin<${targetModel}>`), + this.createMixinDeclaration(`add${pascalCase(singularTarget)}`, `HasManyAddAssociationMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`add${pascalCase(pluralTarget)}`, `HasManyAddAssociationsMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`set${pascalCase(pluralTarget)}`, `HasManySetAssociationsMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`remove${pascalCase(singularTarget)}`, `HasManyRemoveAssociationMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`remove${pascalCase(pluralTarget)}`, `HasManyRemoveAssociationsMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`has${pascalCase(singularTarget)}`, `HasManyHasAssociationMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`has${pascalCase(pluralTarget)}`, `HasManyHasAssociationsMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`count${pascalCase(pluralTarget)}`, 'HasManyCountAssociationsMixin'), + this.createMixinDeclaration(`create${pascalCase(singularTarget)}`, `HasManyCreateAssociationMixin<${targetModel}>`) + ]; + + case 'HasOne': + return [ + this.createMixinDeclaration(`get${pascalCase(singularTarget)}`, `HasOneGetAssociationMixin<${targetModel}>`), + this.createMixinDeclaration(`set${pascalCase(singularTarget)}`, `HasOneSetAssociationMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`create${pascalCase(singularTarget)}`, `HasOneCreateAssociationMixin<${targetModel}>`), + ]; + + case 'BelongsTo': + return [ + this.createMixinDeclaration(`get${pascalCase(singularTarget)}`, `BelongsToGetAssociationMixin<${targetModel}>`), + this.createMixinDeclaration(`set${pascalCase(singularTarget)}`, `BelongsToSetAssociationMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`create${pascalCase(singularTarget)}`, `BelongsToCreateAssociationMixin<${targetModel}>`), + ]; + + case 'BelongsToMany': + return [ + this.createMixinDeclaration(`get${pascalCase(pluralTarget)}`, `BelongsToManyGetAssociationsMixin<${targetModel}>`), + this.createMixinDeclaration(`set${pascalCase(pluralTarget)}`, `BelongsToManySetAssociationsMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`add${pascalCase(singularTarget)}`, `BelongsToManyAddAssociationMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`add${pascalCase(pluralTarget)}`, `BelongsToManyAddAssociationsMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`create${pascalCase(singularTarget)}`, `BelongsToManyCreateAssociationMixin<${targetModel}>`), + this.createMixinDeclaration(`remove${pascalCase(singularTarget)}`, `BelongsToManyRemoveAssociationMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`remove${pascalCase(pluralTarget)}`, `BelongsToManyRemoveAssociationsMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`has${pascalCase(singularTarget)}`, `BelongsToManyHasAssociationMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`has${pascalCase(pluralTarget)}`, `BelongsToManyHasAssociationsMixin<${targetModel}, ${primaryKeyType}>`), + this.createMixinDeclaration(`count${pascalCase(pluralTarget)}`, 'BelongsToManyCountAssociationsMixin') + ]; + + default: + return []; + } + } + + private static getPrimaryKeyType(dbType: string): string { + // Map database types to TypeScript types + switch (dbType.toLowerCase()) { + case 'uuid': + return 'string'; + case 'varchar': + case 'char': + case 'text': + return 'string'; + case 'int': + case 'integer': + case 'smallint': + case 'bigint': + return 'number'; + default: + return 'number'; // default fallback + } } /** @@ -131,6 +225,7 @@ export class ModelBuilder extends Builder { */ private static buildTableClassDeclaration( tableMetadata: ITableMetadata, + tablesMetadata: ITablesMetadata, dialect: Dialect, strict: boolean = true ): string { @@ -155,6 +250,45 @@ export class ModelBuilder extends Builder { generatedCode += '\n'; + // Import mixin types from sequelize +generatedCode += nodeToString(generateNamedImports( + [ + // HasMany mixins + 'HasManyGetAssociationsMixin', + 'HasManyAddAssociationMixin', + 'HasManyAddAssociationsMixin', + 'HasManySetAssociationsMixin', + 'HasManyRemoveAssociationMixin', + 'HasManyRemoveAssociationsMixin', + 'HasManyHasAssociationMixin', + 'HasManyHasAssociationsMixin', + 'HasManyCountAssociationsMixin', + 'HasManyCreateAssociationMixin', + // HasOne mixins + 'HasOneGetAssociationMixin', + 'HasOneSetAssociationMixin', + 'HasOneCreateAssociationMixin', + // BelongsTo mixins + 'BelongsToGetAssociationMixin', + 'BelongsToSetAssociationMixin', + 'BelongsToCreateAssociationMixin', + // BelongsToMany mixins + 'BelongsToManyGetAssociationsMixin', + 'BelongsToManySetAssociationsMixin', + 'BelongsToManyAddAssociationMixin', + 'BelongsToManyAddAssociationsMixin', + 'BelongsToManyCreateAssociationMixin', + 'BelongsToManyRemoveAssociationMixin', + 'BelongsToManyRemoveAssociationsMixin', + 'BelongsToManyHasAssociationMixin', + 'BelongsToManyHasAssociationsMixin', + 'BelongsToManyCountAssociationsMixin' + ], + 'sequelize' +)); + +generatedCode += '\n'; + // Named imports for associations const importModels = new Set(); @@ -262,7 +396,7 @@ export class ModelBuilder extends Builder { [ ...Object.values(columns).map(col => this.buildColumnPropertyDecl(col, dialect)), ...tableMetadata.associations && tableMetadata.associations.length ? - tableMetadata.associations.map(a => this.buildAssociationPropertyDecl(a)) : [] + tableMetadata.associations.flatMap(a => this.buildAssociationPropertyDecl(a, tablesMetadata)) : [] ] ); @@ -329,7 +463,7 @@ export class ModelBuilder extends Builder { for (const tableMetadata of Object.values(tablesMetadata)) { console.log(`Processing table ${tableMetadata.originName}`); const tableClassDecl = - ModelBuilder.buildTableClassDeclaration(tableMetadata, this.dialect, this.config.strict); + ModelBuilder.buildTableClassDeclaration(tableMetadata, tablesMetadata, this.dialect, this.config.strict); writePromises.push((async () => { const outPath = path.join(outDir, `${tableMetadata.name}.ts`); From 5628c98f3c48df3cba9c6b681fd50557460af2b6 Mon Sep 17 00:00:00 2001 From: Joe Tsindos Date: Thu, 31 Oct 2024 10:55:27 +1100 Subject: [PATCH 02/10] 11.0.9 id is always optional --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index caa66bf..3aead5e 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "sequelize-typescript-generator", - "version": "11.0.8", + "name": "@jsindos/sequelize-typescript-generator", + "version": "11.0.9", "description": "Automatically generates typescript models compatible with sequelize-typescript library (https://www.npmjs.com/package/sequelize-typescript) directly from your source database.", "main": "build/index.js", "types": "build/index.d.ts", From a5b99a232664f196056da97ab605e053e6c9294c Mon Sep 17 00:00:00 2001 From: Joe Tsindos Date: Thu, 31 Oct 2024 10:57:50 +1100 Subject: [PATCH 03/10] corrected --- package.json | 2 +- src/builders/ModelBuilder.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3aead5e..76b543f 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jsindos/sequelize-typescript-generator", - "version": "11.0.9", + "version": "11.0.10", "description": "Automatically generates typescript models compatible with sequelize-typescript library (https://www.npmjs.com/package/sequelize-typescript) directly from your source database.", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/src/builders/ModelBuilder.ts b/src/builders/ModelBuilder.ts index b6eebbb..1cabee2 100644 --- a/src/builders/ModelBuilder.ts +++ b/src/builders/ModelBuilder.ts @@ -328,7 +328,7 @@ generatedCode += '\n'; ...(Object.values(columns).map(c => ts.factory.createPropertySignature( undefined, ts.factory.createIdentifier(c.name), - c.autoIncrement || c.allowNull || c.defaultValue !== undefined ? + c.name === 'id' || c.autoIncrement || c.allowNull || c.defaultValue !== undefined ? ts.factory.createToken(ts.SyntaxKind.QuestionToken) : undefined, ts.factory.createTypeReferenceNode(dialect.mapDbTypeToJs(c.type) ?? 'any', undefined) ))) From 0e3f58443f2c911f27bbd7c3af15bfacba3c03ee Mon Sep 17 00:00:00 2001 From: Joe Tsindos Date: Fri, 30 May 2025 10:23:41 +1000 Subject: [PATCH 04/10] attempt to fix bug --- src/dialects/DialectPostgres.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialects/DialectPostgres.ts b/src/dialects/DialectPostgres.ts index e3c08ce..f681593 100644 --- a/src/dialects/DialectPostgres.ts +++ b/src/dialects/DialectPostgres.ts @@ -305,7 +305,7 @@ 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: [], From 1a6fb4b4eb75cf29272ba58a3c81ec9f4ffdd6a5 Mon Sep 17 00:00:00 2001 From: Joe Tsindos Date: Fri, 30 May 2025 10:24:28 +1000 Subject: [PATCH 05/10] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 76b543f..e51bcdf 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jsindos/sequelize-typescript-generator", - "version": "11.0.10", + "version": "11.0.11", "description": "Automatically generates typescript models compatible with sequelize-typescript library (https://www.npmjs.com/package/sequelize-typescript) directly from your source database.", "main": "build/index.js", "types": "build/index.d.ts", From 91817c9cd03636d4514c90e754f6ec047e481731 Mon Sep 17 00:00:00 2001 From: Joe Tsindos Date: Fri, 30 May 2025 10:27:16 +1000 Subject: [PATCH 06/10] forgot to build --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e51bcdf..d80ee1c 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jsindos/sequelize-typescript-generator", - "version": "11.0.11", + "version": "11.0.12", "description": "Automatically generates typescript models compatible with sequelize-typescript library (https://www.npmjs.com/package/sequelize-typescript) directly from your source database.", "main": "build/index.js", "types": "build/index.d.ts", From ec8cc4d5f3de1054de1063559ec93d41c35085bb Mon Sep 17 00:00:00 2001 From: Joe Tsindos Date: Fri, 30 May 2025 10:31:35 +1000 Subject: [PATCH 07/10] explicitly provide even when there are defaults? --- src/builders/ModelBuilder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builders/ModelBuilder.ts b/src/builders/ModelBuilder.ts index 1cabee2..07bca69 100644 --- a/src/builders/ModelBuilder.ts +++ b/src/builders/ModelBuilder.ts @@ -82,7 +82,7 @@ export class ModelBuilder extends Builder { ) ], col.name, - (col.autoIncrement || col.allowNull || col.defaultValue !== undefined) ? + (col.autoIncrement || col.allowNull) ? ts.factory.createToken(ts.SyntaxKind.QuestionToken) : ts.factory.createToken(ts.SyntaxKind.ExclamationToken), ts.factory.createTypeReferenceNode(dialect.mapDbTypeToJs(col.type) ?? 'any', undefined), undefined, From b06c49fe7e19489304302bb22231ba155df0b768 Mon Sep 17 00:00:00 2001 From: Joe Tsindos Date: Fri, 30 May 2025 10:31:57 +1000 Subject: [PATCH 08/10] new version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d80ee1c..38e1122 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jsindos/sequelize-typescript-generator", - "version": "11.0.12", + "version": "11.0.13", "description": "Automatically generates typescript models compatible with sequelize-typescript library (https://www.npmjs.com/package/sequelize-typescript) directly from your source database.", "main": "build/index.js", "types": "build/index.d.ts", From f5df20cbf11b7e2b951ebfaf5a532860f8625b67 Mon Sep 17 00:00:00 2001 From: Joe Tsindos Date: Fri, 20 Jun 2025 20:19:22 +1000 Subject: [PATCH 09/10] update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 38e1122..39cd92e 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jsindos/sequelize-typescript-generator", - "version": "11.0.13", + "version": "11.0.16", "description": "Automatically generates typescript models compatible with sequelize-typescript library (https://www.npmjs.com/package/sequelize-typescript) directly from your source database.", "main": "build/index.js", "types": "build/index.d.ts", From bc6a714f8cc0bd7d5aea7cc12fc18dbb4a6e2377 Mon Sep 17 00:00:00 2001 From: Joseph Tsindos Date: Fri, 20 Jun 2025 21:20:27 +1000 Subject: [PATCH 10/10] successfully get aliases working with association mixins --- package-lock.json | 8 ++-- package.json | 2 +- src/builders/ModelBuilder.ts | 64 ++++++++++++++++++------------ src/dialects/AssociationsParser.ts | 30 ++++++++++++-- 4 files changed, 70 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 084cf22..367a0b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "sequelize-typescript-generator", - "version": "11.0.8", + "name": "@jsindos/sequelize-typescript-generator", + "version": "11.0.16", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "sequelize-typescript-generator", - "version": "11.0.8", + "name": "@jsindos/sequelize-typescript-generator", + "version": "11.0.16", "license": "ISC", "dependencies": { "@types/eslint": "^8.56.5", diff --git a/package.json b/package.json index 39cd92e..701de1f 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jsindos/sequelize-typescript-generator", - "version": "11.0.16", + "version": "11.1.1", "description": "Automatically generates typescript models compatible with sequelize-typescript library (https://www.npmjs.com/package/sequelize-typescript) directly from your source database.", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/src/builders/ModelBuilder.ts b/src/builders/ModelBuilder.ts index 07bca69..32230f0 100644 --- a/src/builders/ModelBuilder.ts +++ b/src/builders/ModelBuilder.ts @@ -94,37 +94,46 @@ export class ModelBuilder extends Builder { * @param {IAssociationMetadata} association */ private static buildAssociationPropertyDecl(association: IAssociationMetadata, tablesMetadata: ITablesMetadata): ts.PropertyDeclaration[] { - const { associationName, targetModel, joinModel } = association; - + const { associationName, targetModel, joinModel, alias } = association; + const targetModels = [ targetModel ]; joinModel && targetModels.push(joinModel); - + + // Use alias if provided, otherwise use target model name + const nameBase = alias || targetModel; + const propertyName = associationName.includes('Many') ? + pluralize.plural(nameBase) : pluralize.singular(nameBase); + + let decorator; + if (associationName === 'BelongsToMany') { + // For BelongsToMany, don't pass alias in decorator options + // The alias will be handled by the property name + decorator = generateArrowDecorator(associationName, targetModels); + } else { + // For other associations, pass options normally + const options = { + ...(association.sourceKey && { sourceKey: association.sourceKey }), + ...(alias && { as: alias }) + }; + decorator = generateArrowDecorator( + associationName, + targetModels, + Object.keys(options).length > 0 ? options : undefined + ); + } + const mainProperty = ts.factory.createPropertyDeclaration( - [ - ...(association.sourceKey ? - [ - generateArrowDecorator( - associationName, - targetModels, - { sourceKey: association.sourceKey } - ) - ] - : [ - generateArrowDecorator(associationName, targetModels) - ] - ), - ], - associationName.includes('Many') ? - pluralize.plural(targetModel) : pluralize.singular(targetModel), + [decorator], + propertyName, ts.factory.createToken(ts.SyntaxKind.QuestionToken), associationName.includes('Many') ? ts.factory.createArrayTypeNode(ts.factory.createTypeReferenceNode(targetModel, undefined)) : ts.factory.createTypeReferenceNode(targetModel, undefined), undefined, ); - + const mixinDeclarations = this.generateAssociationMixins(association, tablesMetadata); - + return [mainProperty, ...mixinDeclarations]; } @@ -138,10 +147,13 @@ export class ModelBuilder extends Builder { ); } - private static generateAssociationMixins(association: IAssociationMetadata, tablesMetadata: ITablesMetadata): ts.PropertyDeclaration[] { - const { associationName, targetModel } = association; - const singularTarget = pluralize.singular(targetModel); - const pluralTarget = pluralize.plural(targetModel); + private static generateAssociationMixins(association: IAssociationMetadata, tablesMetadata: ITablesMetadata): ts.PropertyDeclaration[] { + const { associationName, targetModel, alias } = association; + + // Use alias if provided, otherwise use target model name + const nameBase = alias || targetModel; + const singularTarget = pluralize.singular(nameBase); + const pluralTarget = pluralize.plural(nameBase); // Get the primary key type from the target table's metadata const targetTable = tablesMetadata[targetModel]; @@ -149,7 +161,7 @@ export class ModelBuilder extends Builder { const primaryKeyType = primaryKeyColumn ? this.getPrimaryKeyType(primaryKeyColumn.type) : 'number'; // fallback to number if not found - + switch (associationName) { case 'HasMany': return [ diff --git a/src/dialects/AssociationsParser.ts b/src/dialects/AssociationsParser.ts index 2e862c4..96a0a6b 100644 --- a/src/dialects/AssociationsParser.ts +++ b/src/dialects/AssociationsParser.ts @@ -13,7 +13,9 @@ type AssociationRow = [ string, // right key string, // left table string, // right table - string? // [join table] + string?, // [join table] + string?, // [left alias] - alias used by leftModel when referring to rightModel + string? // [right alias] - alias used by rightModel when referring to leftModel ]; export interface IAssociationMetadata { @@ -21,6 +23,7 @@ export interface IAssociationMetadata { targetModel: string; joinModel?: string; sourceKey?: string; // Left table key for HasOne and HasMany associations + alias?: string; // Optional alias for the association } export interface IForeignKey { @@ -42,7 +45,9 @@ const validateRow = (row: AssociationRow): void => { rightKey, leftTable, rightTable, - joinTable + joinTable, + leftAlias, + rightAlias ] = row; if (!cardinalities.has(cardinality)) { @@ -68,6 +73,15 @@ const validateRow = (row: AssociationRow): void => { if (cardinality === 'N:N' && (!joinTable || !joinTable.length)) { throw new Error(`Association N:N requires a joinTable in the association row`); } + + // Validate aliases are not empty strings if provided + if (leftAlias !== undefined && leftAlias.length === 0) { + throw new Error(`Left alias cannot be empty string. Use undefined or omit the field instead.`); + } + + if (rightAlias !== undefined && rightAlias.length === 0) { + throw new Error(`Right alias cannot be empty string. Use undefined or omit the field instead.`); + } } /** @@ -109,7 +123,9 @@ export class AssociationsParser { rightKey, leftModel, rightModel, - joinModel + joinModel, + leftAlias, + rightAlias ] = row; const [ @@ -135,15 +151,19 @@ export class AssociationsParser { // 1:1 and 1:N association if (cardinality !== 'N:N') { + // Left model association (HasOne/HasMany) associationsMetadata[leftModel].associations.push({ associationName: rightCardinality === '1' ? 'HasOne' : 'HasMany', targetModel: rightModel, sourceKey: leftKey, + ...(leftAlias && { alias: leftAlias }) }); + // Right model association (BelongsTo) associationsMetadata[rightModel].associations.push({ associationName: 'BelongsTo', targetModel: leftModel, + ...(rightAlias && { alias: rightAlias }) }); associationsMetadata[rightModel].foreignKeys.push({ @@ -161,16 +181,20 @@ export class AssociationsParser { }; } + // Left model BelongsToMany association associationsMetadata[leftModel].associations.push({ associationName: 'BelongsToMany', targetModel: rightModel, joinModel: joinModel, + ...(leftAlias && { alias: leftAlias }) }); + // Right model BelongsToMany association associationsMetadata[rightModel].associations.push({ associationName: 'BelongsToMany', targetModel: leftModel, joinModel: joinModel, + ...(rightAlias && { alias: rightAlias }) }); associationsMetadata[joinModel!].foreignKeys.push({