1 | # sequelize-typescript
|
2 |
|
3 | [](https://github.com/sequelize/sequelize-typescript/actions?query=workflow%3A%22Node.js+CI%22)
|
4 | [](https://codecov.io/gh/sequelize/sequelize-typescript)
|
5 | [](https://www.npmjs.com/package/sequelize-typescript)
|
6 |
|
7 | Decorators 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
|
45 | npm install --save-dev @types/node @types/validator
|
46 | npm install sequelize reflect-metadata sequelize-typescript
|
47 | ```
|
48 |
|
49 | Your `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 |
|
64 | The `@Scopes` and `@DefaultScope` decorators now take lambda's as options
|
65 |
|
66 | ```ts
|
67 | @DefaultScope(() => ({...}))
|
68 | @Scopes(() => ({...}))
|
69 | ```
|
70 |
|
71 | instead of deprecated way:
|
72 |
|
73 | ```ts
|
74 | @DefaultScope({...})
|
75 | @Scopes({...}))
|
76 | ```
|
77 |
|
78 | ## Model definition
|
79 |
|
80 | ```typescript
|
81 | import { Table, Column, Model, HasMany } from 'sequelize-typescript';
|
82 |
|
83 | @Table
|
84 | class 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
|
99 | import { Table, Model } from 'sequelize-typescript';
|
100 |
|
101 | @Table
|
102 | class Person extends Model {}
|
103 | ```
|
104 |
|
105 | ### More strict
|
106 |
|
107 | ```typescript
|
108 | import { Optional } from 'sequelize';
|
109 | import { Table, Model } from 'sequelize-typescript';
|
110 |
|
111 | interface PersonAttributes {
|
112 | id: number;
|
113 | name: string;
|
114 | }
|
115 |
|
116 | interface PersonCreationAttributes extends Optional<PersonAttributes, 'id'> {}
|
117 |
|
118 | @Table
|
119 | class Person extends Model<PersonAttributes, PersonCreationAttributes> {}
|
120 | ```
|
121 |
|
122 | The model needs to extend the `Model` class and has to be annotated with the `@Table` decorator. All properties that
|
123 | should appear as a column in the database require the `@Column` annotation.
|
124 |
|
125 | See more advanced example [here](https://github.com/RobinBuschmann/sequelize-typescript-example).
|
126 |
|
127 | ### `@Table`
|
128 |
|
129 | The `@Table` annotation can be used without passing any parameters. To specify some more define options, use
|
130 | an object literal (all [define options](https://sequelize.org/v5/manual/models-definition.html#configuration)
|
131 | from sequelize are valid):
|
132 |
|
133 | ```typescript
|
134 | @Table({
|
135 | timestamps: true,
|
136 | ...
|
137 | })
|
138 | class 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 |
|
150 | A 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
|
152 | attribute as primary key. So either set `@Column({primaryKey: true})` or use `@PrimaryKey` together with `@Column`.
|
153 |
|
154 | #### `@CreatedAt`, `@UpdatedAt`, `@DeletedAt`
|
155 |
|
156 | Annotations 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 |
|
177 | The `@Column` annotation can be used without passing any parameters. But therefore it is necessary that
|
178 | the js type can be inferred automatically (see [Type inference](#type-inference) for details).
|
179 |
|
180 | ```typescript
|
181 | @Column
|
182 | name: string;
|
183 | ```
|
184 |
|
185 | If the type cannot or should not be inferred, use:
|
186 |
|
187 | ```typescript
|
188 | import {DataType} from 'sequelize-typescript';
|
189 |
|
190 | @Column(DataType.TEXT)
|
191 | name: string;
|
192 | ```
|
193 |
|
194 | Or for a more detailed column description, use an object literal
|
195 | (all [attribute options](https://sequelize.org/v5/manual/models-definition.html#configuration)
|
196 | from 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 |
|
217 | If you're in love with decorators: _sequelize-typescript_ provides some more of them. The following decorators can be
|
218 | used 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 |
|
232 | The 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 |
|
245 | Get/set accessors do work as well
|
246 |
|
247 | ```typescript
|
248 | @Table
|
249 | class 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 |
|
263 | Except 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 |
|
268 | To make the defined models available, you have to configure a `Sequelize` instance from `sequelize-typescript`(!).
|
269 |
|
270 | ```typescript
|
271 | import { Sequelize } from 'sequelize-typescript';
|
272 |
|
273 | const 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 |
|
283 | Before you can use your models you have to tell sequelize where they can be found. So either set `models` in the
|
284 | sequelize config or add the required models later on by calling `sequelize.addModels([Person])` or
|
285 | `sequelize.addModels([__dirname + '/models'])`:
|
286 |
|
287 | ```typescript
|
288 | sequelize.addModels([Person]);
|
289 | sequelize.addModels(['path/to/models']);
|
290 | ```
|
291 |
|
292 | ### globs
|
293 |
|
294 | ```typescript
|
295 | import {Sequelize} from 'sequelize-typescript';
|
296 |
|
297 | const sequelize = new Sequelize({
|
298 | ...
|
299 | models: [__dirname + '/**/*.model.ts']
|
300 | });
|
301 | // or
|
302 | sequelize.addModels([__dirname + '/**/*.model.ts']);
|
303 | ```
|
304 |
|
305 | #### Model-path resolving
|
306 |
|
307 | A model is matched to a file by its filename. E.g.
|
308 |
|
309 | ```typescript
|
310 | // File User.ts matches the following exported model.
|
311 | export class User extends Model {}
|
312 | ```
|
313 |
|
314 | This is done by comparison of the filename against all exported members. The
|
315 | matching can be customized by specifying the `modelMatch` function in the
|
316 | configuration object.
|
317 |
|
318 | For 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
|
322 | import {Sequelize} from 'sequelize-typescript';
|
323 |
|
324 | const 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 |
|
332 | For each file that matches the `*.model.ts` pattern, the `modelMatch` function
|
333 | will be called with its exported members. E.g. for the following file
|
334 |
|
335 | ```TypeScript
|
336 | //user.model.ts
|
337 | import {Table, Column, Model} from 'sequelize-typescript';
|
338 |
|
339 | export const UserN = 'Not a model';
|
340 | export const NUser = 'Not a model';
|
341 |
|
342 | @Table
|
343 | export class User extends Model {
|
344 |
|
345 | @Column
|
346 | nickname: string;
|
347 | }
|
348 | ```
|
349 |
|
350 | The `modelMatch` function will be called three times with the following arguments.
|
351 |
|
352 | ```text
|
353 | user.model UserN -> false
|
354 | user.model NUser -> false
|
355 | user.model User -> true (User will be added as model)
|
356 | ```
|
357 |
|
358 | Another way to match model to file is to make your model the default export.
|
359 |
|
360 | ```TypeScript
|
361 | export 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 |
|
370 | Instantiation and inserts can be achieved in the good old sequelize way
|
371 |
|
372 | ```typescript
|
373 | const person = Person.build({ name: 'bob', age: 99 });
|
374 | person.save();
|
375 |
|
376 | Person.create({ name: 'bob', age: 99 });
|
377 | ```
|
378 |
|
379 | but _sequelize-typescript_ also makes it possible to create instances with `new`:
|
380 |
|
381 | ```typescript
|
382 | const person = new Person({ name: 'bob', age: 99 });
|
383 | person.save();
|
384 | ```
|
385 |
|
386 | ### Find and update
|
387 |
|
388 | Finding 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
|
392 | Person.findOne().then((person) => {
|
393 | person.age = 100;
|
394 | return person.save();
|
395 | });
|
396 |
|
397 | Person.update(
|
398 | {
|
399 | name: 'bobby',
|
400 | },
|
401 | { where: { id: 1 } }
|
402 | ).then(() => {});
|
403 | ```
|
404 |
|
405 | ## Model association
|
406 |
|
407 | Relations can be described directly in the model by the `@HasMany`, `@HasOne`, `@BelongsTo`, `@BelongsToMany`
|
408 | and `@ForeignKey` annotations.
|
409 |
|
410 | ### One-to-many
|
411 |
|
412 | ```typescript
|
413 | @Table
|
414 | class 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
|
430 | class Team extends Model {
|
431 | @Column
|
432 | name: string;
|
433 |
|
434 | @HasMany(() => Player)
|
435 | players: Player[];
|
436 | }
|
437 | ```
|
438 |
|
439 | That's all, _sequelize-typescript_ does everything else for you. So when retrieving a team by `find`
|
440 |
|
441 | ```typescript
|
442 | Team.findOne({ include: [Player] }).then((team) => {
|
443 | team.players.forEach((player) => console.log(`Player ${player.name}`));
|
444 | });
|
445 | ```
|
446 |
|
447 | the players will also be resolved (when passing `include: Player` to the find options)
|
448 |
|
449 | ### Many-to-many
|
450 |
|
451 | ```typescript
|
452 | @Table
|
453 | class Book extends Model {
|
454 | @BelongsToMany(() => Author, () => BookAuthor)
|
455 | authors: Author[];
|
456 | }
|
457 |
|
458 | @Table
|
459 | class Author extends Model {
|
460 | @BelongsToMany(() => Book, () => BookAuthor)
|
461 | books: Book[];
|
462 | }
|
463 |
|
464 | @Table
|
465 | class 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 |
|
478 | To access the _through_-table instance (instanceOf `BookAuthor` in the upper example) type safely, the type
|
479 | need 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 |
|
488 | For 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 |
|
511 | 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, 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.
|
516 | So if you define a model with multiple relations like
|
517 |
|
518 | ```typescript
|
519 | @Table
|
520 | class 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
|
537 | class 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
|
547 | explicitly:
|
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 |
|
568 | With the creation of a relation, sequelize generates some method on the corresponding
|
569 | models. So when you create a 1:n relation between `ModelA` and `ModelB`, an instance of `ModelA` will
|
570 | have the functions `getModelBs`, `setModelBs`, `addModelB`, `removeModelB`, `hasModelB`. These functions still exist with _sequelize-typescript_.
|
571 | But 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`
|
573 | functions.
|
574 |
|
575 | ```typescript
|
576 | @Table
|
577 | class ModelA extends Model {
|
578 | @HasMany(() => ModelB)
|
579 | bs: ModelB[];
|
580 | }
|
581 |
|
582 | @Table
|
583 | class ModelB extends Model {
|
584 | @BelongsTo(() => ModelA)
|
585 | a: ModelA;
|
586 | }
|
587 | ```
|
588 |
|
589 | To use them, pass the property key of the respective relation as the first parameter:
|
590 |
|
591 | ```typescript
|
592 | const modelA = new ModelA();
|
593 |
|
594 | modelA
|
595 | .$set('bs', [
|
596 | /* instance */
|
597 | ])
|
598 | .then(/* ... */);
|
599 | modelA.$add('b' /* instance */).then(/* ... */);
|
600 | modelA.$get('bs').then(/* ... */);
|
601 | modelA.$count('bs').then(/* ... */);
|
602 | modelA.$has('bs').then(/* ... */);
|
603 | modelA.$remove('bs' /* instance */).then(/* ... */);
|
604 | modelA.$create('bs' /* value */).then(/* ... */);
|
605 | ```
|
606 |
|
607 | ## Indexes
|
608 |
|
609 | ### `@Index`
|
610 |
|
611 | The `@Index` annotation can be used without passing any parameters.
|
612 |
|
613 | ```typescript
|
614 | @Table
|
615 | class 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 |
|
626 | To specify index and index field options, use
|
627 | an object literal (see [indexes define option](https://sequelize.org/v5/manual/models-definition.html#indexes)):
|
628 |
|
629 | ```typescript
|
630 | @Table
|
631 | class 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 |
|
674 | The `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
|
677 | const SomeIndex = createIndexDecorator();
|
678 | const 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
|
692 | class 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 |
|
717 | With `sequelize-typescript@1` comes a repository mode. See [docs](#repository-mode) for details.
|
718 |
|
719 | The repository mode makes it possible to separate static operations like `find`, `create`, ... from model definitions.
|
720 | It also empowers models so that they can be used with multiple sequelize instances.
|
721 |
|
722 | ### How to enable repository mode?
|
723 |
|
724 | Enable repository mode by setting `repositoryMode` flag:
|
725 |
|
726 | ```typescript
|
727 | const sequelize = new Sequelize({
|
728 | repositoryMode: true,
|
729 | ...,
|
730 | });
|
731 | ```
|
732 |
|
733 | ### How to use repository mode?
|
734 |
|
735 | Retrieve repository to create instances or perform search operations:
|
736 |
|
737 | ```typescript
|
738 | const userRepository = sequelize.getRepository(User);
|
739 |
|
740 | const luke = await userRepository.create({ name: 'Luke Skywalker' });
|
741 | const luke = await userRepository.findOne({ where: { name: 'luke' } });
|
742 | ```
|
743 |
|
744 | ### How to use associations with repository mode?
|
745 |
|
746 | For now one need to use the repositories within the include options in order to retrieve or create related data:
|
747 |
|
748 | ```typescript
|
749 | const userRepository = sequelize.getRepository(User);
|
750 | const addressRepository = sequelize.getRepository(Address);
|
751 |
|
752 | userRepository.find({ include: [addressRepository] });
|
753 | userRepository.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 |
|
760 | Nested 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
|
774 | class User extends Model {}
|
775 | ```
|
776 |
|
777 | > ⚠️ This will change in the future: Simple includes will be implemented.
|
778 |
|
779 | ## Model validation
|
780 |
|
781 | Validation options can be set through the `@Column` annotation, but if you prefer to use separate decorators for
|
782 | validation instead, you can do so by simply adding the validate options _as_ decorators:
|
783 | So that `validate.isEmail=true` becomes `@IsEmail`, `validate.equals='value'` becomes `@Equals('value')`
|
784 | and so on. Please notice that a validator that expects a boolean is translated to an annotation without a parameter.
|
785 |
|
786 | See sequelize [docs](https://sequelize.org/v5/manual/models-definition.html#validations)
|
787 | for all validators.
|
788 |
|
789 | ### Exceptions
|
790 |
|
791 | The 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
|
801 | const HEX_REGEX = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
802 |
|
803 | @Table
|
804 | export 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 |
|
855 | Scopes can be defined with annotations as well. The scope options are identical to native
|
856 | sequelize (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
|
873 | export 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 |
|
897 | Hooks 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 |
|
899 | Each 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 |
|
901 | The 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
|
905 | export 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
|
927 | comes to circular-dependencies (which are in general solved by node for you) `Model` can be `undefined`
|
928 | when it gets passed to @ForeignKey. With the usage of a function, which returns the actual model, we prevent
|
929 | this issue.
|
930 |
|
931 | ## Recommendations and limitations
|
932 |
|
933 | ### One Sequelize instance per model (without repository mode)
|
934 |
|
935 | Unless you are using the [repository mode](#repository-mode), you won't be able to add one and the same model to multiple
|
936 | Sequelize instances with differently configured connections. So that one model will only work for one connection.
|
937 |
|
938 | ### One model class per file
|
939 |
|
940 | This is not only good practice regarding design, but also matters for the order
|
941 | of execution. Since Typescript creates a `__metadata("design:type", SomeModel)` call due to `emitDecoratorMetadata`
|
942 | compile option, in some cases `SomeModel` is probably not defined(not undefined!) and would throw a `ReferenceError`.
|
943 | When putting `SomeModel` in a separate file, it would look like `__metadata("design:type", SomeModel_1.SomeModel)`,
|
944 | which does not throw an error.
|
945 |
|
946 | ### Minification
|
947 |
|
948 | If you need to minify your code, you need to set `tableName` and `modelName`
|
949 | in the `DefineOptions` for `@Table` annotation. sequelize-typescript
|
950 | uses the class name as default name for `tableName` and `modelName`.
|
951 | When the code is minified the class name will no longer be the originally
|
952 | defined one (So that `class User` will become `class b` for example).
|
953 |
|
954 | ## Contributing
|
955 |
|
956 | To 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 |
|
963 | In 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 |
|
969 | In 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
|
970 | idea to open a PR into [sequelize](https://github.com/sequelize/sequelize) so that Sequelize can maintain its own types, but that
|
971 | might be harder than getting updated types into microsoft's repo. The Typescript team is slowly trying to encourage
|
972 | npm package maintainers to maintain their own typings, but Microsoft still has dedicated and good people maintaining the DT repo,
|
973 | accepting PR's and keeping quality high.
|
974 |
|
975 | **Keep in mind `sequelize-typescript` does not provide typings for `sequelize`** - these are seperate things.
|
976 | A lot of the types in `sequelize-typescript` augment, refer to, or extend what sequelize already has.
|