Typescript relationships

Hello, I want to translate our admin forest app to typescript, I followed the instructions in the documentation, everything is working fine but I get the following error

Model creation error: TypeError: Cannot read property 'companyIdKey' of undefined
 Model creation error: TypeError: Cannot read property 'name' of undefined
Model creation error: TypeError: Cannot read property 'undefined' of undefined
Model creation error: TypeError: Cannot read property 'undefined' of undefined
Model creation error: TypeError: Cannot read property 'undefined' of undefined
 Model creation error: TypeError: Cannot read property 'name' of undefined

It still seem that things works properly, but I am just a bit worried haha

Expected behavior

No error message

Actual behavior

Thing seem to work but I get the following error message

Failure Logs

Model creation error: TypeError: Cannot read property 'companyIdKey' of undefined

Context

Please provide any relevant information about your setup.

  • “forest-express-sequelize”: “^6.0.0”,
  • Express Version: “~4.16.3”
  • Sequelize Version: “~5.15.1”,
  • Database Dialect: Postgres
  • Database Version: “~6.1.0”,
  • Project Name: Toolt-mvp,
    “typescript”: “^3.9.7”

exemple of one of my models

import {
  Model, Sequelize, DataTypes
} from 'sequelize';
import { User } from './user';

export class Company extends Model {

  public id!: number;
  public name!: string;
  public users!: User[];
  public readonly created_at: Date;
  public readonly updated_at: Date;

}

export default (sequelize: Sequelize): typeof Company => {

  Company.init(
    {

      // uuid      : { type: DataTypes.UUID, },
      name      : { type: DataTypes.STRING, },
      created_at: {
        type        : 'TIMESTAMP',
        defaultValue: 'now()',
      },
      updated_at: { type: DataTypes.DATE, },
    }, {
      tableName  : 'company',
      modelName  : 'company',
      underscored: true,
      sequelize
    }
  );

  Company.hasMany(User, {
    foreignKey: {
      name : 'companyIdKey',
      field: 'company_id',
    },
    as: 'users',
  });

  return Company;

};

I also get this error

Model creation error: TypeError: defineCall is not a function

I think when each model is required (for relationship mapping they are not yet defined)

It runs okay at first but then the models will not update

Hi @Benjamin_Marquis,

We are trying to reproduce your issue :wink:. I’ll keep you posted

Hey @Benjamin_Marquis,

We did reproduce your issue. Your associations need to be defined inside an associate method like so:

export default (sequelize: Sequelize): typeof Company => {
  Company.init({ ... });

  Company.associate = (models) => {
    Company.hasMany(models.user, {
      foreignKey: {
        name : 'companyIdKey',
        field: 'company_id',
      },
      as: 'users',
    });
  };

  return Company;
};

And then it should work :pray:

1 Like

Hello @vince, thanks a lot for your help!
If I do so i get the following typescript error :thinking:

Property 'associate' does not exist on type 'typeof Company'

Yeah I do get the same error but it works on my side. One way to make it disappear is to move the associate method in your class declaration but I don’t really like it either :sweat_smile:… We’ll try to add some examples in the documentation on relationships to avoid this kind of issues

Hello, thanks for your help @vince It does work with associate inside model, as follow:

import { DataTypes, Sequelize } from 'sequelize';
import BaseModel from './base-model';
import { User } from './user';

export class Company extends BaseModel {

  public name!: string;
  public users!: User[];

  static associate = models => {

    Company.hasMany(models.user, {
      foreignKey: {
        name : 'companyIdKey',
        field: 'company_id',
      },
      as: 'users',
    });

  };

}

export default (sequelize: Sequelize): typeof Company => {

  Company.init(
    {

      // uuid      : { type: DataTypes.UUID, },
      name      : { type: DataTypes.STRING, },
      created_at: {
        type        : 'TIMESTAMP',
        defaultValue: 'now()',
      },
      updated_at: { type: DataTypes.DATE, },
    }, {
      tableName  : 'company',
      modelName  : 'company',
      underscored: true,
      sequelize
    }
  );

  return Company;

};

ps: BaseModel is a simple model like this

/* eslint-disable camelcase */
import { Model } from 'sequelize';

export default class BaseModel extends Model {

  public id!: number;
  public readonly created_at: Date;
  public readonly updated_at: Date;

}

(ps: i deleted a comment about something not working i just had a pending migration haha )

Fore any one figuring out thing with typescript here is an other exemple using a set for a related model

/* eslint-disable import/no-cycle */
import { Sequelize, DataTypes, BelongsToManySetAssociationsMixin } from 'sequelize';
import { Models } from 'types/model';
import { Label } from './label';
import { SoftwareType } from './software-type';
import { Recommandation } from './recommandation';
import { Feature } from './feature';
import { SoftwareFeature } from './software-feature';
import BaseModel from './base-model';

export class Software extends BaseModel {

  public domain!: string;
  public supportPage!: string;
  public name!: string;
  public logo!: string;

  public softwareType!: SoftwareType;
  public recommandations?: Recommandation[];
  public features!: Feature[];
  public labels?: Label[];
  public softwareFeature!: SoftwareFeature[];

  setFeatures: BelongsToManySetAssociationsMixin<Feature, Feature['id']>;

  static associate = (models: Models) => {

    Software.belongsTo(models.softwareType);

    Software.hasMany(models.recommandation);

    Software.belongsToMany(models.feature, {
      through   : 'software_feature',
      foreignKey: 'software_id',
      otherKey  : 'feature_id',
    });

    Software.hasMany(models.label);

    Software.hasMany(models.softwareFeature, {
      foreignKey: {
        field: 'software_id',
      },
      as: 'softwareFeature'
    });

  };

}

export default (sequelize: Sequelize): typeof Software => {

  Software.init({

    // uuid       : { type: DataTypes.UUID, },
    name       : { type: DataTypes.STRING, },
    logo       : { type: DataTypes.STRING, },
    domain     : { type: DataTypes.STRING, },
    supportPage: {
      type: DataTypes.STRING, field: 'support_page'
    },
    created_at: {
      type        : 'TIMESTAMP',
      defaultValue: 'now()',
    },
    updated_at: { type: DataTypes.DATE, },
  }, {
    hooks: {
      async afterCreate(software) {

        const { feature } = sequelize.models;

        await software.setFeatures(await feature.findAll() as Feature[]);

      },
    },
    tableName  : 'software',
    modelName  : 'software',
    underscored: true,
    sequelize
  });

  return Software;

};

@vince little detail but the article referenced in the doc https://vivacitylabs.com/setup-typescript-sequelize/
is sligthly outdated

Thanks for the date again