UNPKG

35.7 kBMarkdownView Raw
1# sequelize-typescript
2
3[![Build Status](https://github.com/RobinBuschmann/sequelize-typescript/workflows/Node.js%20CI/badge.svg)](https://github.com/RobinBuschmann/sequelize-typescript/actions?query=workflow%3A%22Node.js+CI%22)
4[![codecov](https://codecov.io/gh/RobinBuschmann/sequelize-typescript/branch/master/graph/badge.svg)](https://codecov.io/gh/RobinBuschmann/sequelize-typescript)
5[![NPM](https://img.shields.io/npm/v/sequelize-typescript.svg)](https://www.npmjs.com/package/sequelize-typescript)
6[![Dependency Status](https://img.shields.io/david/RobinBuschmann/sequelize-typescript.svg)](https://www.npmjs.com/package/sequelize-typescript)
7
8Decorators and some other features for sequelize (v6).
9
10- [Installation](#installation)
11- [Model Definition](#model-definition)
12 - [`@Table` API](#table-api)
13 - [`@Column` API](#column-api)
14- [Usage](#usage)
15 - [Configuration](#configuration)
16 - [globs](#globs)
17 - [Model-path resolving](#model-path-resolving)
18- [Model association](#model-association)
19 - [One-to-many](#one-to-many)
20 - [Many-to-many](#many-to-many)
21 - [One-to-one](#one-to-one)
22 - [`@ForeignKey`, `@BelongsTo`, `@HasMany`, `@HasOne`, `@BelongsToMany` API](#foreignkey-belongsto-hasmany-hasone-belongstomany-api)
23 - [Generated getter and setter](#type-safe-usage-of-auto-generated-functions)
24 - [Multiple relations of same models](#multiple-relations-of-same-models)
25- [Indexes](#indexes)
26 - [`@Index` API](#index)
27 - [`createIndexDecorator()` API](#createindexdecorator)
28- [Repository mode](#repository-mode)
29 - [How to enable repository mode?](#how-to-enable-repository-mode)
30 - [How to use repository mode?](#how-to-use-repository-mode)
31 - [How to use associations with repository mode?](#how-to-use-associations-with-repository-mode)
32 - [Limitations of repository mode](#limitations-of-repository-mode)
33- [Model validation](#model-validation)
34- [Scopes](#scopes)
35- [Hooks](#hooks)
36- [Why `() => Model`?](#user-content-why---model)
37- [Recommendations and limitations](#recommendations-and-limitations)
38
39## Installation
40
41- this assumes usage of `sequelize@6`
42- _sequelize-typescript_ requires [sequelize](https://github.com/sequelize/sequelize)
43- additional typings as documented [here](https://sequelize.org/master/manual/typescript.html) and [reflect-metadata](https://www.npmjs.com/package/reflect-metadata)
44
45```sh
46npm install --save-dev @types/node @types/validator
47npm install sequelize reflect-metadata sequelize-typescript
48```
49
50Your `tsconfig.json` needs the following flags:
51
52```json
53"target": "es6", // or a more recent ecmascript version
54"experimentalDecorators": true,
55"emitDecoratorMetadata": true
56```
57
58### Sequelize Options
59
60- `SequelizeConfig` renamed to `SequelizeOptions`
61- `modelPaths` property renamed to `models`
62
63### Scopes Options
64
65The `@Scopes` and `@DefaultScope` decorators now take lambda's as options
66
67```ts
68@DefaultScope(() => ({...}))
69@Scopes(() => ({...}))
70```
71
72instead of deprecated way:
73
74```ts
75@DefaultScope({...})
76@Scopes({...}))
77```
78
79## Model definition
80
81```typescript
82import { Table, Column, Model, HasMany } from 'sequelize-typescript'
83
84@Table
85class Person extends Model {
86 @Column
87 name: string
88
89 @Column
90 birthday: Date
91
92 @HasMany(() => Hobby)
93 hobbies: Hobby[]
94}
95```
96
97### Less strict
98
99```typescript
100import { Table, Model } from 'sequelize-typescript'
101
102@Table
103class Person extends Model {}
104```
105
106### More strict
107
108```typescript
109import { Optional } from 'sequelize'
110import { Table, Model } from 'sequelize-typescript'
111
112interface PersonAttributes {
113 id: number
114 name: string
115}
116
117interface PersonCreationAttributes extends Optional<PersonAttributes, 'id'> {}
118
119@Table
120class Person extends Model<PersonAttributes, PersonCreationAttributes> {}
121```
122
123The model needs to extend the `Model` class and has to be annotated with the `@Table` decorator. All properties that
124should appear as a column in the database require the `@Column` annotation.
125
126See more advanced example [here](https://github.com/RobinBuschmann/sequelize-typescript-example).
127
128### `@Table`
129
130The `@Table` annotation can be used without passing any parameters. To specify some more define options, use
131an object literal (all [define options](https://sequelize.org/v5/manual/models-definition.html#configuration)
132from sequelize are valid):
133
134```typescript
135@Table({
136 timestamps: true,
137 ...
138})
139class Person extends Model {}
140```
141
142#### Table API
143
144| Decorator | Description |
145| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
146| `@Table` | sets `options.tableName=<CLASS_NAME>` and `options.modelName=<CLASS_NAME>` automatically |
147| `@Table(options: DefineOptions)` | sets [define options](https://sequelize.org/v5/manual/models-definition.html#configuration) (also sets `options.tableName=<CLASS_NAME>` and `options.modelName=<CLASS_NAME>` if not already defined by define options) |
148
149#### Primary key
150
151A primary key (`id`) will be inherited from base class `Model`. This primary key is by default an `INTEGER` and has
152`autoIncrement=true` (This behaviour is a native sequelize thing). The id can easily be overridden by marking another
153attribute as primary key. So either set `@Column({primaryKey: true})` or use `@PrimaryKey` together with `@Column`.
154
155#### `@CreatedAt`, `@UpdatedAt`, `@DeletedAt`
156
157Annotations to define custom and type safe `createdAt`, `updatedAt` and `deletedAt` attributes:
158
159```typescript
160 @CreatedAt
161 creationDate: Date;
162
163 @UpdatedAt
164 updatedOn: Date;
165
166 @DeletedAt
167 deletionDate: Date;
168```
169
170| Decorator | Description |
171| ------------ | ---------------------------------------------------------------------- |
172| `@CreatedAt` | sets `timestamps=true` and `createdAt='creationDate'` |
173| `@UpdatedAt` | sets `timestamps=true` and `updatedAt='updatedOn'` |
174| `@DeletedAt` | sets `timestamps=true`, `paranoid=true` and `deletedAt='deletionDate'` |
175
176### `@Column`
177
178The `@Column` annotation can be used without passing any parameters. But therefore it is necessary that
179the js type can be inferred automatically (see [Type inference](#type-inference) for details).
180
181```typescript
182 @Column
183 name: string;
184```
185
186If the type cannot or should not be inferred, use:
187
188```typescript
189import {DataType} from 'sequelize-typescript';
190
191 @Column(DataType.TEXT)
192 name: string;
193```
194
195Or for a more detailed column description, use an object literal
196(all [attribute options](https://sequelize.org/v5/manual/models-definition.html#configuration)
197from sequelize are valid):
198
199```typescript
200 @Column({
201 type: DataType.FLOAT,
202 comment: 'Some value',
203 ...
204 })
205 value: number;
206```
207
208#### Column API
209
210| Decorator | Description |
211| ------------------------------------ | --------------------------------------------------------------------------------------------------------- |
212| `@Column` | tries to infer [dataType](https://sequelize.org/v5/manual/models-definition.html#data-types) from js type |
213| `@Column(dataType: DataType)` | sets [dataType](https://sequelize.org/v5/manual/models-definition.html#data-types) explicitly |
214| `@Column(options: AttributeOptions)` | sets [attribute options](https://sequelize.org/v5/manual/models-definition.html#configuration) |
215
216#### _Shortcuts_
217
218If you're in love with decorators: _sequelize-typescript_ provides some more of them. The following decorators can be
219used together with the @Column annotation to make some attribute options easier available:
220
221| Decorator | Description | Options |
222| --------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
223| `@AllowNull(allowNull?: boolean)` | sets `attribute.allowNull` (default is `true`) |
224| `@AutoIncrement` | sets `attribute.autoIncrement=true` |
225| `@Unique(options? UniqueOptions)` | sets `attribute.unique=true` | [UniqueOptions](https://github.com/RobinBuschmann/sequelize-typescript/blob/master/src/model/column/column-options/unique.ts#L3) |
226| `@Default(value: any)` | sets `attribute.defaultValue` to specified value |
227| `@PrimaryKey` | sets `attribute.primaryKey=true` |
228| `@Comment(value: string)` | sets `attribute.comment` to specified string |
229| Validate annotations | see [Model validation](#model-validation) |
230
231### Type inference
232
233The following types can be automatically inferred from javascript type. Others have to be defined explicitly.
234
235| Design type | Sequelize data type |
236| ----------- | ------------------- |
237| `string` | `STRING` |
238| `boolean` | `BOOLEAN` |
239| `number` | `INTEGER` |
240| `bigint` | `BIGINT` |
241| `Date` | `DATE` |
242| `Buffer` | `BLOB` |
243
244### Accessors
245
246Get/set accessors do work as well
247
248```typescript
249@Table
250class Person extends Model {
251 @Column
252 get name(): string {
253 return 'My name is ' + this.getDataValue('name')
254 }
255
256 set name(value: string) {
257 this.setDataValue('name', value)
258 }
259}
260```
261
262## Usage
263
264Except for minor variations _sequelize-typescript_ will work like pure sequelize.
265(See sequelize [docs](https://docs.sequelizejs.com/manual/tutorial/models-usage.html))
266
267### Configuration
268
269To make the defined models available, you have to configure a `Sequelize` instance from `sequelize-typescript`(!).
270
271```typescript
272import { Sequelize } from 'sequelize-typescript'
273
274const sequelize = new Sequelize({
275 database: 'some_db',
276 dialect: 'sqlite',
277 username: 'root',
278 password: '',
279 storage: ':memory:',
280 models: [__dirname + '/models'] // or [Player, Team],
281})
282```
283
284Before you can use your models you have to tell sequelize where they can be found. So either set `models` in the
285sequelize config or add the required models later on by calling `sequelize.addModels([Person])` or
286`sequelize.addModels([__dirname + '/models'])`:
287
288```typescript
289sequelize.addModels([Person])
290sequelize.addModels(['path/to/models'])
291```
292
293### globs
294
295```typescript
296import {Sequelize} from 'sequelize-typescript';
297
298const sequelize = new Sequelize({
299 ...
300 models: [__dirname + '/**/*.model.ts']
301});
302// or
303sequelize.addModels([__dirname + '/**/*.model.ts']);
304```
305
306#### Model-path resolving
307
308A model is matched to a file by its filename. E.g.
309
310```typescript
311// File User.ts matches the following exported model.
312export class User extends Model {}
313```
314
315This is done by comparison of the filename against all exported members. The
316matching can be customized by specifying the `modelMatch` function in the
317configuration object.
318
319For example, if your models are named `user.model.ts`, and your class is called
320`User`, you can match these two by using the following function:
321
322```typescript
323import {Sequelize} from 'sequelize-typescript';
324
325const sequelize = new Sequelize({
326 models: [__dirname + '/models/**/*.model.ts']
327 modelMatch: (filename, member) => {
328 return filename.substring(0, filename.indexOf('.model')) === member.toLowerCase();
329 },
330});
331```
332
333For each file that matches the `*.model.ts` pattern, the `modelMatch` function
334will be called with its exported members. E.g. for the following file
335
336```TypeScript
337//user.model.ts
338import {Table, Column, Model} from 'sequelize-typescript';
339
340export const UserN = 'Not a model';
341export const NUser = 'Not a model';
342
343@Table
344export class User extends Model {
345
346 @Column
347 nickname: string;
348}
349```
350
351The `modelMatch` function will be called three times with the following arguments.
352
353```text
354user.model UserN -> false
355user.model NUser -> false
356user.model User -> true (User will be added as model)
357```
358
359Another way to match model to file is to make your model the default export.
360
361```TypeScript
362export default class User extends Model {}
363```
364
365> ⚠️ When using paths to add models, keep in mind that they will be loaded during runtime. This means that the path
366> may differ from development time to execution time. For instance, using `.ts` extension within paths will only work
367> together with [ts-node](https://github.com/TypeStrong/ts-node).
368
369### Build and create
370
371Instantiation and inserts can be achieved in the good old sequelize way
372
373```typescript
374const person = Person.build({ name: 'bob', age: 99 })
375person.save()
376
377Person.create({ name: 'bob', age: 99 })
378```
379
380but _sequelize-typescript_ also makes it possible to create instances with `new`:
381
382```typescript
383const person = new Person({ name: 'bob', age: 99 })
384person.save()
385```
386
387### Find and update
388
389Finding and updating entries does also work like using native sequelize. So see sequelize
390[docs](https://docs.sequelizejs.com/manual/tutorial/models-usage.html) for more details.
391
392```typescript
393Person.findOne().then((person) => {
394 person.age = 100
395 return person.save()
396})
397
398Person.update(
399 {
400 name: 'bobby'
401 },
402 { where: { id: 1 } }
403).then(() => {})
404```
405
406## Model association
407
408Relations can be described directly in the model by the `@HasMany`, `@HasOne`, `@BelongsTo`, `@BelongsToMany`
409and `@ForeignKey` annotations.
410
411### One-to-many
412
413```typescript
414@Table
415class Player extends Model {
416 @Column
417 name: string
418
419 @Column
420 num: number
421
422 @ForeignKey(() => Team)
423 @Column
424 teamId: number
425
426 @BelongsTo(() => Team)
427 team: Team
428}
429
430@Table
431class Team extends Model {
432 @Column
433 name: string
434
435 @HasMany(() => Player)
436 players: Player[]
437}
438```
439
440That's all, _sequelize-typescript_ does everything else for you. So when retrieving a team by `find`
441
442```typescript
443Team.findOne({ include: [Player] }).then((team) => {
444 team.players.forEach((player) => console.log(`Player ${player.name}`))
445})
446```
447
448the players will also be resolved (when passing `include: Player` to the find options)
449
450### Many-to-many
451
452```typescript
453@Table
454class Book extends Model {
455 @BelongsToMany(() => Author, () => BookAuthor)
456 authors: Author[]
457}
458
459@Table
460class Author extends Model {
461 @BelongsToMany(() => Book, () => BookAuthor)
462 books: Book[]
463}
464
465@Table
466class BookAuthor extends Model {
467 @ForeignKey(() => Book)
468 @Column
469 bookId: number
470
471 @ForeignKey(() => Author)
472 @Column
473 authorId: number
474}
475```
476
477#### Type safe _through_-table instance access
478
479To access the _through_-table instance (instanceOf `BookAuthor` in the upper example) type safely, the type
480need to be set up manually. For `Author` model it can be achieved like so:
481
482```ts
483 @BelongsToMany(() => Book, () => BookAuthor)
484 books: Array<Book & {BookAuthor: BookAuthor}>;
485```
486
487### One-to-one
488
489For one-to-one use `@HasOne(...)`(foreign key for the relation exists on the other model) and
490`@BelongsTo(...)` (foreign key for the relation exists on this model)
491
492### `@ForeignKey`, `@BelongsTo`, `@HasMany`, `@HasOne`, `@BelongsToMany` API
493
494| Decorator | Description |
495| ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
496| `@ForeignKey(relatedModelGetter: () => typeof Model)` | marks property as `foreignKey` for related class |
497| `@BelongsTo(relatedModelGetter: () => typeof Model)` | sets `SourceModel.belongsTo(RelatedModel, ...)` while `as` is key of annotated property and `foreignKey` is resolved from source class |
498| `@BelongsTo(relatedModelGetter: () => typeof Model, foreignKey: string)` | sets `SourceModel.belongsTo(RelatedModel, ...)` while `as` is key of annotated property and `foreignKey` is explicitly specified value |
499| `@BelongsTo(relatedModelGetter: () => typeof Model, options: AssociationOptionsBelongsTo)` | sets `SourceModel.belongsTo(RelatedModel, ...)` while `as` is key of annotated property and `options` are additional association options |
500| `@HasMany(relatedModelGetter: () => typeof Model)` | sets `SourceModel.hasMany(RelatedModel, ...)` while `as` is key of annotated property and `foreignKey` is resolved from target related class |
501| `@HasMany(relatedModelGetter: () => typeof Model, foreignKey: string)` | sets `SourceModel.hasMany(RelatedModel, ...)` while `as` is key of annotated property and `foreignKey` is explicitly specified value |
502| `@HasMany(relatedModelGetter: () => typeof Model, options: AssociationOptionsHasMany)` | sets `SourceModel.hasMany(RelatedModel, ...)` while `as` is key of annotated property and `options` are additional association options |
503| `@HasOne(relatedModelGetter: () => typeof Model)` | sets `SourceModel.hasOne(RelatedModel, ...)` while `as` is key of annotated property and `foreignKey` is resolved from target related class |
504| `@HasOne(relatedModelGetter: () => typeof Model, foreignKey: string)` | sets `SourceModel.hasOne(RelatedModel, ...)` while `as` is key of annotated property and `foreignKey` is explicitly specified value |
505| `@HasOne(relatedModelGetter: () => typeof Model, options: AssociationOptionsHasOne)` | sets `SourceModel.hasOne(RelatedModel, ...)` while `as` is key of annotated property and `options` are additional association options |
506| `@BelongsToMany(relatedModelGetter: () => typeof Model, through: (() => typeof Model))` | sets `SourceModel.belongsToMany(RelatedModel, {through: ThroughModel, ...})` while `as` is key of annotated property and `foreignKey`/`otherKey` is resolved from through class |
507| `@BelongsToMany(relatedModelGetter: () => typeof Model, through: (() => typeof Model), foreignKey: string)` | sets `SourceModel.belongsToMany(RelatedModel, {through: ThroughModel, ...})` while `as` is key of annotated property, `foreignKey` is explicitly specified value and `otherKey` is resolved from through class |
508| `@BelongsToMany(relatedModelGetter: () => typeof Model, through: (() => typeof Model), foreignKey: string, otherKey: string)` | sets `SourceModel.belongsToMany(RelatedModel, {through: ThroughModel, ...})` while `as` is key of annotated property and `foreignKey`/`otherKey` are explicitly specified values |
509| `@BelongsToMany(relatedModelGetter: () => typeof Model, through: string, foreignKey: string, otherKey: string)` | sets `SourceModel.belongsToMany(RelatedModel, {through: throughString, ...})` while `as` is key of annotated property and `foreignKey`/`otherKey` are explicitly specified values |
510| `@BelongsToMany(relatedModelGetter: () => typeof Model, options: AssociationOptionsBelongsToMany)` | sets `SourceModel.belongsToMany(RelatedModel, {through: throughString, ...})` while `as` is key of annotated property and `options` are additional association values, including `foreignKey` and `otherKey`. |
511
512Note 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, and `through` will be the explicitly stated value.
513
514### Multiple relations of same models
515
516_sequelize-typescript_ resolves the foreign keys by identifying the corresponding class references.
517So if you define a model with multiple relations like
518
519```typescript
520@Table
521class Book extends Model {
522 @ForeignKey(() => Person)
523 @Column
524 authorId: number
525
526 @BelongsTo(() => Person)
527 author: Person
528
529 @ForeignKey(() => Person)
530 @Column
531 proofreaderId: number
532
533 @BelongsTo(() => Person)
534 proofreader: Person
535}
536
537@Table
538class Person extends Model {
539 @HasMany(() => Book)
540 writtenBooks: Book[]
541
542 @HasMany(() => Book)
543 proofedBooks: Book[]
544}
545```
546
547_sequelize-typescript_ cannot know which foreign key to use for which relation. So you have to add the foreign keys
548explicitly:
549
550```typescript
551
552 // in class "Books":
553 @BelongsTo(() => Person, 'authorId')
554 author: Person;
555
556 @BelongsTo(() => Person, 'proofreaderId')
557 proofreader: Person;
558
559 // in class "Person":
560 @HasMany(() => Book, 'authorId')
561 writtenBooks: Book[];
562
563 @HasMany(() => Book, 'proofreaderId')
564 proofedBooks: Book[];
565```
566
567### Type safe usage of auto generated functions
568
569With the creation of a relation, sequelize generates some method on the corresponding
570models. So when you create a 1:n relation between `ModelA` and `ModelB`, an instance of `ModelA` will
571have the functions `getModelBs`, `setModelBs`, `addModelB`, `removeModelB`, `hasModelB`. These functions still exist with _sequelize-typescript_.
572But TypeScript wont recognize them and will complain if you try to access `getModelB`, `setModelB` or
573`addModelB`. To make TypeScript happy, the `Model.prototype` of _sequelize-typescript_ has `$set`, `$get`, `$add`
574functions.
575
576```typescript
577@Table
578class ModelA extends Model {
579 @HasMany(() => ModelB)
580 bs: ModelB[]
581}
582
583@Table
584class ModelB extends Model {
585 @BelongsTo(() => ModelA)
586 a: ModelA
587}
588```
589
590To use them, pass the property key of the respective relation as the first parameter:
591
592```typescript
593const modelA = new ModelA()
594
595modelA
596 .$set('bs', [
597 /* instance */
598 ])
599 .then(/* ... */)
600modelA.$add('b' /* instance */).then(/* ... */)
601modelA.$get('bs').then(/* ... */)
602modelA.$count('bs').then(/* ... */)
603modelA.$has('bs').then(/* ... */)
604modelA.$remove('bs' /* instance */).then(/* ... */)
605modelA.$create('bs' /* value */).then(/* ... */)
606```
607
608## Indexes
609
610### `@Index`
611
612The `@Index` annotation can be used without passing any parameters.
613
614```typescript
615@Table
616class Person extends Model {
617 @Index // Define an index with default name
618 @Column
619 name: string
620
621 @Index // Define another index
622 @Column
623 birthday: Date
624}
625```
626
627To specify index and index field options, use
628an object literal (see [indexes define option](https://sequelize.org/v5/manual/models-definition.html#indexes)):
629
630```typescript
631@Table
632class Person extends Model {
633 @Index('my-index') // Define a multi-field index on name and birthday
634 @Column
635 name: string
636
637 @Index('my-index') // Add birthday as the second field to my-index
638 @Column
639 birthday: Date
640
641 @Index({
642 // index options
643 name: 'job-index',
644 parser: 'my-parser',
645 type: 'UNIQUE',
646 unique: true,
647 where: { isEmployee: true },
648 concurrently: true,
649 using: 'BTREE',
650 operator: 'text_pattern_ops',
651 prefix: 'test-',
652 // index field options
653 length: 10,
654 order: 'ASC',
655 collate: 'NOCASE'
656 })
657 @Column
658 jobTitle: string
659
660 @Column
661 isEmployee: boolean
662}
663```
664
665#### Index API
666
667| Decorator | Description |
668| ---------------------------------------- | --------------------------------------------------------------------------------------------------------- |
669| `@Index` | adds new index on decorated field to `options.indexes` |
670| `@Index(name: string)` | adds new index or adds the field to an existing index with specified name |
671| `@Table(options: IndexDecoratorOptions)` | sets both index and index field [options](https://sequelize.org/v5/manual/models-definition.html#indexes) |
672
673### `createIndexDecorator()`
674
675The `createIndexDecorator()` 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.
676
677```typescript
678const SomeIndex = createIndexDecorator()
679const JobIndex = createIndexDecorator({
680 // index options
681 name: 'job-index',
682 parser: 'my-parser',
683 type: 'UNIQUE',
684 unique: true,
685 where: { isEmployee: true },
686 concurrently: true,
687 using: 'BTREE',
688 operator: 'text_pattern_ops',
689 prefix: 'test-'
690})
691
692@Table
693class Person extends Model {
694 @SomeIndex // Add name to SomeIndex
695 @Column
696 name: string
697
698 @SomeIndex // Add birthday to SomeIndex
699 @Column
700 birthday: Date
701
702 @JobIndex({
703 // index field options
704 length: 10,
705 order: 'ASC',
706 collate: 'NOCASE'
707 })
708 @Column
709 jobTitle: string
710
711 @Column
712 isEmployee: boolean
713}
714```
715
716## Repository mode
717
718With `sequelize-typescript@1` comes a repository mode. See [docs](#repository-mode-1) for details.
719
720The repository mode makes it possible to separate static operations like `find`, `create`, ... from model definitions.
721It also empowers models so that they can be used with multiple sequelize instances.
722
723### How to enable repository mode?
724
725Enable repository mode by setting `repositoryMode` flag:
726
727```typescript
728const sequelize = new Sequelize({
729 repositoryMode: true,
730 ...,
731});
732```
733
734### How to use repository mode?
735
736Retrieve repository to create instances or perform search operations:
737
738```typescript
739const userRepository = sequelize.getRepository(User)
740
741const luke = await userRepository.create({ name: 'Luke Skywalker' })
742const luke = await userRepository.findOne({ where: { name: 'luke' } })
743```
744
745### How to use associations with repository mode?
746
747For now one need to use the repositories within the include options in order to retrieve or create related data:
748
749```typescript
750const userRepository = sequelize.getRepository(User)
751const addressRepository = sequelize.getRepository(Address)
752
753userRepository.find({ include: [addressRepository] })
754userRepository.create({ name: 'Bear' }, { include: [addressRepository] })
755```
756
757> ⚠️ This will change in the future: One will be able to refer the model classes instead of the repositories.
758
759### Limitations of repository mode
760
761Nested scopes and includes in general won't work when using `@Scope` annotation together with repository mode like:
762
763```typescript
764@Scopes(() => ({
765 // includes
766 withAddress: {
767 include: [() => Address]
768 },
769 // nested scopes
770 withAddressIncludingLatLng: {
771 include: [() => Address.scope('withLatLng')]
772 }
773}))
774@Table
775class User extends Model {}
776```
777
778> ⚠️ This will change in the future: Simple includes will be implemented.
779
780## Model validation
781
782Validation options can be set through the `@Column` annotation, but if you prefer to use separate decorators for
783validation instead, you can do so by simply adding the validate options _as_ decorators:
784So that `validate.isEmail=true` becomes `@IsEmail`, `validate.equals='value'` becomes `@Equals('value')`
785and so on. Please notice that a validator that expects a boolean is translated to an annotation without a parameter.
786
787See sequelize [docs](https://sequelize.org/v5/manual/models-definition.html#validations)
788for all validators.
789
790### Exceptions
791
792The following validators cannot simply be translated from sequelize validator to an annotation:
793
794| Validator | Annotation |
795| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
796| `validate.len=[number, number]` | `@Length({max?: number, min?: number})` |
797| `validate[customName: string]` | For custom validators also use the `@Is(...)` annotation: Either `@Is('custom', (value) => { /* ... */})` or with named function `@Is(function custom(value) { /* ... */})` |
798
799### Example
800
801```typescript
802const HEX_REGEX = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
803
804@Table
805export class Shoe extends Model {
806 @IsUUID(4)
807 @PrimaryKey
808 @Column
809 id: string
810
811 @Equals('lala')
812 @Column
813 readonly key: string
814
815 @Contains('Special')
816 @Column
817 special: string
818
819 @Length({ min: 3, max: 15 })
820 @Column
821 brand: string
822
823 @IsUrl
824 @Column
825 brandUrl: string
826
827 @Is('HexColor', (value) => {
828 if (!HEX_REGEX.test(value)) {
829 throw new Error(`"${value}" is not a hex color value.`)
830 }
831 })
832 @Column
833 primaryColor: string
834
835 @Is(function hexColor(value: string): void {
836 if (!HEX_REGEX.test(value)) {
837 throw new Error(`"${value}" is not a hex color value.`)
838 }
839 })
840 @Column
841 secondaryColor: string
842
843 @Is(HEX_REGEX)
844 @Column
845 tertiaryColor: string
846
847 @IsDate
848 @IsBefore('2017-02-27')
849 @Column
850 producedAt: Date
851}
852```
853
854## Scopes
855
856Scopes can be defined with annotations as well. The scope options are identical to native
857sequelize (See sequelize [docs](https://docs.sequelizejs.com/manual/tutorial/scopes.html) for more details)
858
859### `@DefaultScope` and `@Scopes`
860
861```typescript
862@DefaultScope(() => ({
863 attributes: ['id', 'primaryColor', 'secondaryColor', 'producedAt']
864}))
865@Scopes(() => ({
866 full: {
867 include: [Manufacturer]
868 },
869 yellow: {
870 where: { primaryColor: 'yellow' }
871 }
872}))
873@Table
874export class ShoeWithScopes extends Model {
875 @Column
876 readonly secretKey: string
877
878 @Column
879 primaryColor: string
880
881 @Column
882 secondaryColor: string
883
884 @Column
885 producedAt: Date
886
887 @ForeignKey(() => Manufacturer)
888 @Column
889 manufacturerId: number
890
891 @BelongsTo(() => Manufacturer)
892 manufacturer: Manufacturer
893}
894```
895
896## Hooks
897
898Hooks can be attached to your models. All Model-level hooks are supported. See [the related unit tests](test/models/Hook.ts) for a summary.
899
900Each hook must be a `static` method. Multiple hooks can be attached to a single method, and you can define multiple methods for a given hook.
901
902The name of the method cannot be the same as the name of the hook (for example, a `@BeforeCreate` hook method cannot be named `beforeCreate`). That’s because Sequelize has pre-defined methods with those names.
903
904```typescript
905@Table
906export class Person extends Model {
907 @Column
908 name: string
909
910 @BeforeUpdate
911 @BeforeCreate
912 static makeUpperCase(instance: Person) {
913 // this will be called when an instance is created or updated
914 instance.name = instance.name.toLocaleUpperCase()
915 }
916
917 @BeforeCreate
918 static addUnicorn(instance: Person) {
919 // this will also be called when an instance is created
920 instance.name += ' 🦄'
921 }
922}
923```
924
925## Why `() => Model`?
926
927`@ForeignKey(Model)` is much easier to read, so why is `@ForeignKey(() => Model)` so important? When it
928comes to circular-dependencies (which are in general solved by node for you) `Model` can be `undefined`
929when it gets passed to @ForeignKey. With the usage of a function, which returns the actual model, we prevent
930this issue.
931
932## Recommendations and limitations
933
934### One Sequelize instance per model (without repository mode)
935
936Unless you are using the [repository mode](#repository-mode-1), you won't be able to add one and the same model to multiple
937Sequelize instances with differently configured connections. So that one model will only work for one connection.
938
939### One model class per file
940
941This is not only good practice regarding design, but also matters for the order
942of execution. Since Typescript creates a `__metadata("design:type", SomeModel)` call due to `emitDecoratorMetadata`
943compile option, in some cases `SomeModel` is probably not defined(not undefined!) and would throw a `ReferenceError`.
944When putting `SomeModel` in a separate file, it would look like `__metadata("design:type", SomeModel_1.SomeModel)`,
945which does not throw an error.
946
947### Minification
948
949If you need to minify your code, you need to set `tableName` and `modelName`
950in the `DefineOptions` for `@Table` annotation. sequelize-typescript
951uses the class name as default name for `tableName` and `modelName`.
952When the code is minified the class name will no longer be the originally
953defined one (So that `class User` will become `class b` for example).
954
955## Contributing
956
957To contribute you can:
958
959- Open issues and participate in discussion of other issues.
960- Fork the project to open up PR's.
961- Update the [types of Sequelize](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/sequelize).
962- Anything else constructively helpful.
963
964In order to open a pull request please:
965
966- Create a new branch.
967- Run tests locally (`npm install && npm run build && npm run cover`) and ensure your commits don't break the tests.
968- Document your work well with commit messages, a good PR description, comments in code when necessary, etc.
969
970In order to update the types for sequelize please go to [the Definitely Typed repo](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/sequelize), it would also be a good
971idea to open a PR into [sequelize](https://github.com/sequelize/sequelize) so that Sequelize can maintain its own types, but that
972might be harder than getting updated types into microsoft's repo. The Typescript team is slowly trying to encourage
973npm package maintainers to maintain their own typings, but Microsoft still has dedicated and good people maintaining the DT repo,
974accepting PR's and keeping quality high.
975
976**Keep in mind `sequelize-typescript` does not provide typings for `sequelize`** - these are seperate things.
977A lot of the types in `sequelize-typescript` augment, refer to, or extend what sequelize already has.