TypeScript

Sequelize 提供了自己的 TypeScript 定义.

请注意, 仅支持 TypeScript >= 4.1. 我们对 TypeScript 的支持不遵循 SemVer. 我们将支持 TypeScript 版本至少一年, 之后它们可能会在 SemVer MINOR 版本中被删除.

由于 Sequelize 严重依赖于运行时属性分配, 因此 TypeScript 无法立即开箱即用.

为了使模型可用, 需要大量的手动类型声明.

安装

为了避免非 TS 用户的安装膨胀,你必须手动安装以下键入程序包:

  • @types/node (在 node 项目中这是通常是必须的)
  • @types/validator

使用

重要: 您必须在类属性类型上使用 declare 以确保 TypeScript 不会触发这些类属性. 参阅 公共类字段的注意事项

Sequelize Models 接受两种通用类型来定义模型的属性和创建属性是什么样的:

  1. import { Model, Optional } from 'sequelize';
  2. // We don't recommend doing this. Read on for the new way of declaring Model typings.
  3. type UserAttributes = {
  4. id: number,
  5. name: string,
  6. // other attributes...
  7. };
  8. // we're telling the Model that 'id' is optional
  9. // when creating an instance of the model (such as using Model.create()).
  10. type UserCreationAttributes = Optional<UserAttributes, 'id'>;
  11. class User extends Model<UserAttributes, UserCreationAttributes> {
  12. declare id: number;
  13. declare string: number;
  14. // other attributes...
  15. }

这个解决方案很冗长. Sequelize >=6.14.0 提供了新的实用程序类型, 将大大减少数量 必需的样板: InferAttributesInferCreationAttributes. 他们将提取属性类型 直接来自模型:

  1. import { Model, InferAttributes, InferCreationAttributes, CreationOptional } from 'sequelize';
  2. // order of InferAttributes & InferCreationAttributes is important.
  3. class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
  4. // 'CreationOptional' is a special type that marks the field as optional
  5. // when creating an instance of the model (such as using Model.create()).
  6. declare id: CreationOptional<number>;
  7. declare string: number;
  8. // other attributes...
  9. }

关于 InferAttributesInferCreationAttributes 工作需要了解的重要事项: 它们将选择类的所有声明属性,除了:

  • 静态字段和方法.
  • 方法(任何类型为函数的东西).
  • 类型使用铭记类型 NonAttribute 的那些.
  • 像这样使用 AttributesOf 排除的那些: InferAttributes<User, { omit: 'properties' | 'to' | 'omit' }>.
  • 由 Model 超类声明的那些(但不是中间类!). 如果您的属性之一与 Model 的属性之一同名, 请更改其名称. 无论如何, 这样做可能会导致问题.
  • Getter & setter 不会被自动排除. 将它们的 return / parameter 类型设置为 NonAttribute, 或将它们添加到 omit 以排除它们.

InferCreationAttributes 的工作方式与 AttributesOf 相同, 但有一个例外: 使用 CreationOptional 类型键入的属性将被标记为可选.

您只需要在类实例字段或 getter 上使用 CreationOptionalNonAttribute.

