diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 4cff33d2..fa76b142 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -13,37 +13,38 @@ module.exports = { '@ephys/eslint-config-typescript/commonjs', 'plugin:mdx/recommended', ], - rules: { - // there are not supported enough in recent browsers to justify enforcing their usage - 'prefer-object-has-own': 'off', - 'unicorn/prefer-at': 'off', - 'unicorn/prefer-string-replace-all': 'off', - - 'unicorn/prefer-spread': 'off', - 'unicorn/no-useless-undefined': 'off', - }, - overrides: [{ - files: ['*.mdx/**', '*.md/**'], - rules: { - // these rules require proper type-checking and cannot be enabled on code snippets inside markdown files - '@typescript-eslint/dot-notation': 'off', - '@typescript-eslint/return-await': 'off', - '@typescript-eslint/no-throw-literal': 'off', - '@typescript-eslint/no-implied-eval': 'off', - '@typescript-eslint/no-floating-promises': 'off', - '@typescript-eslint/no-misused-promises': 'off', - '@typescript-eslint/no-unnecessary-type-assertion': 'off', - '@typescript-eslint/non-nullable-type-assertion-style': 'off', - '@typescript-eslint/prefer-includes': 'off', - '@typescript-eslint/prefer-readonly': 'off', - '@typescript-eslint/prefer-string-starts-ends-with': 'off', - '@typescript-eslint/restrict-plus-operands': 'off', - '@typescript-eslint/require-array-sort-compare': 'off', - '@typescript-eslint/promise-function-async': 'off', - '@typescript-eslint/consistent-type-exports': 'off', - '@typescript-eslint/prefer-return-this-type': 'off', + overrides: [ + { + files: ['*.mdx/**', '*.md/**'], + rules: { + // these rules require proper type-checking and cannot be enabled on code snippets inside markdown files + '@typescript-eslint/dot-notation': 'off', + '@typescript-eslint/return-await': 'off', + '@typescript-eslint/no-throw-literal': 'off', + '@typescript-eslint/no-implied-eval': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/non-nullable-type-assertion-style': 'off', + '@typescript-eslint/prefer-includes': 'off', + '@typescript-eslint/prefer-readonly': 'off', + '@typescript-eslint/prefer-string-starts-ends-with': 'off', + '@typescript-eslint/restrict-plus-operands': 'off', + '@typescript-eslint/require-array-sort-compare': 'off', + '@typescript-eslint/promise-function-async': 'off', + '@typescript-eslint/consistent-type-exports': 'off', + '@typescript-eslint/prefer-return-this-type': 'off', + }, }, - }], + { + files: ['*.d.ts', 'src/pages/*', 'src/theme/**/*'], + rules: { + // in .d.ts files, we don't really have a choice as it's dictated by the file that's being typed. + // For pages and theme files, docusaurus imposes the default export + 'import/no-default-export': 'off', + }, + }, + ], ignorePatterns: [ // archives 'static/v1', diff --git a/.github/ISSUE_TEMPLATE/documentation_issue.md b/.github/ISSUE_TEMPLATE/documentation_issue.md index f5328c7d..2136fffc 100644 --- a/.github/ISSUE_TEMPLATE/documentation_issue.md +++ b/.github/ISSUE_TEMPLATE/documentation_issue.md @@ -1,9 +1,9 @@ --- name: 📗 Documentation Issue about: Documentation is unclear, or otherwise insufficient/misleading, examples aren't working as expected -title: "" -labels: "" -assignees: "" +title: '' +labels: '' +assignees: '' --- ## Issue Description @@ -14,12 +14,8 @@ Try to be as clear as possible. Don't assume that the maintainers will immediate ### What was unclear/insufficient/not covered in the documentation - - ### If possible: Provide some suggestion on how we can enhance the docs - - ### Additional context <-- Add any other context or screenshots about the issue here. --> diff --git a/.github/workflows/draft.yml b/.github/workflows/draft.yml index f073abce..239a5732 100644 --- a/.github/workflows/draft.yml +++ b/.github/workflows/draft.yml @@ -20,9 +20,8 @@ jobs: node-version: 20.x cache: yarn - run: yarn install --frozen-lockfile - - run: yarn lint-no-fix - - run: yarn lint-scss-no-fix - - run: yarn typecheck + - run: yarn test:format + - run: yarn test:typings - run: yarn sync - run: yarn build - name: Deploy to Draft to Netlify diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1d733d24..b6b5c2ff 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,8 +18,8 @@ jobs: node-version: 20.x cache: yarn - run: yarn install --frozen-lockfile - - run: yarn lint-no-fix - - run: yarn typecheck + - run: yarn test:format + - run: yarn test:typings - run: yarn sync - run: yarn build - name: Prepare Netlify dependencies diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..d3d65e7f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +.yarn +static/v5 +static/v4 +static/v3 +static/v2 +static/v1 diff --git a/.prettierrc.json b/.prettierrc.json index 544138be..f0053d02 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,3 +1,6 @@ { - "singleQuote": true + "bracketSameLine": true, + "printWidth": 100, + "singleQuote": true, + "arrowParens": "avoid" } diff --git a/docs/_fragments/_uuid-support-table.mdx b/docs/_fragments/_uuid-support-table.mdx index 809b31ff..1f62d484 100644 --- a/docs/_fragments/_uuid-support-table.mdx +++ b/docs/_fragments/_uuid-support-table.mdx @@ -3,8 +3,8 @@ import { DialectTableFilter } from '@site/src/components/dialect-table-filter.ts | | PostgreSQL | MariaDB | MySQL | MSSQL | SQLite | Snowflake | db2 | ibmi | -|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------|----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------|--------|-----------|-----|------| +| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------ | --------- | --- | ---- | | `uuidV1` | [`uuid_generate_v1`](https://www.postgresql.org/docs/current/uuid-ossp.html) (requires `uuid-ossp`) | [`UUID`](https://mariadb.com/kb/en/uuid/) | [`UUID`](https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_uuid) | N/A | N/A | N/A | N/A | N/A | -| `uuidV4` | __pg >= v13__: [`gen_random_uuid`](https://www.postgresql.org/docs/current/functions-uuid.html)
__pg < v13__: [`uuid_generate_v4`](https://www.postgresql.org/docs/current/uuid-ossp.html) (requires `uuid-ossp`) | N/A | N/A | [`NEWID`](https://learn.microsoft.com/en-us/sql/t-sql/functions/newid-transact-sql?view=sql-server-ver16) | N/A | N/A | N/A | N/A | +| `uuidV4` | **pg >= v13**: [`gen_random_uuid`](https://www.postgresql.org/docs/current/functions-uuid.html)
**pg < v13**: [`uuid_generate_v4`](https://www.postgresql.org/docs/current/uuid-ossp.html) (requires `uuid-ossp`) | N/A | N/A | [`NEWID`](https://learn.microsoft.com/en-us/sql/t-sql/functions/newid-transact-sql?view=sql-server-ver16) | N/A | N/A | N/A | N/A |
diff --git a/docs/associations/association-scopes.md b/docs/associations/association-scopes.md index 9e28790e..0ba25439 100644 --- a/docs/associations/association-scopes.md +++ b/docs/associations/association-scopes.md @@ -52,7 +52,7 @@ SELECT * FROM `restaurants` WHERE `restaurants`.`status` = 'open' AND `restauran ## BelongsToMany scope -All associations support specifying a scope to filter the target model, but the `BelongsToMany` association +All associations support specifying a scope to filter the target model, but the `BelongsToMany` association also supports specifying a scope to filter the join table. This is useful when you want to filter based on extra information stored in the join table. @@ -71,7 +71,7 @@ class Person extends Model {} class Game extends Model { /** This association will list everyone that worked on the game */ @BelongsToMany(() => Person, { - through: GameAuthor + through: GameAuthor, }) allAuthors; } @@ -89,10 +89,10 @@ class Game extends Model { otherKey: 'personId', }) allAuthors; - + /** This association will list everyone that worked on the game as a programmer */ @BelongsToMany(() => Person, { - through: { + through: { model: GameAuthor, foreignKey: 'gameId', otherKey: 'personId', @@ -101,7 +101,7 @@ class Game extends Model { }, }) programmers; - + /** This association will list everyone that worked on the game as a designer */ @BelongsToMany(() => Person, { through: { diff --git a/docs/associations/basics.md b/docs/associations/basics.md index aee3872e..ba707707 100644 --- a/docs/associations/basics.md +++ b/docs/associations/basics.md @@ -5,17 +5,17 @@ title: Basics # Association Basics -Sequelize provides what are called __associations__. -These can be declared on your models to define common [__relationships__](https://en.wikipedia.org/wiki/Cardinality_(data_modeling)) between your tables. +Sequelize provides what are called **associations**. +These can be declared on your models to define common [**relationships**]() between your tables. -The two concepts are closely related, but not the same. __Associations__ are defined in JavaScript between your _models_, while -__relationships__ are defined in your database between your _tables_. +The two concepts are closely related, but not the same. **Associations** are defined in JavaScript between your _models_, while +**relationships** are defined in your database between your _tables_. Sequelize supports the standard associations: [One-To-One](https://en.wikipedia.org/wiki/One-to-one_%28data_model%29), [One-To-Many](https://en.wikipedia.org/wiki/One-to-many_%28data_model%29) and [Many-To-Many](https://en.wikipedia.org/wiki/Many-to-many_%28data_model%29). ## One-to-one Relationships -In a One-To-One relationship, a row of one table is associated with a single row of another table. +In a One-To-One relationship, a row of one table is associated with a single row of another table. The most common type of One-To-One relationship is one where one side is mandatory, and the other side is optional. For instance, a driving license always belongs to a single person, but a person can have zero or one driving licenses (from the same place). @@ -25,7 +25,7 @@ erDiagram people ||--o| driving_licenses : drivingLicense ``` -One-To-One relationships can be created by using __the [`HasOne`](./has-one.md) association__. +One-To-One relationships can be created by using **the [`HasOne`](./has-one.md) association**. ## One-to-many Relationships @@ -38,7 +38,7 @@ erDiagram people ||--o{ cities : birthplace ``` -One-To-Many relationships can be created by using __the [`HasMany`](./has-many.md) association__. +One-To-Many relationships can be created by using **the [`HasMany`](./has-many.md) association**. ## Many-to-many Relationships @@ -51,4 +51,4 @@ erDiagram people }o--o{ toots : likedToots ``` -Many-To-Many relationships can be created by using __the [`BelongsToMany`](./belongs-to-many.md) association__. +Many-To-Many relationships can be created by using **the [`BelongsToMany`](./belongs-to-many.md) association**. diff --git a/docs/associations/belongs-to-many.md b/docs/associations/belongs-to-many.md index 68f5084a..1920381e 100644 --- a/docs/associations/belongs-to-many.md +++ b/docs/associations/belongs-to-many.md @@ -5,7 +5,7 @@ title: BelongsToMany # The BelongsToMany Association -The BelongsToMany association is used to create a [Many-To-Many relationship](https://en.wikipedia.org/wiki/Many-to-many_(data_model)) between two models. +The BelongsToMany association is used to create a [Many-To-Many relationship]() between two models. In a Many-To-Many relationship, a row of one table is associated with _zero, one or more_ rows of another table, and vice versa. @@ -16,7 +16,7 @@ erDiagram people }o--o{ toots : likedToots ``` -Because foreign keys can only point to a single row, Many-To-Many relationships are implemented using a junction table (called __through table__ in Sequelize), and are +Because foreign keys can only point to a single row, Many-To-Many relationships are implemented using a junction table (called **through table** in Sequelize), and are really just two One-To-Many relationships. ```mermaid @@ -54,7 +54,7 @@ and will receive the two foreign keys: `userId` and `tootId`. :::caution String `through` option -The `through` option is used to specify the through __model__, not the through __table__. +The `through` option is used to specify the through **model**, not the through **table**. We recommend that you follow the same naming conventions as other models (i.e. PascalCase & singular): ```ts @@ -72,11 +72,17 @@ class Person extends Model, InferCreationAttributes, InferCreationAttributes, InferCreationAttributes, InferCreationAttributes, InferCreationAttributes`: Associates multiple new models. ```ts -import { BelongsToManyAddAssociationMixin, BelongsToManyAddAssociationsMixin } from '@sequelize/core'; +import { + BelongsToManyAddAssociationMixin, + BelongsToManyAddAssociationsMixin, +} from '@sequelize/core'; class Author extends Model, InferCreationAttributes> { @BelongsToMany(() => Book, { through: 'BookAuthor' }) @@ -386,9 +395,11 @@ There are two versions of this method: - `remove`: Removes a single associated model. - `remove`: Removes multiple associated models. - ```ts -import { BelongsToManyRemoveAssociationMixin, BelongsToManyRemoveAssociationsMixin } from '@sequelize/core'; +import { + BelongsToManyRemoveAssociationMixin, + BelongsToManyRemoveAssociationsMixin, +} from '@sequelize/core'; class Author extends Model, InferCreationAttributes> { @BelongsToMany(() => Book, { through: 'BookAuthor' }) @@ -462,8 +473,7 @@ In the example above, we did not need to specify the `postId` attribute. This is If you use TypeScript, you need to let TypeScript know that the foreign key is not required. You can do so using the second generic argument of the `BelongsToManyCreateAssociationMixin` type. ```ts -BelongsToManyCreateAssociationMixin - ^ Here +BelongsToManyCreateAssociationMixin ^ Here; ``` ::: @@ -476,7 +486,10 @@ The association checker is used to check if a model is associated with another m - `has`: Checks whether all the specified models are associated. ```ts -import { BelongsToManyHasAssociationMixin, BelongsToManyHasAssociationsMixin } from '@sequelize/core'; +import { + BelongsToManyHasAssociationMixin, + BelongsToManyHasAssociationsMixin, +} from '@sequelize/core'; class Author extends Model, InferCreationAttributes> { @BelongsToMany(() => Book, { through: 'BookAuthor' }) diff --git a/docs/associations/belongs-to.md b/docs/associations/belongs-to.md index 2f294df7..effeeb6e 100644 --- a/docs/associations/belongs-to.md +++ b/docs/associations/belongs-to.md @@ -15,12 +15,25 @@ We recommend reading the guides on [`HasOne`](./has-one.md) and [`HasMany`](./ha The `BelongsTo` association is used on the opposite side of where you would use a `HasOne` or `HasMany` association. It is capable of creating both One-To-One and One-To-Many relationships. -For instance, here is how you would create the association we described in the [`HasMany`](./has-many.md) guide, +For instance, here is how you would create the association we described in the [`HasMany`](./has-many.md) guide, using a `BelongsTo` association: ```ts -import { Model, DataTypes, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute } from '@sequelize/core'; -import { PrimaryKey, Attribute, AutoIncrement, NotNull, BelongsTo } from '@sequelize/core/decorators-legacy'; +import { + Model, + DataTypes, + InferAttributes, + InferCreationAttributes, + CreationOptional, + NonAttribute, +} from '@sequelize/core'; +import { + PrimaryKey, + Attribute, + AutoIncrement, + NotNull, + BelongsTo, +} from '@sequelize/core/decorators-legacy'; class Post extends Model, InferCreationAttributes> { @Attribute(DataTypes.INTEGER) @@ -50,8 +63,22 @@ class Comment extends Model, InferCreationAttributes, InferCreationAttributes> { @Attribute(DataTypes.INTEGER) @@ -60,12 +87,15 @@ class Person extends Model, InferCreationAttributes; } -class DrivingLicense extends Model, InferCreationAttributes> { +class DrivingLicense extends Model< + InferAttributes, + InferCreationAttributes +> { @Attribute(DataTypes.INTEGER) @AutoIncrement @PrimaryKey declare id: CreationOptional; - + // highlight-start @BelongsTo(() => Person, /* foreign key */ 'ownerId') declare owner?: NonAttribute; @@ -91,7 +121,7 @@ class Post extends Model, InferCreationAttributes> { @AutoIncrement @PrimaryKey declare id: CreationOptional; - + // highlight-start /** Declared by {@link Comment#post} */ declare comments?: Comment[]; @@ -136,7 +166,7 @@ import { BelongsToGetAssociationMixin } from '@sequelize/core'; class Comment extends Model { @BelongsTo(() => Post, 'postId') declare post?: NonAttribute; - + // highlight-start declare getPost: BelongsToGetAssociationMixin; // highlight-end @@ -158,7 +188,7 @@ import { BelongsToSetAssociationMixin } from '@sequelize/core'; class Comment extends Model { @BelongsTo(() => Post, 'postId') declare post?: NonAttribute; - + // highlight-start declare setPost: BelongsToSetAssociationMixin; // highlight-end @@ -172,7 +202,7 @@ await comment.setPost(post); await comment.setPost(1); ``` -It is also possible to delay the call to `save` by setting the `save` option to `false`, however __this is not very useful__, +It is also possible to delay the call to `save` by setting the `save` option to `false`, however **this is not very useful**, as it is equivalent to setting the foreign key directly, but using a (pointlessly) asynchronous method. ```ts @@ -192,7 +222,7 @@ import { BelongsToCreateAssociationMixin } from '@sequelize/core'; class Comment extends Model { @BelongsTo(() => Post, 'postId') declare post?: NonAttribute; - + // highlight-start declare createPost: BelongsToCreateAssociationMixin; // highlight-end @@ -250,7 +280,7 @@ You can customize this by using the `targetKey` option. ```ts class Comment extends Model { declare id: CreationOptional; - + @BelongsTo(() => Post, { foreignKey: 'postId', // highlight-next-line @@ -259,4 +289,4 @@ class Comment extends Model { }) declare post?: NonAttribute; } -``` \ No newline at end of file +``` diff --git a/docs/associations/faq.md b/docs/associations/faq.md index ff975db3..86b9ee1a 100644 --- a/docs/associations/faq.md +++ b/docs/associations/faq.md @@ -43,7 +43,7 @@ Composite foreign keys are not currently supported by Sequelize's associations. ## Customizing Foreign Keys -Sequelize will generate foreign keys automatically, but you can customize how. The `foreignKey` option (as well as `otherKey` in [`BelongsToMany`](./belongs-to-many.md)) +Sequelize will generate foreign keys automatically, but you can customize how. The `foreignKey` option (as well as `otherKey` in [`BelongsToMany`](./belongs-to-many.md)) can be set to a string to specify the name of the foreign key, or to an object to specify the name of the foreign key and other options. When set to an object, the `foreignKey` option accepts all options that regular attributes accept, including `allowNull` and `defaultValue`. @@ -70,7 +70,7 @@ class Person extends Model { } class DrivingLicense extends Model { - @Attribute({ + @Attribute({ columnName: 'owner_id', }) declare ownerId: number; diff --git a/docs/associations/has-many.md b/docs/associations/has-many.md index 8b761c60..5fe74c2b 100644 --- a/docs/associations/has-many.md +++ b/docs/associations/has-many.md @@ -21,8 +21,22 @@ erDiagram Here is how you would define the `Post` and `Comment` models in Sequelize: ```ts -import { Model, DataTypes, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute } from '@sequelize/core'; -import { PrimaryKey, Attribute, AutoIncrement, NotNull, HasMany, BelongsTo } from '@sequelize/core/decorators-legacy'; +import { + Model, + DataTypes, + InferAttributes, + InferCreationAttributes, + CreationOptional, + NonAttribute, +} from '@sequelize/core'; +import { + PrimaryKey, + Attribute, + AutoIncrement, + NotNull, + HasMany, + BelongsTo, +} from '@sequelize/core/decorators-legacy'; class Post extends Model, InferCreationAttributes> { @Attribute(DataTypes.INTEGER) @@ -51,8 +65,8 @@ class Comment extends Model, InferCreationAttributes, InferCreationAttributes> { @Attribute(DataTypes.INTEGER) @AutoIncrement @PrimaryKey declare id: CreationOptional; - + @HasMany(() => Comment, { foreignKey: 'postId', // highlight-start @@ -92,7 +120,7 @@ class Comment extends Model, InferCreationAttributes; // highlight-end - + // This is the foreign key @Attribute(DataTypes.INTEGER) @NotNull @@ -173,7 +201,7 @@ await post.setComments([1, 2, 3]); If the foreign key is not nullable, calling this method will delete the previously associated models (if any), as setting their foreign key to `null` would result in a validation error. -If the foreign key is nullable, it will by default set it to null on all previously associated models. +If the foreign key is nullable, it will by default set it to null on all previously associated models. You can use the `destroyPrevious` option to delete the previously associated models instead: ```ts @@ -185,7 +213,7 @@ await post.setComments([], { destroyPrevious: true }); ### Association Adder (`addX`) -The association adder is used to add one or more new associated models without removing existing ones. +The association adder is used to add one or more new associated models without removing existing ones. There are two versions of this method: - `add`: Associates a single new model. @@ -241,7 +269,6 @@ There are two versions of this method: - `remove`: Removes a single associated model. - `remove`: Removes multiple associated models. - ```ts import { HasManyRemoveAssociationMixin, HasManyRemoveAssociationsMixin } from '@sequelize/core'; @@ -332,8 +359,7 @@ In the example above, we did not need to specify the `postId` attribute. This is If you use TypeScript, you need to let TypeScript know that the foreign key is not required. You can do so using the second generic argument of the `HasManyCreateAssociationMixin` type. ```ts -HasManyCreateAssociationMixin - ^ Here +HasManyCreateAssociationMixin ^ Here; ``` ::: @@ -417,7 +443,7 @@ You can customize this by using the `sourceKey` option. ```ts class Post extends Model { declare id: CreationOptional; - + @HasMany(() => Comment, { foreignKey: 'postId', // highlight-next-line diff --git a/docs/associations/has-one.md b/docs/associations/has-one.md index 488ddc49..731797b6 100644 --- a/docs/associations/has-one.md +++ b/docs/associations/has-one.md @@ -9,7 +9,7 @@ The HasOne association is used to create a One-To-One relationship between two m In a One-To-One relationship, a row of one table is associated with a single row of another table. -The most common type of One-To-One relationship is one where one side is mandatory, and the other side is optional. +The most common type of One-To-One relationship is one where one side is mandatory, and the other side is optional. For instance, a driving license always belongs to a single person, but a person can have zero or one driving licenses (from the same place). ```mermaid @@ -22,8 +22,22 @@ erDiagram Here is how you would define the `Person` and `DrivingLicense` models in Sequelize: ```ts -import { Model, DataTypes, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute } from '@sequelize/core'; -import { PrimaryKey, Attribute, AutoIncrement, NotNull, HasOne, BelongsTo } from '@sequelize/core/decorators-legacy'; +import { + Model, + DataTypes, + InferAttributes, + InferCreationAttributes, + CreationOptional, + NonAttribute, +} from '@sequelize/core'; +import { + PrimaryKey, + Attribute, + AutoIncrement, + NotNull, + HasOne, + BelongsTo, +} from '@sequelize/core/decorators-legacy'; class Person extends Model, InferCreationAttributes> { @Attribute(DataTypes.INTEGER) @@ -37,7 +51,10 @@ class Person extends Model, InferCreationAttributes, InferCreationAttributes> { +class DrivingLicense extends Model< + InferAttributes, + InferCreationAttributes +> { @Attribute(DataTypes.INTEGER) @AutoIncrement @PrimaryKey @@ -52,12 +69,12 @@ class DrivingLicense extends Model, InferCreatio } ``` -Note that in the example above, the `DrivingLicense` model has a foreign key to the `Person` model. __`HasOne` adds the foreign key -on the model the association targets.__ +Note that in the example above, the `DrivingLicense` model has a foreign key to the `Person` model. **`HasOne` adds the foreign key +on the model the association targets.** Always think about which model should own the foreign key, as the foreign key is the one that enforces the relationship. -In this case, because the driving license model has a non-null foreign key, +In this case, because the driving license model has a non-null foreign key, it is impossible to create a Driving License without assigning it to a Person. However, it's possible to create a Person without a Driving License. @@ -70,7 +87,10 @@ When using `HasOne`, you may want to add a unique constraint on the foreign key You can do this by using the `@Unique` decorator on the foreign key: ```ts -class DrivingLicense extends Model, InferCreationAttributes> { +class DrivingLicense extends Model< + InferAttributes, + InferCreationAttributes +> { @Attribute(DataTypes.INTEGER) @NotNull // highlight-next-line @@ -83,21 +103,35 @@ class DrivingLicense extends Model, InferCreatio ## Inverse association -The `HasOne` association automatically creates an inverse association on the target model. +The `HasOne` association automatically creates an inverse association on the target model. The inverse association is a [`BelongsTo`](./belongs-to.md) association. You can configure that inverse association by using the `inverse` option: ```ts -import { Model, DataTypes, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute } from '@sequelize/core'; -import { PrimaryKey, Attribute, AutoIncrement, NotNull, HasOne, BelongsTo } from '@sequelize/core/decorators-legacy'; +import { + Model, + DataTypes, + InferAttributes, + InferCreationAttributes, + CreationOptional, + NonAttribute, +} from '@sequelize/core'; +import { + PrimaryKey, + Attribute, + AutoIncrement, + NotNull, + HasOne, + BelongsTo, +} from '@sequelize/core/decorators-legacy'; class Person extends Model, InferCreationAttributes> { @Attribute(DataTypes.INTEGER) @AutoIncrement @PrimaryKey declare id: CreationOptional; - + @HasOne(() => DrivingLicense, { foreignKey: 'ownerId', // highlight-start @@ -109,12 +143,15 @@ class Person extends Model, InferCreationAttributes; } -class DrivingLicense extends Model, InferCreationAttributes> { +class DrivingLicense extends Model< + InferAttributes, + InferCreationAttributes +> { @Attribute(DataTypes.INTEGER) @AutoIncrement @PrimaryKey declare id: CreationOptional; - + // highlight-start /** Defined by {@link Person.drivingLicense} */ declare owner?: NonAttribute; @@ -142,7 +179,7 @@ import { HasOneGetAssociationMixin } from '@sequelize/core'; class Person extends Model, InferCreationAttributes> { @HasOne(() => DrivingLicense, 'ownerId') declare drivingLicense?: NonAttribute; - + // highlight-start declare getDrivingLicense: HasOneGetAssociationMixin; // highlight-end @@ -168,11 +205,11 @@ import { HasOneSetAssociationMixin } from '@sequelize/core'; class Person extends Model, InferCreationAttributes> { @HasOne(() => DrivingLicense, 'ownerId') declare drivingLicense?: NonAttribute; - + // highlight-start declare setDrivingLicense: HasOneSetAssociationMixin< - DrivingLicense, - /* this is the type of the primary key of the target */ + DrivingLicense, + /* this is the type of the primary key of the target */ DrivingLicense['id'] >; // highlight-end @@ -181,7 +218,9 @@ class Person extends Model, InferCreationAttributes, InferCreationAttributes> { @HasOne(() => DrivingLicense, 'ownerId') declare drivingLicense?: NonAttribute; - + // highlight-start declare createDrivingLicense: HasOneCreateAssociationMixin; // highlight-end @@ -245,8 +284,7 @@ In the example above, we did not need to specify the `ownerId` attribute. This i If you use TypeScript, you need to let TypeScript know that the foreign key is not required. You can do so using the second generic argument of the `HasOneCreateAssociationMixin` type. ```ts -HasOneCreateAssociationMixin - ^ Here +HasOneCreateAssociationMixin ^ Here; ``` ::: @@ -275,7 +313,7 @@ You can customize this by using the `sourceKey` option. ```ts class Person extends Model { declare id: CreationOptional; - + @HasOne(() => DrivingLicense, { foreignKey: 'ownerId', // highlight-next-line diff --git a/docs/associations/polymorphic-associations.md b/docs/associations/polymorphic-associations.md index 402ae63a..619bdbe3 100644 --- a/docs/associations/polymorphic-associations.md +++ b/docs/associations/polymorphic-associations.md @@ -28,7 +28,10 @@ for each model that can have comments, such as `ArticleComment` and `VideoCommen ```ts // This is the base model, which defines the common fields between all comments. @AbstractModel -abstract class AbstractComment extends Model { +abstract class AbstractComment extends Model< + Attributes, + CreationAttributes +> { declare id: number; @Attributes(DataTypes.STRING) @@ -41,13 +44,19 @@ abstract class AbstractComment extends Model, InferCreationAttributes> { +class ArticleComment extends AbstractComment< + InferAttributes, + InferCreationAttributes +> { @BelongsTo(() => Article, 'targetId') declare target?: Article; } // This is the model for comments on videos. -class VideoComment extends AbstractComment, InferCreationAttributes> { +class VideoComment extends AbstractComment< + InferAttributes, + InferCreationAttributes +> { @BelongsTo(() => Video, 'targetId') declare target?: Video; } @@ -98,7 +107,7 @@ For these reasons, we highly recommend using one of the other two solutions inst ::: -In this type of polymorphic association, we don't use foreign keys at all. +In this type of polymorphic association, we don't use foreign keys at all. Instead, we use two columns: one to store the type of the associated model, and one to store the ID of the associated model. As stated above, we must disable the foreign key constraints on the association, as the same column is referencing multiple tables. @@ -121,13 +130,13 @@ class Comment extends Model, InferCreationAttributes; - + /** Defined by {@link Video#comments} */ declare video?: NonAttribute