sequelize / sequelize-typescript

Decorators and some other features for sequelize

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cannot use @DefaultScope() simultaneously in two related Models

caioalmeida12 opened this issue · comments

Issue

Cannot use @DefaultScope() simultaneously in two related Models

Versions

  • sequelize: 6.35.1
  • sequelize-typescript: 2.1.6
  • typescript: 5.3.3

Issue type

  • [ x] bug report
  • feature request

Actual behavior

I have two related models: Jogador and Responsavel, in which Jogador is the one referenced by Responsavel. I added a DefaultScope to Jogador, in which it retrieves all attributes and nested objects, including Responsavel; When it comes to Responsavel, i am trying to do the same: add a DefaultScope that retireves all attributes and the Jogador it is related to, but i cant do it because i keep getting the following error as soon as i add the DefaultScope to the ResponsavelModel (note the error only occurs once i add the DefaultScope to this one; if it is only in Jogador the error won't occur and the result will be as expected)

Expected behavior

It should let me query by default Jogador and retrieve it's Responsavel from database automatically; the same thing should happen with Responsavel, but in this case retrieving it's related Jogador.

Steps to reproduce

Start the sequelize-typescript instance and add DefaultScope to two related models

Related code

[tsconfig]
{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "nodemon --exec ts-node src/index.ts --watch src"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/bcrypt": "^5.0.0",
    "@types/cors": "^2.8.14",
    "@types/express": "^4.17.17",
    "@types/jest": "^29.5.4",
    "@types/morgan": "^1.9.9",
    "@types/node": "^20.6.5",
    "@types/sequelize": "^4.28.15",
    "@types/supertest": "^2.0.12",
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "jest": "^29.7.0",
    "morgan": "^1.10.0",
    "nodemon": "^3.0.1",
    "ts-jest": "^29.1.1",
    "ts-node": "^10.9.1",
    "tsc-alias": "^1.8.7",
    "typescript": "^5.1.6"
  },
  "include": [
    "src/**/*.ts"
  ],
  "dependencies": {
    "bcrypt": "^5.1.1",
    "cors": "^2.8.5",
    "helmet": "^7.1.0",
    "mysql2": "^3.6.1",
    "sequelize": "^6.33.0",
    "sequelize-typescript": "^2.1.6",
    "sqlite3": "^5.1.6",
    "tsconfig-paths": "^4.2.0",
    "utility-types": "^3.10.0",
    "zod": "^3.22.2"
  }
}

[sequelize-typescript instance]

import path from "path";
import { Dialect } from "sequelize";
import { Sequelize } from "sequelize-typescript";

if (!["sqlite", "postgres"].includes(process.env.DB_DIALECT as string)) throw new Error(`Database dialect "${process.env.DB_DIALECT}" not supported`);

let sequelizeConfig = {
    dialect: process.env.DB_DIALECT as Dialect,
    host: process.env.DB_HOST,
    port: Number(process.env.DB_PORT),
    models: [path.resolve(__dirname, '../models/')],
    storage: "" as string | undefined,
    username: "" as string | undefined,
    password: "" as string | undefined,
    database: "" as string | undefined,
    logging: Boolean(process.env.DB_LOGGING == "true") ? console.log : false,
}

switch (process.env.DB_DIALECT) {
    case "sqlite":
        sequelizeConfig = {
            ...sequelizeConfig,
            storage: process.env.DB_STORAGE,
        };
        break;
    case "postgres":
        sequelizeConfig = {
            ...sequelizeConfig,
            username: process.env.DB_USER,
            password: process.env.DB_PASS,
            database: process.env.DB_NAME,
        };
        break;
}

const sequelize = new Sequelize(sequelizeConfig);

sequelize.sync().then(() => {
    console.log(`\x1b[36m\nSuccessfully connected to database "${process.env.DB_NAME}" with "${process.env.DB_DIALECT}" dialect\n\x1b[0m`);
}).catch((error) => {
    console.log(`\x1b[31m\nError connecting to database "${process.env.DB_NAME}" with "${process.env.DB_DIALECT}" dialect\n\x1b[0m`);
    console.log(error);
});

export default sequelize;

[JogadorModel]

import { JogadorType } from '@lib/types/jogadorType';
import { AllowNull, Column, Length, Table, DataType, Model, ForeignKey, BelongsTo, HasMany, Min, Max, PrimaryKey, Unique, Scopes, DefaultScope, Default, HasOne} from "sequelize-typescript";

import ResponsavelModel from './responsavelModel';

@DefaultScope(() => ({
    include: {
        all: true,
        nested: true,
    }
}))
@Table({
    tableName: process.env.MODEL_JOGADOR_TABLE_NAME,
    paranoid: true,
})
export default class JogadorModel extends Model<JogadorType, Omit<JogadorType, "id">> {
    @PrimaryKey
    @Default(DataType.UUIDV4)
    @Column(DataType.UUIDV4)
    declare id: string;

    @Unique
    @AllowNull(false)
    @Length({ min: 11, max: 11 })
    @Column(DataType.STRING(11))
    declare cpf: string;   