对属性进行严格类型检查的最小 TypeScript 项目示例:

  1. import {
  2. Association, DataTypes, HasManyAddAssociationMixin, HasManyCountAssociationsMixin,
  3. HasManyCreateAssociationMixin, HasManyGetAssociationsMixin, HasManyHasAssociationMixin,
  4. HasManySetAssociationsMixin, HasManyAddAssociationsMixin, HasManyHasAssociationsMixin,
  5. HasManyRemoveAssociationMixin, HasManyRemoveAssociationsMixin, Model, ModelDefined, Optional,
  6. Sequelize, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute
  7. } from 'sequelize';
  8. const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');
  9. // 'projects' is excluded as it's not an attribute, it's an association.
  10. class User extends Model<InferAttributes<User, { omit: 'projects' }>, InferCreationAttributes<User, { omit: 'projects' }>> {
  11. // id can be undefined during creation when using `autoIncrement`
  12. declare id: CreationOptional<number>;
  13. declare name: string;
  14. declare preferredName: string | null; // for nullable fields
  15. // timestamps!
  16. // createdAt can be undefined during creation
  17. declare createdAt: CreationOptional<Date>;
  18. // updatedAt can be undefined during creation
  19. declare updatedAt: CreationOptional<Date>;
  20. // Since TS cannot determine model association at compile time
  21. // we have to declare them here purely virtually
  22. // these will not exist until `Model.init` was called.
  23. declare getProjects: HasManyGetAssociationsMixin<Project>; // Note the null assertions!
  24. declare addProject: HasManyAddAssociationMixin<Project, number>;
  25. declare addProjects: HasManyAddAssociationsMixin<Project, number>;
  26. declare setProjects: HasManySetAssociationsMixin<Project, number>;
  27. declare removeProject: HasManyRemoveAssociationMixin<Project, number>;
  28. declare removeProjects: HasManyRemoveAssociationsMixin<Project, number>;
  29. declare hasProject: HasManyHasAssociationMixin<Project, number>;
  30. declare hasProjects: HasManyHasAssociationsMixin<Project, number>;
  31. declare countProjects: HasManyCountAssociationsMixin;
  32. declare createProject: HasManyCreateAssociationMixin<Project, 'ownerId'>;
  33. // You can also pre-declare possible inclusions, these will only be populated if you
  34. // actively include a relation.
  35. declare projects?: NonAttribute<Project[]>; // Note this is optional since it's only populated when explicitly requested in code
  36. // getters that are not attributes should be tagged using NonAttribute
  37. // to remove them from the model's Attribute Typings.
  38. get fullName(): NonAttribute<string> {
  39. return this.name;
  40. }
  41. declare static associations: {
  42. projects: Association<User, Project>;
  43. };
  44. }
  45. class Project extends Model<
  46. InferAttributes<Project>,
  47. InferCreationAttributes<Project>
  48. > {
  49. // id can be undefined during creation when using `autoIncrement`
  50. declare id: CreationOptional<number>;
  51. declare ownerId: number;
  52. declare name: string;
  53. // `owner` is an eagerly-loaded association.
  54. // We tag it as `NonAttribute`
  55. declare owner?: NonAttribute<User>;
  56. // createdAt can be undefined during creation
  57. declare createdAt: CreationOptional<Date>;
  58. // updatedAt can be undefined during creation
  59. declare updatedAt: CreationOptional<Date>;
  60. }
  61. class Address extends Model<
  62. InferAttributes<Address>,
  63. InferCreationAttributes<Address>
  64. > {
  65. declare userId: number;
  66. declare address: string;
  67. // createdAt can be undefined during creation
  68. declare createdAt: CreationOptional<Date>;
  69. // updatedAt can be undefined during creation
  70. declare updatedAt: CreationOptional<Date>;
  71. }
  72. Project.init(
  73. {
  74. id: {
  75. type: DataTypes.INTEGER.UNSIGNED,
  76. autoIncrement: true,
  77. primaryKey: true
  78. },
  79. ownerId: {
  80. type: DataTypes.INTEGER.UNSIGNED,
  81. allowNull: false
  82. },
  83. name: {
  84. type: new DataTypes.STRING(128),
  85. allowNull: false
  86. },
  87. createdAt: DataTypes.DATE,
  88. updatedAt: DataTypes.DATE,
  89. },
  90. {
  91. sequelize,
  92. tableName: 'projects'
  93. }
  94. );
  95. User.init(
  96. {
  97. id: {
  98. type: DataTypes.INTEGER.UNSIGNED,
  99. autoIncrement: true,
  100. primaryKey: true
  101. },
  102. name: {
  103. type: new DataTypes.STRING(128),
  104. allowNull: false
  105. },
  106. preferredName: {
  107. type: new DataTypes.STRING(128),
  108. allowNull: true
  109. },
  110. createdAt: DataTypes.DATE,
  111. updatedAt: DataTypes.DATE,
  112. },
  113. {
  114. tableName: 'users',
  115. sequelize // passing the `sequelize` instance is required
  116. }
  117. );
  118. Address.init(
  119. {
  120. userId: {
  121. type: DataTypes.INTEGER.UNSIGNED
  122. },
  123. address: {
  124. type: new DataTypes.STRING(128),
  125. allowNull: false
  126. },
  127. createdAt: DataTypes.DATE,
  128. updatedAt: DataTypes.DATE,
  129. },
  130. {
  131. tableName: 'address',
  132. sequelize // passing the `sequelize` instance is required
  133. }
  134. );
  135. // You can also define modules in a functional way
  136. interface NoteAttributes {
  137. id: number;
  138. title: string;
  139. content: string;
  140. }
  141. // You can also set multiple attributes optional at once
  142. type NoteCreationAttributes = Optional<NoteAttributes, 'id' | 'title'>;
  143. // And with a functional approach defining a module looks like this
  144. const Note: ModelDefined<
  145. NoteAttributes,
  146. NoteCreationAttributes
  147. > = sequelize.define(
  148. 'Note',
  149. {
  150. id: {
  151. type: DataTypes.INTEGER.UNSIGNED,
  152. autoIncrement: true,
  153. primaryKey: true
  154. },
  155. title: {
  156. type: new DataTypes.STRING(64),
  157. defaultValue: 'Unnamed Note'
  158. },
  159. content: {
  160. type: new DataTypes.STRING(4096),
  161. allowNull: false
  162. }
  163. },
  164. {
  165. tableName: 'notes'
  166. }
  167. );
  168. // Here we associate which actually populates out pre-declared `association` static and other methods.
  169. User.hasMany(Project, {
  170. sourceKey: 'id',
  171. foreignKey: 'ownerId',
  172. as: 'projects' // this determines the name in `associations`!
  173. });
  174. Address.belongsTo(User, { targetKey: 'id' });
  175. User.hasOne(Address, { sourceKey: 'id' });
  176. async function doStuffWithUser() {
  177. const newUser = await User.create({
  178. name: 'Johnny',
  179. preferredName: 'John',
  180. });
  181. console.log(newUser.id, newUser.name, newUser.preferredName);
  182. const project = await newUser.createProject({
  183. name: 'first!'
  184. });
  185. const ourUser = await User.findByPk(1, {
  186. include: [User.associations.projects],
  187. rejectOnEmpty: true // Specifying true here removes `null` from the return type!
  188. });
  189. // Note the `!` null assertion since TS can't know if we included
  190. // the model or not
  191. console.log(ourUser.projects![0].name);
  192. }
  193. (async () => {
  194. await sequelize.sync();
  195. await doStuffWithUser();
  196. })();

