Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(types): Add ModelDefined type as syntactic sugar #12445

Merged
merged 6 commits into from
Jul 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 38 additions & 1 deletion docs/manual/other-topics/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ In order to avoid installation bloat for non TS users, you must install the foll

Example of a minimal TypeScript project with strict type-checking for attributes.

**NOTE:** Keep the following code in sync with `typescriptDocs/ModelInit.ts` to ensure it typechecks correctly.
**NOTE:** Keep the following code in sync with `/types/test/typescriptDocs/ModelInit.ts` to ensure it typechecks correctly.

```ts
import {
Sequelize,
Model,
ModelDefined,
DataTypes,
HasManyGetAssociationsMixin,
HasManyAddAssociationMixin,
Expand Down Expand Up @@ -104,6 +105,16 @@ class Address extends Model<AddressAttributes> implements AddressAttributes {
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: {
Expand Down Expand Up @@ -164,6 +175,32 @@ Address.init(
}
);

// 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",
Expand Down
2 changes: 2 additions & 0 deletions types/lib/model.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2849,6 +2849,8 @@ export type ModelType = typeof Model;
// must come first for unknown reasons.
export type ModelCtor<M extends Model> = typeof Model & { new(): M };

export type ModelDefined<S, T> = ModelCtor<Model<S, T>>;

export type ModelStatic<M extends Model> = { new(): M };

export default Model;
74 changes: 56 additions & 18 deletions types/test/typescriptDocs/ModelInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import {
Sequelize,
Model,
ModelDefined,
DataTypes,
HasManyGetAssociationsMixin,
HasManyAddAssociationMixin,
Expand All @@ -12,9 +13,9 @@ import {
HasManyCountAssociationsMixin,
HasManyCreateAssociationMixin,
Optional,
} from 'sequelize';
} from "sequelize";

const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');
const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb");

// These are all the attributes in the User model
interface UserAttributes {
Expand All @@ -24,7 +25,7 @@ interface UserAttributes {
}

// Some attributes are optional in `User.build` and `User.create` calls
interface UserCreationAttributes extends Optional<UserAttributes, 'id'> {}
interface UserCreationAttributes extends Optional<UserAttributes, "id"> {}

class User extends Model<UserAttributes, UserCreationAttributes>
implements UserAttributes {
Expand Down Expand Up @@ -60,7 +61,7 @@ interface ProjectAttributes {
name: string;
}

interface ProjectCreationAttributes extends Optional<ProjectAttributes, 'id'> {}
interface ProjectCreationAttributes extends Optional<ProjectAttributes, "id"> {}

class Project extends Model<ProjectAttributes, ProjectCreationAttributes>
implements ProjectAttributes {
Expand All @@ -87,6 +88,17 @@ class Address extends Model<AddressAttributes> implements AddressAttributes {
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: {
Expand All @@ -105,8 +117,8 @@ Project.init(
},
{
sequelize,
tableName: 'projects',
},
tableName: "projects",
}
);

User.init(
Expand All @@ -126,9 +138,9 @@ User.init(
},
},
{
tableName: 'users',
tableName: "users",
sequelize, // passing the `sequelize` instance is required
},
}
);

Address.init(
Expand All @@ -142,30 +154,56 @@ Address.init(
},
},
{
tableName: 'address',
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`!
sourceKey: "id",
foreignKey: "ownerId",
as: "projects", // this determines the name in `associations`!
});

Address.belongsTo(User, { targetKey: 'id' });
User.hasOne(Address, { sourceKey: 'id' });
Address.belongsTo(User, { targetKey: "id" });
User.hasOne(Address, { sourceKey: "id" });

async function doStuffWithUser() {
const newUser = await User.create({
name: 'Johnny',
preferredName: 'John',
name: "Johnny",
preferredName: "John",
});
console.log(newUser.id, newUser.name, newUser.preferredName);

const project = await newUser.createProject({
name: 'first!',
name: "first!",
});

const ourUser = await User.findByPk(1, {
Expand All @@ -176,4 +214,4 @@ async function doStuffWithUser() {
// Note the `!` null assertion since TS can't know if we included
// the model or not
console.log(ourUser.projects![0].name);
}
}