diff --git a/package-lock.json b/package-lock.json index 24cd216..93b6a1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -209,6 +209,12 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@types/array.prototype.flatmap": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/array.prototype.flatmap/-/array.prototype.flatmap-1.2.0.tgz", + "integrity": "sha512-sMRMigmTCA1EVX46F0jPhSFo51uFNo83GkeYlp7r4xKkXwfjkDpTm13vB9Tba4Ey+0rUXX3QjSWHAIwSb3qzrA==", + "dev": true + }, "@types/chai": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.3.tgz", @@ -706,6 +712,37 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "array.prototype.flatmap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.2.tgz", + "integrity": "sha512-ZZtPLE74KNE+0XcPv/vQmcivxN+8FhwOLvt2udHauO0aDEpsXDQrmd5HuJGpgPVyaV8HvkDPWnJ2iaem0oCKtA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.15.0", + "function-bind": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", + "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.0", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" + } + } + } + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -4092,6 +4129,12 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -5043,6 +5086,26 @@ } } }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", diff --git a/package.json b/package.json index f10558a..1eb1a81 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "yn": "^3.1.1" }, "devDependencies": { + "@types/array.prototype.flatmap": "^1.2.0", "@types/chai": "^4.2.3", "@types/chai-as-promised": "^7.1.2", "@types/chai-subset": "^1.3.3", @@ -61,6 +62,7 @@ "@typescript-eslint/eslint-plugin": "^2.3.3", "@typescript-eslint/parser": "^2.3.3", "@typescript-eslint/typescript-estree": "^2.3.3", + "array.prototype.flatmap": "^1.2.2", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "chai-subset": "^1.6.0", diff --git a/src/ModelCustomization.ts b/src/ModelCustomization.ts index 0041e10..d718222 100644 --- a/src/ModelCustomization.ts +++ b/src/ModelCustomization.ts @@ -47,7 +47,7 @@ function removeIndicesGeneratedByTypeorm(dbModel: Entity[]): Entity[] { v => !( v.primary && - v.name !== + v.name === namingStrategy.primaryKeyName( entity.tscName, primaryColumns diff --git a/test/integration/github-issues/183/entity/Client.ts b/test/integration/github-issues/183/entity/Client.ts new file mode 100644 index 0000000..e8b856d --- /dev/null +++ b/test/integration/github-issues/183/entity/Client.ts @@ -0,0 +1,19 @@ +import { PrimaryGeneratedColumn, Entity, ManyToMany, JoinTable } from "typeorm"; +import { ClientCategory } from "./ClientCategory"; + +@Entity("Client") +export class Client { + @PrimaryGeneratedColumn() + id: number; + + @ManyToMany( + () => ClientCategory, + (clientCategory: ClientCategory) => clientCategory.clients + ) + @JoinTable({ + name: "client_categories", + joinColumn: { name: "client_id" }, + inverseJoinColumn: { name: "category_id" } + }) + clientCategories: ClientCategory[]; +} diff --git a/test/integration/github-issues/183/entity/ClientCategory.ts b/test/integration/github-issues/183/entity/ClientCategory.ts new file mode 100644 index 0000000..b7aae65 --- /dev/null +++ b/test/integration/github-issues/183/entity/ClientCategory.ts @@ -0,0 +1,11 @@ +import { Entity, PrimaryGeneratedColumn, ManyToMany } from "typeorm"; +import { Client } from "./Client"; + +@Entity("ClientCategory") +export class ClientCategory { + @PrimaryGeneratedColumn() + id: number; + + @ManyToMany(() => Client, (client: Client) => client.clientCategories) + clients: Client[]; +} diff --git a/test/integration/runTestsFromPath.test.ts b/test/integration/runTestsFromPath.test.ts index 32c143d..71e4d3d 100644 --- a/test/integration/runTestsFromPath.test.ts +++ b/test/integration/runTestsFromPath.test.ts @@ -4,6 +4,7 @@ import * as ts from "typescript"; import * as fs from "fs-extra"; import * as path from "path"; import * as chaiSubset from "chai-subset"; +import * as flatMap from "array.prototype.flatmap"; import yn from "yn"; import EntityFileToJson from "../utils/EntityFileToJson"; import { createDriver, dataCollectionPhase } from "../../src/Engine"; @@ -15,6 +16,7 @@ import modelGenerationPhase from "../../src/ModelGeneration"; require("dotenv").config(); +flatMap.shim(); chai.use(chaiSubset); const { expect } = chai; @@ -186,17 +188,62 @@ function compareGeneratedFiles(filesOrgPathTS: string, filesGenPath: string) { expect(filesOrg, "Errors detected in model comparison").to.be.deep.equal( filesGen ); - filesOrg.forEach(file => { - const jsonEntityGen = EntityFileToJson.convert( + const generatedEntities = filesOrg.map(file => + EntityFileToJson.convert( fs.readFileSync(path.resolve(filesGenPath, file)) - ); - const jsonEntityOrg = EntityFileToJson.convert( + ) + ); + const originalEntities = filesGen.map(file => + EntityFileToJson.convert( fs.readFileSync(path.resolve(filesOrgPathTS, file)) - ); - expect(jsonEntityGen, `Error in file ${file}`).to.containSubset( - jsonEntityOrg - ); - }); + ) + ); + generatedEntities + .flatMap(entity => + entity.columns + .filter( + column => + column.relationType === "ManyToMany" && + column.joinOptions.length > 0 + ) + .map(v => { + return { + ownerColumn: v, + ownerEntity: entity + }; + }) + ) + + .forEach(({ ownerColumn, ownerEntity }) => { + const childColumn = generatedEntities + .find( + childEntity => + childEntity.entityName.toLowerCase() === + ownerColumn.columnTypes[0] + .substring(0, ownerColumn.columnTypes[0].length - 2) + .toLowerCase() + )! + .columns.find( + column => + column.columnTypes[0].toLowerCase() === + `${ownerEntity.entityName}[]`.toLowerCase() + )!; + childColumn.joinOptions = ownerColumn.joinOptions.map(options => { + return { + ...options, + joinColumns: options.inverseJoinColumns, + inverseJoinColumns: options.joinColumns + }; + }); + }); + // TODO: set relation options on ManyToMany to both side of relation + generatedEntities + .map((ent, i) => [ent, originalEntities[i], filesOrg[i]]) + .forEach(([generated, original, file]) => { + expect(generated, `Error in file ${file}`).to.containSubset( + original + ); + }); } // TODO: Move(?) diff --git a/test/utils/EntityFileToJson.ts b/test/utils/EntityFileToJson.ts index 73cd595..443aef1 100644 --- a/test/utils/EntityFileToJson.ts +++ b/test/utils/EntityFileToJson.ts @@ -398,7 +398,6 @@ export default class EntityFileToJson { priorPartOfMultilineStatement = trimmedLine; } else { isMultilineStatement = false; - // TODO: get joinColumnOptions options retVal.columns[ retVal.columns.length - 1 ].isOwnerOfRelation = true; @@ -428,8 +427,35 @@ export default class EntityFileToJson { priorPartOfMultilineStatement = trimmedLine; } else { isMultilineStatement = false; - // TODO: get joinTableOptions options - is it possible while JoinTable can be on either side of the relationship? - // it doesn't matter which side of ManyToMany relation is marked as owner + const decoratorParameters = trimmedLine + .substring( + trimmedLine.indexOf("(") + 1, + trimmedLine.indexOf(")") + ) + .trim() + .replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '); + if (decoratorParameters.length > 0) { + const column = + retVal.columns[retVal.columns.length - 1]; + const options = JSON.parse(decoratorParameters); + if ( + options.inverseJoinColumn && + !Array.isArray(options.inverseJoinColumn) + ) { + options.inverseJoinColumns = [ + options.inverseJoinColumn + ]; + delete options.inverseJoinColumn; + } + if ( + options.joinColumn && + !Array.isArray(options.joinColumn) + ) { + options.joinColumns = [options.joinColumn]; + delete options.joinColumn; + } + column.joinOptions = [options]; + } } return; } diff --git a/tsconfig.json b/tsconfig.json index 1a824ca..5d766ab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ "newLine": "LF", "outDir": "dist", "lib": [ - "es2017" + "es2019","es2019.array" ], "resolveJsonModule": true, },