使用非严格类型

Sequelize v5 的类型允许你定义模型而无需指定属性类型. 对于向后兼容以及在你觉得对属性进行严格检查是不值得的情况下, 这仍然是可行的.

  1. import { Sequelize, Model, DataTypes } from 'sequelize';
  2. const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');
  3. class User extends Model {
  4. declare id: number;
  5. declare name: string;
  6. declare preferredName: string | null;
  7. }
  8. User.init(
  9. {
  10. id: {
  11. type: DataTypes.INTEGER.UNSIGNED,
  12. autoIncrement: true,
  13. primaryKey: true,
  14. },
  15. name: {
  16. type: new DataTypes.STRING(128),
  17. allowNull: false,
  18. },
  19. preferredName: {
  20. type: new DataTypes.STRING(128),
  21. allowNull: true,
  22. },
  23. },
  24. {
  25. tableName: 'users',
  26. sequelize, // passing the `sequelize` instance is required
  27. },
  28. );
  29. async function doStuffWithUserModel() {
  30. const newUser = await User.create({
  31. name: 'Johnny',
  32. preferredName: 'John',
  33. });
  34. console.log(newUser.id, newUser.name, newUser.preferredName);
  35. const foundUser = await User.findOne({ where: { name: 'Johnny' } });
  36. if (foundUser === null) return;
  37. console.log(foundUser.name);
  38. }

使用 sequelize.define

在 v5 之前的 Sequelize 版本中, 定义模型的默认方式涉及使用 sequelize.define. 仍然可以使用它来定义模型, 也可以使用接口在这些模型中添加类型.

  1. import { Sequelize, Model, DataTypes, Optional } from 'sequelize';
  2. const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');
  3. // We recommend you declare an interface for the attributes, for stricter typechecking
  4. interface UserAttributes {
  5. id: number;
  6. name: string;
  7. }
  8. // Some fields are optional when calling UserModel.create() or UserModel.build()
  9. interface UserCreationAttributes extends Optional<UserAttributes, 'id'> {}
  10. // We need to declare an interface for our model that is basically what our class would be
  11. interface UserInstance
  12. extends Model<UserAttributes, UserCreationAttributes>,
  13. UserAttributes {}
  14. const UserModel = sequelize.define<UserInstance>('User', {
  15. id: {
  16. primaryKey: true,
  17. type: DataTypes.INTEGER.UNSIGNED,
  18. },
  19. name: {
  20. type: DataTypes.STRING,
  21. }
  22. });
  23. async function doStuff() {
  24. const instance = await UserModel.findByPk(1, {
  25. rejectOnEmpty: true,
  26. });
  27. console.log(instance.id);
  28. }

