Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Decorators and some other features for sequelize

License

NotificationsYou must be signed in to change notification settings

sequelize/sequelize-typescript

Repository files navigation

Build StatuscodecovNPM

Decorators and some other features for sequelize (v6).

Installation

npm install --save-dev @types/node @types/validatornpm install sequelize reflect-metadata sequelize-typescript

Yourtsconfig.json needs the following flags:

"target":"es6",// or a more recent ecmascript version"experimentalDecorators":true,"emitDecoratorMetadata":true

Sequelize Options

  • SequelizeConfig renamed toSequelizeOptions
  • modelPaths property renamed tomodels

Scopes Options

The@Scopes and@DefaultScope decorators now take lambda's as options

@DefaultScope(()=>({...}))@Scopes(()=>({...}))

instead of deprecated way:

@DefaultScope({...})@Scopes({...}))

Model definition

import{Table,Column,Model,HasMany}from'sequelize-typescript';@TableclassPersonextendsModel{  @Columnname:string;  @Columnbirthday:Date;  @HasMany(()=>Hobby)hobbies:Hobby[];}

Less strict

import{Table,Model}from'sequelize-typescript';@TableclassPersonextendsModel{}

More strict

import{Optional}from'sequelize';import{Table,Model}from'sequelize-typescript';interfacePersonAttributes{id:number;name:string;}interfacePersonCreationAttributesextendsOptional<PersonAttributes,'id'>{}@TableclassPersonextendsModel<PersonAttributes,PersonCreationAttributes>{}

The model needs to extend theModel class and has to be annotated with the@Table decorator. All properties thatshould appear as a column in the database require the@Column annotation.

See more advanced examplehere.

@Table

The@Table annotation can be used without passing any parameters. To specify some more define options, usean object literal (alldefine optionsfrom sequelize are valid):

@Table({timestamps:true,  ...})classPersonextendsModel{}

Table API

DecoratorDescription
@Tablesetsoptions.tableName=<CLASS_NAME> andoptions.modelName=<CLASS_NAME> automatically
@Table(options: DefineOptions)setsdefine options (also setsoptions.tableName=<CLASS_NAME> andoptions.modelName=<CLASS_NAME> if not already defined by define options)

Primary key

A primary key (id) will be inherited from base classModel. This primary key is by default anINTEGER and hasautoIncrement=true (This behaviour is a native sequelize thing). The id can easily be overridden by marking anotherattribute as primary key. So either set@Column({primaryKey: true}) or use@PrimaryKey together with@Column.

@CreatedAt,@UpdatedAt,@DeletedAt

Annotations to define custom and type safecreatedAt,updatedAt anddeletedAt attributes:

  @CreatedAt  creationDate:Date;  @UpdatedAt  updatedOn:Date;  @DeletedAt  deletionDate:Date;
DecoratorDescription
@CreatedAtsetstimestamps=true andcreatedAt='creationDate'
@UpdatedAtsetstimestamps=true andupdatedAt='updatedOn'
@DeletedAtsetstimestamps=true,paranoid=true anddeletedAt='deletionDate'

@Column

The@Column annotation can be used without passing any parameters. But therefore it is necessary thatthe js type can be inferred automatically (seeType inference for details).

  @Column  name:string;

If the type cannot or should not be inferred, use:

import{DataType}from'sequelize-typescript';  @Column(DataType.TEXT)  name:string;

Or for a more detailed column description, use an object literal(allattribute optionsfrom sequelize are valid):

  @Column({type:DataType.FLOAT,comment:'Some value',    ...})  value:number;

Column API

DecoratorDescription
@Columntries to inferdataType from js type
@Column(dataType: DataType)setsdataType explicitly
@Column(options: AttributeOptions)setsattribute options

Shortcuts

If you're in love with decorators:sequelize-typescript provides some more of them. The following decorators can beused together with the @Column annotation to make some attribute options easier available:

DecoratorDescriptionOptions
@AllowNull(allowNull?: boolean)setsattribute.allowNull (default istrue)
@AutoIncrementsetsattribute.autoIncrement=true
@Unique(options? UniqueOptions)setsattribute.unique=trueUniqueOptions
@Default(value: any)setsattribute.defaultValue to specified value
@PrimaryKeysetsattribute.primaryKey=true
@Comment(value: string)setsattribute.comment to specified string
Validate annotationsseeModel validation

Type inference

The following types can be automatically inferred from javascript type. Others have to be defined explicitly.

Design typeSequelize data type
stringSTRING
booleanBOOLEAN
numberINTEGER
bigintBIGINT
DateDATE
BufferBLOB

Accessors

Get/set accessors do work as well

@TableclassPersonextendsModel{  @Columngetname():string{return'My name is '+this.getDataValue('name');}setname(value:string){this.setDataValue('name',value);}}

Usage

Except for minor variationssequelize-typescript will work like pure sequelize.(See sequelizedocs)

Configuration

To make the defined models available, you have to configure aSequelize instance fromsequelize-typescript(!).

import{Sequelize}from'sequelize-typescript';constsequelize=newSequelize({database:'some_db',dialect:'sqlite',username:'root',password:'',storage:':memory:',models:[__dirname+'/models'],// or [Player, Team],});

Before you can use your models you have to tell sequelize where they can be found. So either setmodels in thesequelize config or add the required models later on by callingsequelize.addModels([Person]) orsequelize.addModels([__dirname + '/models']):

sequelize.addModels([Person]);sequelize.addModels(['path/to/models']);

globs

import{Sequelize}from'sequelize-typescript';constsequelize=newSequelize({        ...models:[__dirname+'/**/*.model.ts']});// orsequelize.addModels([__dirname+'/**/*.model.ts']);

Model-path resolving

A model is matched to a file by its filename. E.g.

// File User.ts matches the following exported model.exportclassUserextendsModel{}

This is done by comparison of the filename against all exported members. Thematching can be customized by specifying themodelMatch function in theconfiguration object.

For example, if your models are nameduser.model.ts, and your class is calledUser, you can match these two by using the following function:

import{Sequelize}from'sequelize-typescript';constsequelize=newSequelize({models:[__dirname+'/models/**/*.model.ts']modelMatch:(filename,member)=>{returnfilename.substring(0,filename.indexOf('.model'))===member.toLowerCase();},});

For each file that matches the*.model.ts pattern, themodelMatch functionwill be called with its exported members. E.g. for the following file

//user.model.tsimport{Table,Column,Model}from'sequelize-typescript';exportconstUserN='Not a model';exportconstNUser='Not a model';@TableexportclassUserextendsModel{  @Columnnickname:string;}

ThemodelMatch function will be called three times with the following arguments.

user.model UserN -> falseuser.model NUser -> falseuser.model User  -> true (User will be added as model)

Another way to match model to file is to make your model the default export.

exportdefaultclassUserextendsModel{}

⚠️ When using paths to add models, keep in mind that they will be loaded during runtime. This means that the pathmay differ from development time to execution time. For instance, using.ts extension within paths will only worktogether withts-node.

Build and create

Instantiation and inserts can be achieved in the good old sequelize way

constperson=Person.build({name:'bob',age:99});person.save();Person.create({name:'bob',age:99});

butsequelize-typescript also makes it possible to create instances withnew:

constperson=newPerson({name:'bob',age:99});person.save();

Find and update

Finding and updating entries does also work like using native sequelize. So see sequelizedocs for more details.

Person.findOne().then((person)=>{person.age=100;returnperson.save();});Person.update({name:'bobby',},{where:{id:1}}).then(()=>{});

Model association

Relations can be described directly in the model by the@HasMany,@HasOne,@BelongsTo,@BelongsToManyand@ForeignKey annotations.

One-to-many

@TableclassPlayerextendsModel{  @Columnname:string;  @Columnnum:number;  @ForeignKey(()=>Team)  @ColumnteamId:number;  @BelongsTo(()=>Team)team:Team;}@TableclassTeamextendsModel{  @Columnname:string;  @HasMany(()=>Player)players:Player[];}

That's all,sequelize-typescript does everything else for you. So when retrieving a team byfind

Team.findOne({include:[Player]}).then((team)=>{team.players.forEach((player)=>console.log(`Player${player.name}`));});

the players will also be resolved (when passinginclude: Player to the find options)

Many-to-many

@TableclassBookextendsModel{  @BelongsToMany(()=>Author,()=>BookAuthor)authors:Author[];}@TableclassAuthorextendsModel{  @BelongsToMany(()=>Book,()=>BookAuthor)books:Book[];}@TableclassBookAuthorextendsModel{  @ForeignKey(()=>Book)  @ColumnbookId:number;  @ForeignKey(()=>Author)  @ColumnauthorId:number;}

Type safethrough-table instance access

To access thethrough-table instance (instanceOfBookAuthor in the upper example) type safely, the typeneed to be set up manually. ForAuthor model it can be achieved like so:

  @BelongsToMany(()=>Book,()=>BookAuthor)  books:Array<Book&{BookAuthor:BookAuthor}>;

One-to-one

For one-to-one use@HasOne(...)(foreign key for the relation exists on the other model) and@BelongsTo(...) (foreign key for the relation exists on this model)

@ForeignKey,@BelongsTo,@HasMany,@HasOne,@BelongsToMany API

DecoratorDescription
@ForeignKey(relatedModelGetter: () => typeof Model)marks property asforeignKey for related class
@BelongsTo(relatedModelGetter: () => typeof Model)setsSourceModel.belongsTo(RelatedModel, ...) whileas is key of annotated property andforeignKey is resolved from source class
@BelongsTo(relatedModelGetter: () => typeof Model, foreignKey: string)setsSourceModel.belongsTo(RelatedModel, ...) whileas is key of annotated property andforeignKey is explicitly specified value
@BelongsTo(relatedModelGetter: () => typeof Model, options: AssociationOptionsBelongsTo)setsSourceModel.belongsTo(RelatedModel, ...) whileas is key of annotated property andoptions are additional association options
@HasMany(relatedModelGetter: () => typeof Model)setsSourceModel.hasMany(RelatedModel, ...) whileas is key of annotated property andforeignKey is resolved from target related class
@HasMany(relatedModelGetter: () => typeof Model, foreignKey: string)setsSourceModel.hasMany(RelatedModel, ...) whileas is key of annotated property andforeignKey is explicitly specified value
@HasMany(relatedModelGetter: () => typeof Model, options: AssociationOptionsHasMany)setsSourceModel.hasMany(RelatedModel, ...) whileas is key of annotated property andoptions are additional association options
@HasOne(relatedModelGetter: () => typeof Model)setsSourceModel.hasOne(RelatedModel, ...) whileas is key of annotated property andforeignKey is resolved from target related class
@HasOne(relatedModelGetter: () => typeof Model, foreignKey: string)setsSourceModel.hasOne(RelatedModel, ...) whileas is key of annotated property andforeignKey is explicitly specified value
@HasOne(relatedModelGetter: () => typeof Model, options: AssociationOptionsHasOne)setsSourceModel.hasOne(RelatedModel, ...) whileas is key of annotated property andoptions are additional association options
@BelongsToMany(relatedModelGetter: () => typeof Model, through: (() => typeof Model))setsSourceModel.belongsToMany(RelatedModel, {through: ThroughModel, ...}) whileas is key of annotated property andforeignKey/otherKey is resolved from through class
@BelongsToMany(relatedModelGetter: () => typeof Model, through: (() => typeof Model), foreignKey: string)setsSourceModel.belongsToMany(RelatedModel, {through: ThroughModel, ...}) whileas is key of annotated property,foreignKey is explicitly specified value andotherKey is resolved from through class
@BelongsToMany(relatedModelGetter: () => typeof Model, through: (() => typeof Model), foreignKey: string, otherKey: string)setsSourceModel.belongsToMany(RelatedModel, {through: ThroughModel, ...}) whileas is key of annotated property andforeignKey/otherKey are explicitly specified values
@BelongsToMany(relatedModelGetter: () => typeof Model, through: string, foreignKey: string, otherKey: string)setsSourceModel.belongsToMany(RelatedModel, {through: throughString, ...}) whileas is key of annotated property andforeignKey/otherKey are explicitly specified values
@BelongsToMany(relatedModelGetter: () => typeof Model, options: AssociationOptionsBelongsToMany)setsSourceModel.belongsToMany(RelatedModel, {through: throughString, ...}) whileas is key of annotated property andoptions are additional association values, includingforeignKey andotherKey.

Note that when using AssociationOptions, certain properties will be overwritten when the association is built, based on reflection metadata or explicit attribute parameters. For example,as will always be the annotated property's name, andthrough will be the explicitly stated value.

Multiple relations of same models

sequelize-typescript resolves the foreign keys by identifying the corresponding class references.So if you define a model with multiple relations like

@TableclassBookextendsModel{  @ForeignKey(()=>Person)  @ColumnauthorId:number;  @BelongsTo(()=>Person)author:Person;  @ForeignKey(()=>Person)  @ColumnproofreaderId:number;  @BelongsTo(()=>Person)proofreader:Person;}@TableclassPersonextendsModel{  @HasMany(()=>Book)writtenBooks:Book[];  @HasMany(()=>Book)proofedBooks:Book[];}

sequelize-typescript cannot know which foreign key to use for which relation. So you have to add the foreign keysexplicitly:

// in class "Books":  @BelongsTo(()=>Person,'authorId')  author:Person;  @BelongsTo(()=>Person,'proofreaderId')  proofreader:Person;// in class "Person":  @HasMany(()=>Book,'authorId')  writtenBooks:Book[];  @HasMany(()=>Book,'proofreaderId')  proofedBooks:Book[];

Type safe usage of auto generated functions

With the creation of a relation, sequelize generates some method on the correspondingmodels. So when you create a 1:n relation betweenModelA andModelB, an instance ofModelA willhave the functionsgetModelBs,setModelBs,addModelB,removeModelB,hasModelB. These functions still exist withsequelize-typescript.But TypeScript wont recognize them and will complain if you try to accessgetModelB,setModelB oraddModelB. To make TypeScript happy, theModel.prototype ofsequelize-typescript has$set,$get,$addfunctions.

@TableclassModelAextendsModel{  @HasMany(()=>ModelB)bs:ModelB[];}@TableclassModelBextendsModel{  @BelongsTo(()=>ModelA)a:ModelA;}

To use them, pass the property key of the respective relation as the first parameter:

constmodelA=newModelA();modelA.$set('bs',[/* instance */]).then(/* ... */);modelA.$add('b'/* instance */).then(/* ... */);modelA.$get('bs').then(/* ... */);modelA.$count('bs').then(/* ... */);modelA.$has('bs').then(/* ... */);modelA.$remove('bs'/* instance */).then(/* ... */);modelA.$create('bs'/* value */).then(/* ... */);

Indexes

@Index

The@Index annotation can be used without passing any parameters.

@TableclassPersonextendsModel{  @Index// Define an index with default name  @Columnname:string;  @Index// Define another index  @Columnbirthday:Date;}

To specify index and index field options, usean object literal (seeindexes define option):

@TableclassPersonextendsModel{  @Index('my-index')// Define a multi-field index on name and birthday  @Columnname:string;  @Index('my-index')// Add birthday as the second field to my-index  @Columnbirthday:Date;  @Index({// index optionsname:'job-index',parser:'my-parser',type:'UNIQUE',unique:true,where:{isEmployee:true},concurrently:true,using:'BTREE',operator:'text_pattern_ops',prefix:'test-',// index field optionslength:10,order:'ASC',collate:'NOCASE',})  @ColumnjobTitle:string;  @ColumnisEmployee:boolean;}

Index API

DecoratorDescription
@Indexadds new index on decorated field tooptions.indexes
@Index(name: string)adds new index or adds the field to an existing index with specified name
@Table(options: IndexDecoratorOptions)sets both index and index fieldoptions

createIndexDecorator()

ThecreateIndexDecorator() function can be used to create a decorator for an index with options specified with an object literal supplied as the argument. Fields are added to the index by decorating properties.

constSomeIndex=createIndexDecorator();constJobIndex=createIndexDecorator({// index optionsname:'job-index',parser:'my-parser',type:'UNIQUE',unique:true,where:{isEmployee:true},concurrently:true,using:'BTREE',operator:'text_pattern_ops',prefix:'test-',});@TableclassPersonextendsModel{  @SomeIndex// Add name to SomeIndex  @Columnname:string;  @SomeIndex// Add birthday to SomeIndex  @Columnbirthday:Date;  @JobIndex({// index field optionslength:10,order:'ASC',collate:'NOCASE',})  @ColumnjobTitle:string;  @ColumnisEmployee:boolean;}

Repository mode

Withsequelize-typescript@1 comes a repository mode. Seedocs for details.

The repository mode makes it possible to separate static operations likefind,create, ... from model definitions.It also empowers models so that they can be used with multiple sequelize instances.

How to enable repository mode?

Enable repository mode by settingrepositoryMode flag:

constsequelize=newSequelize({repositoryMode:true,  ...,});

How to use repository mode?

Retrieve repository to create instances or perform search operations:

constuserRepository=sequelize.getRepository(User);constluke=awaituserRepository.create({name:'Luke Skywalker'});constluke=awaituserRepository.findOne({where:{name:'luke'}});

How to use associations with repository mode?

For now one need to use the repositories within the include options in order to retrieve or create related data:

constuserRepository=sequelize.getRepository(User);constaddressRepository=sequelize.getRepository(Address);userRepository.find({include:[addressRepository]});userRepository.create({name:'Bear'},{include:[addressRepository]});

⚠️ This will change in the future: One will be able to refer the model classes instead of the repositories.

Limitations of repository mode

Nested scopes and includes in general won't work when using@Scope annotation together with repository mode like:

@Scopes(()=>({// includeswithAddress:{include:[()=>Address],},// nested scopeswithAddressIncludingLatLng:{include:[()=>Address.scope('withLatLng')],},}))@TableclassUserextendsModel{}

⚠️ This will change in the future: Simple includes will be implemented.

Model validation

Validation options can be set through the@Column annotation, but if you prefer to use separate decorators forvalidation instead, you can do so by simply adding the validate optionsas decorators:So thatvalidate.isEmail=true becomes@IsEmail,validate.equals='value' becomes@Equals('value')and so on. Please notice that a validator that expects a boolean is translated to an annotation without a parameter.

See sequelizedocsfor all validators.

Exceptions

The following validators cannot simply be translated from sequelize validator to an annotation:

ValidatorAnnotation
validate.len=[number, number]@Length({max?: number, min?: number})
validate[customName: string]For custom validators also use the@Is(...) annotation: Either@Is('custom', (value) => { /* ... */}) or with named function@Is(function custom(value) { /* ... */})

Example

constHEX_REGEX=/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;@TableexportclassShoeextendsModel{  @IsUUID(4)  @PrimaryKey  @Columnid:string;  @Equals('lala')  @Columnreadonlykey:string;  @Contains('Special')  @Columnspecial:string;  @Length({min:3,max:15})  @Columnbrand:string;  @IsUrl  @ColumnbrandUrl:string;  @Is('HexColor',(value)=>{if(!HEX_REGEX.test(value)){thrownewError(`"${value}" is not a hex color value.`);}})  @ColumnprimaryColor:string;  @Is(functionhexColor(value:string):void{if(!HEX_REGEX.test(value)){thrownewError(`"${value}" is not a hex color value.`);}})  @ColumnsecondaryColor:string;  @Is(HEX_REGEX)  @ColumntertiaryColor:string;  @IsDate  @IsBefore('2017-02-27')  @ColumnproducedAt:Date;}

Scopes

Scopes can be defined with annotations as well. The scope options are identical to nativesequelize (See sequelizedocs for more details)

@DefaultScope and@Scopes

@DefaultScope(()=>({attributes:['id','primaryColor','secondaryColor','producedAt'],}))@Scopes(()=>({full:{include:[Manufacturer],},yellow:{where:{primaryColor:'yellow'},},}))@TableexportclassShoeWithScopesextendsModel{  @ColumnreadonlysecretKey:string;  @ColumnprimaryColor:string;  @ColumnsecondaryColor:string;  @ColumnproducedAt:Date;  @ForeignKey(()=>Manufacturer)  @ColumnmanufacturerId:number;  @BelongsTo(()=>Manufacturer)manufacturer:Manufacturer;}

Hooks

Hooks can be attached to your models. All Model-level hooks are supported. Seethe related unit tests for a summary.

Each hook must be astatic method. Multiple hooks can be attached to a single method, and you can define multiple methods for a given hook.

The name of the method cannot be the same as the name of the hook (for example, a@BeforeCreate hook method cannot be namedbeforeCreate). That’s because Sequelize has pre-defined methods with those names.

@TableexportclassPersonextendsModel{  @Columnname:string;  @BeforeUpdate  @BeforeCreatestaticmakeUpperCase(instance:Person){// this will be called when an instance is created or updatedinstance.name=instance.name.toLocaleUpperCase();}  @BeforeCreatestaticaddUnicorn(instance:Person){// this will also be called when an instance is createdinstance.name+=' 🦄';}}

Why() => Model?

@ForeignKey(Model) is much easier to read, so why is@ForeignKey(() => Model) so important? When itcomes to circular-dependencies (which are in general solved by node for you)Model can beundefinedwhen it gets passed to @ForeignKey. With the usage of a function, which returns the actual model, we preventthis issue.

Recommendations and limitations

One Sequelize instance per model (without repository mode)

Unless you are using therepository mode, you won't be able to add one and the same model to multipleSequelize instances with differently configured connections. So that one model will only work for one connection.

One model class per file

This is not only good practice regarding design, but also matters for the orderof execution. Since Typescript creates a__metadata("design:type", SomeModel) call due toemitDecoratorMetadatacompile option, in some casesSomeModel is probably not defined(not undefined!) and would throw aReferenceError.When puttingSomeModel in a separate file, it would look like__metadata("design:type", SomeModel_1.SomeModel),which does not throw an error.

Minification

If you need to minify your code, you need to settableName andmodelNamein theDefineOptions for@Table annotation. sequelize-typescriptuses the class name as default name fortableName andmodelName.When the code is minified the class name will no longer be the originallydefined one (So thatclass User will becomeclass b for example).

Contributing

To contribute you can:

  • Open issues and participate in discussion of other issues.
  • Fork the project to open up PR's.
  • Update thetypes of Sequelize.
  • Anything else constructively helpful.

In order to open a pull request please:

  • Create a new branch.
  • Run tests locally (npm install && npm run build && npm run cover) and ensure your commits don't break the tests.
  • Document your work well with commit messages, a good PR description, comments in code when necessary, etc.

In order to update the types for sequelize please go tothe Definitely Typed repo, it would also be a goodidea to open a PR intosequelize so that Sequelize can maintain its own types, but thatmight be harder than getting updated types into microsoft's repo. The Typescript team is slowly trying to encouragenpm package maintainers to maintain their own typings, but Microsoft still has dedicated and good people maintaining the DT repo,accepting PR's and keeping quality high.

Keep in mindsequelize-typescript does not provide typings forsequelize - these are seperate things.A lot of the types insequelize-typescript augment, refer to, or extend what sequelize already has.

About

Decorators and some other features for sequelize

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp