'Avoiding cyclic dependencies when using Sequelize with TypeScript

I wanted to create a proper setup with Sequelize and TypeScript but hit a roadblock. The official docs show all examples in the same file, thus they have no issues, but when I try to create models in separate files and reference them in the TS typings, I am hit with cyclic dependencies:

// User.ts

import { Guide } from '.';

export class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
  declare id: CreationOptional<string>;
  // more attributes...

  declare guides?: NonAttribute<Guide>;
}

User.init(...);

// Guide.ts

import { User } from '.';

export class Guide extends Model<InferAttributes<Guide>, InferCreationAttributes<Guide>> {
  declare id: CreationOptional<string>;
  // more attributes

  declare author_id: NonAttribute<User['id']>;
  declare author?: NonAttribute<User>;

}

Guide.init(...);

As you can see, I need to reference Guide in User and vice-versa for correct typings. Am I missing something obvious? Is there a best-practice approach for this? There are also issues with calling association methods that stem from the problem above.



Solution 1:[1]

What you have here is a Many-To-Many association. This needs is a junction model to eliminate cyclic dependencies. Say, a GuideUser model that has references to both. This way User, for example, does not reference Guide directly.

https://sequelize.org/docs/v6/core-concepts/assocs/#many-to-many-relationships

The junction model can also express a One-To-Many association:

https://en.wikipedia.org/wiki/Associative_entity

An associative (or junction) table maps two or more tables together by referencing the primary keys (PK) of each data table. In effect, it contains a number of foreign keys (FK), each in a many-to-one relationship from the junction table to the individual data tables.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1