如果你对模型上非严格的属性检查命令感到满意,则可以通过定义 Instance 来扩展 Model 而无需泛型类型中的任何属性, 从而节省一些代码.

  1. import { Sequelize, Model, DataTypes } from 'sequelize';
  2. const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');
  3. // We need to declare an interface for our model that is basically what our class would be
  4. interface UserInstance extends Model {
  5. id: number;
  6. name: string;
  7. }
  8. const UserModel = sequelize.define<UserInstance>('User', {
  9. id: {
  10. primaryKey: true,
  11. type: DataTypes.INTEGER.UNSIGNED,
  12. },
  13. name: {
  14. type: DataTypes.STRING,
  15. },
  16. });
  17. async function doStuff() {
  18. const instance = await UserModel.findByPk(1, {
  19. rejectOnEmpty: true,
  20. });
  21. console.log(instance.id);
  22. }

实用程序类型

请求模型类

ModelStatic 旨在用于键入模型 class.

以下是请求模型类并返回该类中定义的主键列表的实用方法示例:

  1. import { ModelStatic, ModelAttributeColumnOptions, Model, InferAttributes, InferCreationAttributes, CreationOptional } from 'sequelize';
  2. /**
  3. * Returns the list of attributes that are part of the model's primary key.
  4. */
  5. export function getPrimaryKeyAttributes(model: ModelStatic<any>): ModelAttributeColumnOptions[] {
  6. const attributes: ModelAttributeColumnOptions[] = [];
  7. for (const attribute of Object.values(model.rawAttributes)) {
  8. if (attribute.primaryKey) {
  9. attributes.push(attribute);
  10. }
  11. }
  12. return attributes;
  13. }
  14. class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
  15. id: CreationOptional<number>;
  16. }
  17. User.init({
  18. id: {
  19. type: DataTypes.INTEGER.UNSIGNED,
  20. autoIncrement: true,
  21. primaryKey: true
  22. },
  23. }, { sequelize });
  24. const primaryAttributes = getPrimaryKeyAttributes(User);

获取模型的属性

如果您需要访问给定模型的属性列表, 你需要使用 Attributes<Model>CreationAttributes<Model>.

它们将返回作为参数传递的模型的属性(和创建属性).

不要将它们与 InferAttributesInferCreationAttributes 混淆. 这两种实用程序类型应该只被使用 在模型的定义中自动从模型的公共类字段创建属性列表. 它们仅适用于基于类的模型定义(使用 Model.init 时).

Attributes<Model>CreationAttributes<Model> 将返回任何模型的属性列表, 无论它们是如何创建的(无论是 Model.init 还是 Sequelize#define).

这是一个请求模型类和属性名称的实用函数示例; 并返回相应的属性元数据.

  1. import {
  2. ModelStatic,
  3. ModelAttributeColumnOptions,
  4. Model,
  5. InferAttributes,
  6. InferCreationAttributes,
  7. CreationOptional,
  8. Attributes
  9. } from 'sequelize';
  10. export function getAttributeMetadata<M extends Model>(model: ModelStatic<M>, attributeName: keyof Attributes<M>): ModelAttributeColumnOptions {
  11. const attribute = model.rawAttributes[attributeName];
  12. if (attribute == null) {
  13. throw new Error(`Attribute ${attributeName} does not exist on model ${model.name}`);
  14. }
  15. return attribute;
  16. }
  17. class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
  18. id: CreationOptional<number>;
  19. }
  20. User.init({
  21. id: {
  22. type: DataTypes.INTEGER.UNSIGNED,
  23. autoIncrement: true,
  24. primaryKey: true
  25. },
  26. }, { sequelize });
  27. const idAttributeMeta = getAttributeMetadata(User, 'id'); // works!
  28. // @ts-expect-error
  29. const nameAttributeMeta = getAttributeMetadata(User, 'name'); // fails because 'name' is not an attribute of User