forked from sequelize/sequelize
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(types): Add ModelDefined type as syntactic sugar (sequelize#12445)
- Loading branch information
Showing
3 changed files
with
257 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
/** | ||
* Keep this file in sync with the code in the "Usage" section in typescript.md | ||
*/ | ||
import { | ||
Sequelize, | ||
Model, | ||
ModelDefined, | ||
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; | ||
} | ||
|
||
// You can also define modules in a functional way | ||
interface NoteAttributes { | ||
id: number; | ||
title: string; | ||
content: string; | ||
} | ||
|
||
// You can also set multiple attributes optional at once | ||
interface NoteCreationAttributes | ||
extends Optional<NoteAttributes, "id" | "title"> {} | ||
|
||
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 | ||
} | ||
); | ||
|
||
// And with a functional approach defining a module looks like this | ||
const Note: ModelDefined< | ||
NoteAttributes, | ||
NoteCreationAttributes | ||
> = sequelize.define( | ||
"Note", | ||
{ | ||
id: { | ||
type: DataTypes.INTEGER.UNSIGNED, | ||
autoIncrement: true, | ||
primaryKey: true, | ||
}, | ||
title: { | ||
type: new DataTypes.STRING(64), | ||
defaultValue: "Unnamed Note", | ||
}, | ||
content: { | ||
type: new DataTypes.STRING(4096), | ||
allowNull: false, | ||
}, | ||
}, | ||
{ | ||
tableName: "notes", | ||
} | ||
); | ||
|
||
// 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); | ||
} |