sequelize-typescript: Cannot access [Model] before initialization

Versions

  • sequelize: 4.44.4
  • sequelize-typescript: 1.1.0
  • typescript: 3.9.2

I’m submitting a …

[X] bug report [ ] feature request

Actual behavior:

$ ./node_modules/.bin/ts-node test.ts ReferenceError: Cannot access ‘Team’ before initialization

Expected behavior:

No error.

Steps to reproduce: Use (basically) the code from the README (https://github.com/RobinBuschmann/sequelize-typescript#one-to-many) as test.ts:

import {BelongsTo, Column, ForeignKey, HasMany, Model, Table} from "sequelize-typescript";

@Table
export class Player extends Model<Player> {

  @Column
  name!: string;

  @Column
  num!: number;

  @ForeignKey(() => Team)
  @Column
  teamId!: number;

  @BelongsTo(() => Team)
  team!: Team;
}

@Table
export class Team extends Model<Team> {

  @Column
  name!: string;

  @HasMany(() => Player)
  players!: Player[];
}

tsconfig.json:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "forceConsistentCasingInFileNames": true,
    "lib": ["es2019", "es2020.promise", "es2020.bigint", "es2020.string"],
    "noImplicitAny": true,
    "module": "commonjs",
    "outDir": "_dist",
    "rootDir": ".",
    "skipLibCheck": true,
    "sourceMap": false,
    "strict": true,
    "strictNullChecks": true,
    "target": "ES2019"
  }
}

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 5
  • Comments: 23

Most upvoted comments

I’ve got another workaround.

I’ve noticed that it is possible to reference classes declared later when defining arrays

  // this works
  @HasMany(() => AuctionBid)
  public bids: AuctionBid[];

  // this does not work
  @HasOne(() => AuctionBid)
  public lastBid: AuctionBid;
                  ^ ReferenceError: Cannot access 'AuctionBid' before initialization

Which brought me to this:

  @HasOne(() => AuctionBid)
  public usersBid: ReturnType<() => AuctionBid>;

Compiles without errors.

Nice find @strrife!

The following alternative works too.

  @HasOne(() => AuctionBid)
  public usersBid: Awaited<AuctionBid>;

The root cause of all these issues is the requirement of recursive imports. This PR (https://github.com/RobinBuschmann/sequelize-typescript/pull/1206) solve that in part. But in order to completely solve this, I think the returned reference from an association should be a generic model based on the interface.

So, the final solution could look something like this:


@Table
export class Player extends Model<IPlayer> {

  @Column
  name!: string;

  @Column
  num!: number;

  @ForeignKey(models => models.Team)
  @Column
  teamId!: number;

  @BelongsTo(models => models.Team)
  team!: Model<ITeam>;
}

@Table
export class Team extends Model<ITeam> {

  @Column
  name!: string;

  @HasMany(models => models.Player)
  players!: Model<IPlayer>[];
}

The issue still happens for me when models are in different files but only when module in tsconfig.json is set to any ES version. It does not happen when targeting CommonJS. Maybe @TPME and @EdByrnee can confirm whether they’re also targeting ESM. It seems like using the () => Model as a workaround for avoiding circular dependencies only works in CommonJS.

I have all my entities in different files but I’m getting the same issue?!

I was using @nestjs/sequelize. Got same error. Updating module:es2020 and target:es2015 to module: commonjs and target: es2017 solved the issue.

Hi,

I have the same issue and it’s really blocking me. The weird part is that it seems very inconsistent.

  • Models are all in different files
  • I use Sequelize({config: {models: … } })

Yet I still have this error. Please help 😕

I’ve got another workaround.

I’ve noticed that it is possible to reference classes declared later when defining arrays

  // this works
  @HasMany(() => AuctionBid)
  public bids: AuctionBid[];

  // this does not work
  @HasOne(() => AuctionBid)
  public lastBid: AuctionBid;
                  ^ ReferenceError: Cannot access 'AuctionBid' before initialization

Which brought me to this:

  @HasOne(() => AuctionBid)
  public usersBid: ReturnType<() => AuctionBid>;

Compiles without errors.

i did change the Model to Model[] and it works… or adding union types null also work

in my case i was trying some dirty tricks which brought upon a flawed table design that was given to me in my project while still using ORM. I get this problem when trying to re use the many-to-many intermediate table as a bridge to another table.

In my case changing the order of how you import these models on the same file you use new Sequelize() solved the problem, I’d suggest having a look at this repo’s example because it does the exact same thing and their version works even though it needs updating to the latest version.

I’m trying to remove emitDecoratorMetadata option. If you set types/table names manually it can be a solution as well

Similar issue, only thing I could get to work was to add using belongsTo directly from created Sequelize instance

// instance sequelize = Sequelize({config: {models: … } })

// create the relationship sequelize.model(Player).belongsTo(Team);

TL;DR: see workaround code snippet.

Without in-depth knowledge of the code I’m not sure if this could work but I’m suggesting it anyways if someone wants to try to implement it.

One way of solving the issue might be to allow passing just the model name on one side of the association and keep passing the model getter on the other (basically a TS overload + a typeof modelGetter === 'string' check). This way the circular dependency would be avoided (no import required on one side in emitted JS code) and sequelize-typescript could resolve the model constructor from its name from the already registered models. One problem with this approach is what would happen if the model associating by name is defined before the one associating by getter: in that case sequelize-typescript wouldn’t have any constructor to resolve yet… And this isn’t really under the user’s control since the import order is established by Node.

With that said unfortunately I don’t have the time to create a PR myself at the moment but I might do it some time in the future since I will also need this feature. In the meantime maybe @RobinBuschmann has some suggestions on the feasibility of this approach/other possible solutions…

For now though I also suggest a workaround I’m planning on using myself: It’s possible to associate model A with model B normally in A.ts (using the import + decorator). Then leave out the association completely in B.ts and add the association for B in A.ts using regular old sequelize.

Here’s an example:

// A.ts
import B from './B';

class A extends Model {
  @HasMany(() => B)
  foo!: B[];
}
// Put association in A.ts
B.belongsTo(A);

export default A;

// B.ts
import A from './A';

class B extends Model {
  // Omit association. TS compiler should omit import for A in emitted JS since it's only used as type.
  // @BelongsTo(() => A)
  baz!: A;
}

export default B;

It’s not super pretty but it should work. Let me know what do you think/any possible issue with either approach.

I was using @nestjs/sequelize. Got same error. Updating module:es2020 and target:es2015 to module: commonjs and target: es2017 solved the issue.

It does not because changes the target type to commonjs instead of ESM which is not what we want. If you want to build an ESM module that is not a solution!

I resolved that just putting each model in different files. It is not a sequelize issue, seems like the problem is on typescript compilation with emitDecoratorMetadata: true option.