UNPKG

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