How can I use a model as a type?
I'm trying to do:
let newEvent: Event = {
id: (uuidv4() as unknown) as Guid,
streamId: msg.streamId,
data: msg.data,
// response: undefined,
// responseCode: undefined
}
with Event being:
@Table
export class Event extends Model<Event> {
@Column({ allowNull: false, primaryKey: true, type: DataType.UUID })
id: Guid;
@Column({ allowNull: false })
data: string;
@Column
responseCode?: number;
@Column
response?: number;
@ForeignKey(() => Stream)
@Column({ allowNull: false })
streamId: string;
}
but it says:
Type '{ id: Guid; streamId: any; data: any; }' is missing the following properties from type 'Event': $add, $set, $get, $count, and 28 more
You should create a new object using the .create({}) function; not instantiate it like that. Assuming Stream is also an object, you probably want to add that with the .$add({}) magic function, and not add an id in the .create() function.
But I’ll be adding to the object conditionally based on certain criteria
Then either define a type in that class as an intermediary type, use an any type to build the object and then save it (after saving you have the typings again), or re-think your code logic to fit this style of working with the objects.
In my model modules I'll export four items: the model, a "create" interface, an "update" interface, and sometimes a "read" interface. This is basically what I believe @MagicLegend meant by using an intermediary type, but a bit more extensive.
Example:
export interface UserRead {
email: string;
firstName: string;
lastName: string;
}
@Table
export class User extends Model<User> implements UserRead {
@AllowNull(false)
@PrimaryKey
@IsUUID(4)
@Column
userId!: string;
@AllowNull(false)
@Column
email!: string;
@AllowNull(false)
@Column
firstName!: string;
@AllowNull(false)
@Column
lastName!: string;
}
export interface UserCreate {
email: string;
firstName: string;
lastName: string;
userId: string;
}
export interface UserUpdate {
email?: string;
firstName?: string;
lastName?: string;
}
The class and each interface are carefully tuned to the needs of the model: sometimes fields exist in one or more of them that don't exist in the others. Often fields are required in some that are optional in others. It's all dependent on how I, as the database interface designer, need to provide restrictions the application should abide by to make sure the database is being used correctly - and thus reduce the amount of bugs I and my co-workers can easily create.
The "Read" interface I've only used for one task thus far: when I'm combining a subset of the data from the DB to some other object. So I currently only create it on a case-by-case basis.
I use the "Create" and "Update" interfaces thusly:
const userCreate: UserCreate = {
email,
firstName,
lastName,
userId,
};
await User.create(userCreate);
//...
const userUpdate: UserUpdate = {};
//... logic
userUpdate.firstName = "joe"; // Merely a fake example of course.
//... more logic
await User.update(userUpdate, { where: { userId } });
One of the rules I use is that the "Create" interface should have the same semantics as the create call would need: aka fields that cannot be null in the database means that the "Create" interface should use non-optional fields. This does mean that I often need some additional variables or advanced logic when creating the data:
const userId = uuid.v4();
const email = request?.email?.trim();
if (!email) { // All falsy values are bad.
throw new Error("...");
}
const userCreate: UserCreate = {
email,
firstName: request?.firstName?.trim() ?? "Jo",
lastName: request?.lastName?.trim() ?? "", // Example, not actually recommended.
userId,
};
await User.create(userCreate);
// You probably should have all this in a transaction and detect if the create failed.
I think you might be looking for build(). It will give you a new, unpersisted instance that you can then set attributes on:
const newEvent = Event.build({
id: (uuidv4() as unknown) as Guid,
streamId: msg.streamId,
data: msg.data,
// response: undefined,
// responseCode: undefined
});
if (whatever) {
newEvent.response = "something";
}
I also recently discovered the Partial, Omit, Pick, and related generic types.
So a direct solution for the OP could also be:
// Makes all keys optional.
const newEvent: Partial<Event> = {
id: (uuidv4() as unknown) as Guid,
streamId: msg.streamId,
data: msg.data,
// response: undefined,
// responseCode: undefined
}
Or
// Selects only the keys specified.
const newEvent: Pick<Event, "id" | "streamId" | "data" | "response" | "responseCode"> = {
id: (uuidv4() as unknown) as Guid,
streamId: msg.streamId,
data: msg.data,
// response: undefined,
// responseCode: undefined
}
However I still use the structure I posted before, just with some modifications: there's now a User_Base interface that all the other entries either extend or implement either directly or by modification via the above generics.
Any solution?
I'm using like this
import { Notifications } from 'src/modules/notifications/models/notifications.model';
import { InferAttributes } from 'sequelize';
const baseNotification: InferAttributes<Notifications> = {...}
"sequelize": "6.37.3"