    @AllowNull(false)
    @Length({ min: 1, max: 128 })
    @Column(DataType.STRING(128))
    declare nome_completo: string;

    @AllowNull(false)
    @Length({ min: 11, max: 13 })
    @Column(DataType.STRING(13))
    declare telefone: string;

    @AllowNull(false)
    @Length({ min: 1, max: 128 })
    @Column(DataType.STRING(128))
    declare email: string;

    @HasOne(() => ResponsavelModel, {
        onDelete: "CASCADE",
        onUpdate: "CASCADE",
    })
    declare responsavel: ResponsavelModel;
}

[ResponsavelModel]

import { ResponsavelType } from '@lib/types/responsavelType';
import { AllowNull, Column, Length, Table, DataType, Model, ForeignKey, BelongsTo, HasMany, Min, Max, PrimaryKey, Unique, Scopes, DefaultScope, Default, HasOne } from "sequelize-typescript";

import JogadorModel from './jogadorModel';

@DefaultScope(() => ({
    include: {
        all: true,
        nested: true,
    }
}))
@Table({
    tableName: process.env.MODEL_RESPONSAVEL_TABLE_NAME,
    paranoid: true,
})
export default class ResponsavelModel extends Model<ResponsavelType, Omit<ResponsavelType, "id">> {
    @PrimaryKey
    @Default(DataType.UUIDV4)
    @Column(DataType.UUIDV4)
    declare id: string;

    @Unique
    @AllowNull(false)
    @Length({ min: 11, max: 11 })
    @Column(DataType.STRING(11))
    declare cpf: string;

    @AllowNull(false)
    @Length({ min: 1, max: 128 })
    @Column(DataType.STRING(128))
    declare nome_completo: string;

    @AllowNull(false)
    @Length({ min: 11, max: 13 })
    @Column(DataType.STRING(13))
    declare telefone: string;

    @AllowNull(false)
    @Length({ min: 1, max: 128 })
    @Column(DataType.STRING(128))
    declare email: string;

    @ForeignKey(() => JogadorModel)
    @Column(DataType.UUIDV4)
    declare fk_jogador_id: string;

    @BelongsTo(() => JogadorModel, {
        onDelete: "CASCADE",
        onUpdate: "CASCADE",
    })
    declare jogador: JogadorModel;
}

[Error]

TypeError: Cannot read properties of undefined (reading 'getTableName')
    at Function._validateIncludedElement (C:\Users\caiod\Desktop\campeonato-municipal\server\node_modules\sequelize\src\model.js:611:30)     
    at C:\Users\caiod\Desktop\campeonato-municipal\server\node_modules\sequelize\src\model.js:542:37
    at Array.map (<anonymous>)
    at Function._validateIncludedElements (C:\Users\caiod\Desktop\campeonato-municipal\server\node_modules\sequelize\src\model.js:537:39)    
    at Function._validateIncludedElement (C:\Users\caiod\Desktop\campeonato-municipal\server\node_modules\sequelize\src\model.js:732:38)     
    at C:\Users\caiod\Desktop\campeonato-municipal\server\node_modules\sequelize\src\model.js:542:37
    at Array.map (<anonymous>)
    at Function._validateIncludedElements (C:\Users\caiod\Desktop\campeonato-municipal\server\node_modules\sequelize\src\model.js:537:39)    
    at Function.findAll (C:\Users\caiod\Desktop\campeonato-municipal\server\node_modules\sequelize\src\model.js:1794:12)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

Update: the issue seems to be related to recursive including one class onto another within the DefaultScopes, as i can workaround it by doing the following:

import { ResponsavelType } from '@lib/types/responsavelType';
import { AllowNull, Column, Length, Table, DataType, Model, ForeignKey, BelongsTo, HasMany, Min, Max, PrimaryKey, Unique, Scopes, DefaultScope, Default, HasOne } from "sequelize-typescript";

import JogadorModel from './jogadorModel';

@DefaultScope(() => ({
    include: [JogadorModel.unscoped()]
}))
@Table({
    tableName: process.env.MODEL_RESPONSAVEL_TABLE_NAME,
    paranoid: true,
})
export default class ResponsavelModel extends Model<ResponsavelType, Omit<ResponsavelType, "id">> {
    @PrimaryKey
    @Default(DataType.UUIDV4)
    @Column(DataType.UUIDV4)
    declare id: string;

    @Unique
    @AllowNull(false)
    @Length({ min: 11, max: 11 })
    @Column(DataType.STRING(11))
    declare cpf: string;

    @AllowNull(false)
    @Length({ min: 1, max: 128 })
    @Column(DataType.STRING(128))
    declare nome_completo: string;

    @AllowNull(false)
    @Length({ min: 11, max: 13 })
    @Column(DataType.STRING(13))
    declare telefone: string;

    @AllowNull(false)
    @Length({ min: 1, max: 128 })
    @Column(DataType.STRING(128))
    declare email: string;

    @ForeignKey(() => JogadorModel)
    @Column(DataType.UUIDV4)
    declare fk_jogador_id: string;

    @BelongsTo(() => JogadorModel, {
        onDelete: "CASCADE",
        onUpdate: "CASCADE",
    })
    declare jogador: JogadorModel;
}