sequelize-typescript: createIndexDecorator has problems with "underscored" or "field" params

Versions

  • sequelize: 5.21.3
  • sequelize-typescript: 1.1.0
  • typescript 3.7.4

I’m submitting a … [x] bug report [ ] feature request

Actual behavior: I’ve tried using new feature - createIndexDecorator, but it doesn’t take into account the underscored table param (field param on the column doesn’t work either) - therefor it errors out on database level, because there are no such fields existing.

Expected behavior: Successful creation of index.

Steps to reproduce: Code is below.

Related code:

const OwnedItemUniqueIndex = createIndexDecorator({type: "UNIQUE"});

@Table({
  tableName: "game_owned_items",
  underscored: true
})
export default class OwnedItem extends Model<OwnedItem> {
  @PrimaryKey
  @AutoIncrement
  @Column
  public id: number;

  @OwnedItemUniqueIndex
  @Column
  public ownerId: number;

  @OwnedItemUniqueIndex
  @Column
  public itemId: number;
}

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 11
  • Comments: 17 (3 by maintainers)

Most upvoted comments

I’ve found another potential fix for this, by not using the @Index decorator at all but instead just specifying the indices in the @Table decorator instead, and manually specifying the field(s). Like this:

@Table({
  tableName: 'user',
  underscored: true,
  indexes: [
    {
      // Column name with underscores here
      fields: ['first_name'],
    },
  ],
})
export class User extends Model<User> {
  @PrimaryKey
  @Column
  id: string;

  @Column
  firstName: string;
});

I’ve also been experimenting with a decorator wrapper:

import { snakeCase } from 'lodash';
import { annotateModelWithIndex } from 'sequelize-typescript';

function UnderscoredIndex<T>(target: T, key: string): void {
    annotateModelWithIndex(target, snakeCase(key));
}

Then replace @Index with @UnderscoredIndex.

Still not optimal.

Same here when i’m set underscored property is true (Cuz they missing underscore when create indexing) It is critical issues on me … ;

Same here. It’s about @Index itself, not just others. I think it is quite critical issue for who has already existing schemas.

It seems to me considering to change sequelize-typescript to TypeORM…

I’m still having this problem with sequalize-typescript - 2.1.5

If anyone is interested, extended @jessemyers 's solution to a generic decorator, so we could use it with parameters:

declare type IndexDecoratorOptions = IndexOptions & Pick<IndexFieldOptions, Exclude<keyof IndexFieldOptions, 'name'>>;

type AnnotationFunction = <T>(
    target: T,
    propertyName: string,
) => void;

/**
 * Support @UnderscoredIndex('index_name')
 */
export function UnderscoredIndex(name: string): AnnotationFunction;

/**
 * Support @UnderscoredIndex({ name: 'index_name', unique: true })
 */
export function UnderscoredIndex(indexOptions: IndexOptions): AnnotationFunction;

/**
 * Support @UnderscoredIndex
 */
export function UnderscoredIndex<T>(
    target: T,
    propertyName: string,
    indexDecoratorOptions?: IndexDecoratorOptions,
): void;

/**
 * Overloaded decorator to support all variants of @UnderscoredIndex
 */
export function UnderscoredIndex<T>(...args: unknown[]): AnnotationFunction | void {
    if (arguments.length >= 2) {
        const type: T = <T>args[0];
        const key: string = <string>args[1];
        const indexDecoratorOptions: IndexDecoratorOptions = <IndexDecoratorOptions> args[2];
        annotateModelWithIndex(type, snakeCase(key), indexDecoratorOptions);
    } else {
        return <Type>(target: Type, propertyName: string) => {
            const indexDecoratorOptions: IndexDecoratorOptions = <IndexDecoratorOptions> args[0];
            annotateModelWithIndex(target, snakeCase(propertyName), indexDecoratorOptions);
        };
    }
}

EDIT: Don’t use this over-complicated solution. Just use jessemyers solution here instead if you want a decorator that works. Or use my other solution if you don’t like decorators for some reason.

I also have this issue, and as jessemyers have pointed out, the problem is directly related to the @Index decorator, not just the createIndexDecorator function.

My solution was to use two fields, one with the correct name for the database (database field) and one virtual field for the code (application field) with magic get/set functions.

my_field = database field
myField = application field
@Table({
  tableName: 'my_table',
  underscored: true,
})
export class MyModel extends Model<MyModel> {
  @PrimaryKey
  @Column
  id: string;

  @Index
  @Column
  my_field: string;

  @Column({
    type: DataType.VIRTUAL,
    set(myField: string) {
      this.setDataValue('my_field', myField);
    },
    get(): string {
      return this.getDataValue('my_field');
    },
  })
  myField: string;
}

This solution requires some extra code, but the application field will be fully working, just like a normal field. And the database field is persisted and named correctly in the database so when this bug is fixed you just need to remove this workaround code.

Just remember that, with this solution, when creating a new instance of this model, both the database and application fields will be available. Like myInstance.my_field and myInstance.myField.

This issue will not be fixed in sequelize-typescript, but it is fixed in @sequelize/core v7 which has TypeScript as a first class citizen and removes the need for sequelize-typescript. @sequelize/core v7 is still in alpha, but quite stable. Once the first full version is out we will start to deprecate sequelize-typescript

Fixed as part of the merge of sequelize-typescript in sequelize https://github.com/sequelize/sequelize/pull/15482

I have a similar problem where:

@Table({
    underscored: true,
})
class Foo extends Model<Foo> {
    @Index
    @CreatedAt
    createdAt: Date;
}

raises:

SequelizeDatabaseError: column "createdAt" does not exist

My workaround is:

@Table({
    underscored: true,
})
class Foo extends Model<Foo> {
    @Index
    @CreatedAt
    createdAt: Date;

    @Index
    // eslint-disable-next-line @typescript-eslint/camelcase
    get created_at(): Date {
        return this.createdAt;
    }
}

I would love not to have to do this…

“Temporary fix”

const OwnedItemUniqueIndex = createIndexDecorator({ type: "UNIQUE" });

@Table({
  tableName: "game_owned_items",
  underscored: true
})
export default class OwnedItem extends Model<OwnedItem> {
  @PrimaryKey
  @AutoIncrement
  @Column
  public id: number;

  // @ts-ignore
  @OwnedItemUniqueIndex({ name: "owner_id"})
  @ForeignKey(() => Soldier)
  @Column
  public ownerId: number;

  // @ts-ignore
  @OwnedItemUniqueIndex({ name: "item_id" })
  @ForeignKey(() => Item)
  @Column
  public itemId: number;
}

Works with PostgreSQL.