不要怂,就是干,撸起袖子干!

Commit 9c446f9c by Harry Yu Committed by GitHub

fix(types): fixed types for model.init and sequelize.define; update docs (#12435)

1 parent 54737510
...@@ -1633,7 +1633,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut ...@@ -1633,7 +1633,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
*/ */
public static init<M extends Model>( public static init<M extends Model>(
this: ModelStatic<M>, this: ModelStatic<M>,
attributes: ModelAttributes<M, M['_creationAttributes']>, options: InitOptions<M> attributes: ModelAttributes<M, M['_attributes']>, options: InitOptions<M>
): Model; ): Model;
/** /**
......
...@@ -772,7 +772,7 @@ export class Sequelize extends Hooks { ...@@ -772,7 +772,7 @@ export class Sequelize extends Hooks {
* A hook that is run before sequelize.sync call * A hook that is run before sequelize.sync call
* @param fn A callback function that is called with options passed to sequelize.sync * @param fn A callback function that is called with options passed to sequelize.sync
*/ */
public static beforeBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; public static beforeBulkSync(dname: string, fn: (options: SyncOptions) => HookReturn): void;
public static beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; public static beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void;
/** /**
...@@ -1164,7 +1164,7 @@ export class Sequelize extends Hooks { ...@@ -1164,7 +1164,7 @@ export class Sequelize extends Hooks {
* @param options These options are merged with the default define options provided to the Sequelize * @param options These options are merged with the default define options provided to the Sequelize
* constructor * constructor
*/ */
public define<M extends Model, TCreationAttributes = M['_creationAttributes']>( public define<M extends Model, TCreationAttributes = M['_attributes']>(
modelName: string, modelName: string,
attributes: ModelAttributes<M, TCreationAttributes>, attributes: ModelAttributes<M, TCreationAttributes>,
options?: ModelOptions options?: ModelOptions
......
import { BuildOptions, DataTypes, Model } from 'sequelize'; import { BuildOptions, DataTypes, Model, Optional } from 'sequelize';
import { sequelize } from './connection'; import { sequelize } from './connection';
// I really wouldn't recommend this, but if you want you can still use define() and interfaces // I really wouldn't recommend this, but if you want you can still use define() and interfaces
...@@ -8,26 +8,34 @@ interface UserAttributes { ...@@ -8,26 +8,34 @@ interface UserAttributes {
username: string; username: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
createdAt: Date;
updatedAt: Date;
} }
interface UserCreationAttributes extends Partial<UserAttributes> {} interface UserCreationAttributes extends Optional<UserAttributes, 'id'> {}
interface UserModel extends Model<UserAttributes, UserCreationAttributes>, UserAttributes {} interface UserModel
extends Model<UserAttributes, UserCreationAttributes>,
UserAttributes {}
const User = sequelize.define<UserModel>( const User = sequelize.define<UserModel>(
'User', { firstName: DataTypes.STRING }, { tableName: 'users' }); 'User',
{
id: { type: DataTypes.NUMBER, primaryKey: true },
username: DataTypes.STRING,
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
},
{ tableName: 'users' },
);
async function test() { async function test() {
const user: UserModel = new User() as UserModel; const user: UserModel = new User() as UserModel;
const user2: UserModel | null = await User.findOne(); const user2: UserModel | null = await User.findOne();
if (!user2) return; if (!user2) return;
user2.firstName = 'John'; user2.firstName = 'John';
await user2.save(); await user2.save();
} }
// The below doesn't define Attribute types, but should still work // The below doesn't define Attribute types, but should still work
...@@ -36,9 +44,17 @@ interface UntypedUserModel extends Model, UserAttributes {} ...@@ -36,9 +44,17 @@ interface UntypedUserModel extends Model, UserAttributes {}
type UntypedUserModelStatic = typeof Model & { type UntypedUserModelStatic = typeof Model & {
new (values?: keyof any, options?: BuildOptions): UntypedUserModel; new (values?: keyof any, options?: BuildOptions): UntypedUserModel;
customStaticMethod(): unknown; customStaticMethod(): unknown;
} };
const UntypedUser = sequelize.define<UntypedUserModel>( const UntypedUser = sequelize.define<UntypedUserModel>(
'User', { firstName: DataTypes.STRING }, { tableName: 'users' }) as UntypedUserModelStatic; 'User',
{
id: { type: DataTypes.NUMBER, primaryKey: true },
username: DataTypes.STRING,
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
},
{ tableName: 'users' },
) as UntypedUserModelStatic;
UntypedUser.customStaticMethod = () => {}; UntypedUser.customStaticMethod = () => {};
......
...@@ -21,8 +21,8 @@ export interface UserAttributes { ...@@ -21,8 +21,8 @@ export interface UserAttributes {
} }
/** /**
* In this case, we make every single field optional. In real cases, only * In this case, we make most fields optional. In real cases,
* fields that have default/autoincrement values should be made optional. * only fields that have default/autoincrement values should be made optional.
*/ */
export interface UserCreationAttributes extends Optional<UserAttributes, 'id' | 'username' | 'lastName' | 'groupId'> {} export interface UserCreationAttributes extends Optional<UserAttributes, 'id' | 'username' | 'lastName' | 'groupId'> {}
...@@ -48,6 +48,10 @@ export class User extends Model<UserAttributes, UserCreationAttributes> implemen ...@@ -48,6 +48,10 @@ export class User extends Model<UserAttributes, UserCreationAttributes> implemen
User.init( User.init(
{ {
id: {
type: DataTypes.NUMBER,
primaryKey: true,
},
firstName: DataTypes.STRING, firstName: DataTypes.STRING,
lastName: DataTypes.STRING, lastName: DataTypes.STRING,
username: DataTypes.STRING, username: DataTypes.STRING,
...@@ -62,7 +66,7 @@ User.init( ...@@ -62,7 +66,7 @@ User.init(
}, },
setterMethods: { setterMethods: {
b(val: string) { b(val: string) {
(<User>this).username = val; this.username = val;
}, },
}, },
scopes: { scopes: {
......
/**
* Keep this file in sync with the code in the "Usage of `sequelize.define`"
* section in typescript.md
*/
import { Sequelize, Model, DataTypes, Optional } from 'sequelize';
const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');
// We recommend you declare an interface for the attributes, for stricter typechecking
interface UserAttributes {
id: number;
name: string;
}
// Some fields are optional when calling UserModel.create() or UserModel.build()
interface UserCreationAttributes extends Optional<UserAttributes, 'id'> {}
// We need to declare an interface for our model that is basically what our class would be
interface UserInstance
extends Model<UserAttributes, UserCreationAttributes>,
UserAttributes {}
const UserModel = sequelize.define<UserInstance>('User', {
id: {
primaryKey: true,
type: DataTypes.INTEGER.UNSIGNED,
},
name: {
type: DataTypes.STRING,
}
});
async function doStuff() {
const instance = await UserModel.findByPk(1, {
rejectOnEmpty: true,
});
console.log(instance.id);
}
/**
* Keep this file in sync with the code in the "Usage of `sequelize.define`"
* that doesn't have attribute types in typescript.md
*/
import { Sequelize, Model, DataTypes } from 'sequelize';
const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');
// We need to declare an interface for our model that is basically what our class would be
interface UserInstance extends Model {
id: number;
name: string;
}
const UserModel = sequelize.define<UserInstance>('User', {
id: {
primaryKey: true,
type: DataTypes.INTEGER.UNSIGNED,
},
name: {
type: DataTypes.STRING,
},
});
async function doStuff() {
const instance = await UserModel.findByPk(1, {
rejectOnEmpty: true,
});
console.log(instance.id);
}
/**
* Keep this file in sync with the code in the "Usage" section in typescript.md
*/
import {
Sequelize,
Model,
DataTypes,
HasManyGetAssociationsMixin,
HasManyAddAssociationMixin,
HasManyHasAssociationMixin,
Association,
HasManyCountAssociationsMixin,
HasManyCreateAssociationMixin,
Optional,
} from 'sequelize';
const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');
// These are all the attributes in the User model
interface UserAttributes {
id: number;
name: string;
preferredName: string | null;
}
// Some attributes are optional in `User.build` and `User.create` calls
interface UserCreationAttributes extends Optional<UserAttributes, 'id'> {}
class User extends Model<UserAttributes, UserCreationAttributes>
implements UserAttributes {
public id!: number; // Note that the `null assertion` `!` is required in strict mode.
public name!: string;
public preferredName!: string | null; // for nullable fields
// timestamps!
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
// Since TS cannot determine model association at compile time
// we have to declare them here purely virtually
// these will not exist until `Model.init` was called.
public getProjects!: HasManyGetAssociationsMixin<Project>; // Note the null assertions!
public addProject!: HasManyAddAssociationMixin<Project, number>;
public hasProject!: HasManyHasAssociationMixin<Project, number>;
public countProjects!: HasManyCountAssociationsMixin;
public createProject!: HasManyCreateAssociationMixin<Project>;
// You can also pre-declare possible inclusions, these will only be populated if you
// actively include a relation.
public readonly projects?: Project[]; // Note this is optional since it's only populated when explicitly requested in code
public static associations: {
projects: Association<User, Project>;
};
}
interface ProjectAttributes {
id: number;
ownerId: number;
name: string;
}
interface ProjectCreationAttributes extends Optional<ProjectAttributes, 'id'> {}
class Project extends Model<ProjectAttributes, ProjectCreationAttributes>
implements ProjectAttributes {
public id!: number;
public ownerId!: number;
public name!: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
}
interface AddressAttributes {
userId: number;
address: string;
}
// You can write `extends Model<AddressAttributes, AddressAttributes>` instead,
// but that will do the exact same thing as below
class Address extends Model<AddressAttributes> implements AddressAttributes {
public userId!: number;
public address!: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
}
Project.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
ownerId: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
},
name: {
type: new DataTypes.STRING(128),
allowNull: false,
},
},
{
sequelize,
tableName: 'projects',
},
);
User.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
name: {
type: new DataTypes.STRING(128),
allowNull: false,
},
preferredName: {
type: new DataTypes.STRING(128),
allowNull: true,
},
},
{
tableName: 'users',
sequelize, // passing the `sequelize` instance is required
},
);
Address.init(
{
userId: {
type: DataTypes.INTEGER.UNSIGNED,
},
address: {
type: new DataTypes.STRING(128),
allowNull: false,
},
},
{
tableName: 'address',
sequelize, // passing the `sequelize` instance is required
},
);
// Here we associate which actually populates out pre-declared `association` static and other methods.
User.hasMany(Project, {
sourceKey: 'id',
foreignKey: 'ownerId',
as: 'projects', // this determines the name in `associations`!
});
Address.belongsTo(User, { targetKey: 'id' });
User.hasOne(Address, { sourceKey: 'id' });
async function doStuffWithUser() {
const newUser = await User.create({
name: 'Johnny',
preferredName: 'John',
});
console.log(newUser.id, newUser.name, newUser.preferredName);
const project = await newUser.createProject({
name: 'first!',
});
const ourUser = await User.findByPk(1, {
include: [User.associations.projects],
rejectOnEmpty: true, // Specifying true here removes `null` from the return type!
});
// Note the `!` null assertion since TS can't know if we included
// the model or not
console.log(ourUser.projects![0].name);
}
/**
* Keep this file in sync with the code in the "Usage without strict types for
* attributes" section in typescript.md
*/
import { Sequelize, Model, DataTypes } from 'sequelize';
const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');
class User extends Model {
public id!: number; // Note that the `null assertion` `!` is required in strict mode.
public name!: string;
public preferredName!: string | null; // for nullable fields
}
User.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
name: {
type: new DataTypes.STRING(128),
allowNull: false,
},
preferredName: {
type: new DataTypes.STRING(128),
allowNull: true,
},
},
{
tableName: 'users',
sequelize, // passing the `sequelize` instance is required
},
);
async function doStuffWithUserModel() {
const newUser = await User.create({
name: 'Johnny',
preferredName: 'John',
});
console.log(newUser.id, newUser.name, newUser.preferredName);
const foundUser = await User.findOne({ where: { name: 'Johnny' } });
if (foundUser === null) return;
console.log(foundUser.name);
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!