4

i have two entity typeorm with one to one bi-directional:

Departament:

@Entity('Departament')
export default class Departament {
  @PrimaryGeneratedColumn()
  id: string;

  @Column()
  departament_name: string;

  @OneToOne(type => User, user => user.departament)
  @JoinColumn()
  user: User;

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at' })
  UpdatedAt: Date;
}

User:

@Entity('User')
export default class User {
  @PrimaryGeneratedColumn()
  id: string;

  @Column()
  name: string;

  @Column()
  last_name: string;

  @Column()
  email: string;

  @Column()
  login: string;

  @Column()
  password: string;

  @OneToOne(type => Departament, departament => departament.user)
  departament: Departament;
}

and these are my .eslintrc settings:

{
  "env": {
    "es6": true,
    "node": true,
    "jest": true
  },
  "extends": [
    "airbnb-base",
    "plugin:@typescript-eslint/recommended",
    "prettier/@typescript-eslint",
    "plugin:prettier/recommended"
  ],
  "globals": {
    "Atomics": "readonly",
    "SharedArrayBuffer": "readonly"
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint", "prettier"],
  "rules": {
    "prettier/prettier": "error",
    "no-new": "off",
    "no-underscore-dangle": "off",
    "class-methods-use-this": "off",
    "no-await-in-loop": "off",
    "import/prefer-default-export": "off",
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "ts": "never"
      }
    ],
    "import/no-extraneous-dependencies": [
      "error",
      {
        "devDependencies": ["**/*.spec.ts", "src/utils/tests/*.ts"]
      }
    ],
    "no-useless-constructor": "off",
    "@typescript-eslint/no-unused-vars": [
      "error",
      {
        "argsIgnorePattern": "_"
      }
    ],
    "@typescript-eslint/no-useless-constructor": "error",
    "camelcase": "off",
    "@typescript-eslint/camelcase": "off"
  },
  "overrides": [
    {
      "files": ["*.js"],
      "rules": {
        "@typescript-eslint/no-var-requires": "off"
      }
    }
  ],
  "settings": {
    "import/extensions": [".ts", ".js"],
    "import/parsers": {
      "@typescript-eslint/parser": [".ts", ".js"]
    },
    "import/resolver": {
      "typescript": {
        "alwaysTryTypes": true
      }
    }
  }
}

i got this errors:

dependency cycle detected.eslintimport/no-cycle

on (user and departament)

and:

'type' is defined but never used. Allowed unused args must match /_/u.eslint@typescript-eslint/no-unused-vars

I'm not able to solve this, and I don't know what is the best option to do, I'm following the getting started of typeorm

gabriel
  • 470
  • 3
  • 11
  • 19

1 Answers1

8

Certain cases of circular imports can be confusing for the compiler, but most of the time they can be resolved without problems. The eslint import/no-cycle rule exists to remove any potential issues early, which is a good thing. There is an excellent Stack Overflow answer that sheds more light on the issue and suggests avoiding the circular dependencies altogether.

There is a discussion in the TypeORM repo that offers multiple solutions, each having its own trade-offs. Most complete solutions are entity schemas and string references. Both solutions use a similar idea – to define the relation with a string and resolve the string at a later point.

This is a common way of resolving circular dependencies that can be found in other ORMs, such as Python's SQLAlchemy or Ruby on Rails' ActiveRecord. The idea is simple: the entity class/schema is retrieved from a registry by the entity's name string, which eliminates the need to explicitly import modules to define the relation.

Let me give an example for both.

Entity schemas

This is a functional way of defining your entities. Class definition is replaced with an instance of EntitySchema, and no decorators are used.

export const DepartmentEntity = new EntitySchema({
  name: "Department",
  columns: { /* omitting for brevity */ },
  relations: {
    user: {
      type: "one-to-one",
      target: "User"
    }
  }
})
export const UserEntity = new EntitySchema({
  name: "User",
  columns: { /* omitting for brevity */ },
  relations: {
    department: {
      type: "one-to-one",
      target: "Department"
    }
  }
})

String references

This is a less radical change to what you have already, it would only require changing the type functions and inverse side functions to strings. To correctly type the relation attribute you would need to use import type:

import type User from './user';

@Entity('Department')
export default class Department {
  // omitting most of the columns
  // ...

  @OneToOne('User', 'department')
  @JoinColumn()
  user: User;
}
import type Department from './department';

@Entity('User')
export default class User {
  // omitting most of the columns
  // ...

  @OneToOne('Department', 'user')
  department: Department;
}

What to choose?

It's up to you. Mechanics are exactly the same, but the entity schemas are likely to stay and become a first-class citizen in TypeORM versions to come and they give you more type safety than classes have to offer.

Nikolay Shebanov
  • 1,363
  • 19
  • 33