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