Anyone worked with sequelize-typescript or similar?

I have an existing medium-sized project on ForestAdmin across more than one database.

I’d really like to avoid defining both static interfaces and runtime schema using Sequelize with the suggested approach on the Woodshop.

I’m looking at projects like GitHub - RobinBuschmann/sequelize-typescript: Decorators and some other features for sequelize, which would allow us to define our interfaces and schema at once.

Has anyone worked with such a project and ForestAdmin?

Hi @kll and welcome in our community :champagne: !

Sorry but I did not understood what you meant :sweat_smile:
What are you talking about when you speak about static interfaces ?
Same question for runtime schema ?

Hey @vince !

Using the example from the Woodshop, here’s the static interface:

import { Model } from "sequelize";

export default class Users extends Model {
    public id!: number;
    public firstname!: string;
    public lastname!: string;
    public readonly createdAt: Date;
    public readonly updatedAt: Date;
}

And here’s the runtime schema that is actually used:

import Users from '../interfaces/users';

export default (sequelize, DataTypes) => {
  Users.init(
    {
      id: {
        type: DataTypes.INTEGER.UNSIGNED,
        autoIncrement: true,
        primaryKey: true
      },
      firstname: {
        type: new DataTypes.STRING(128),
        allowNull: false
      },
      lastname: {
        type: new DataTypes.STRING(128),
        allowNull: false
      }
    },{
        tableName: "users",
        modelName: 'users',
        sequelize,
    }
  );

  return Users;
}

I’ve had to define twice every property and every type.
I’m unsure if the type field of every model is safely typed of if I can define one of the interface property as number and the runtime property as new DataTypes.STRING(128).

Using a project such as the one linked in OP would allow to bypass this duality (example from their README):

@Table
class Person extends Model {

  @Column
  name: string;

  @Column
  birthday: Date;

  @HasMany(() => Hobby)
  hobbies: Hobby[];
}

PS: Thanks for the welcome =)

1 Like

Oh okey !
Well we did not tried it as far as I know, but you can try to use it, I don’t think you’ll have issue with ForestAdmin using this library :wink:

1 Like

For native CRUD, Forest Admin will still use plain ‘old’ sequelize queries under the hood even if your models are declared using annotations for type safe attributes.

The OP reads

Build and create

Instantiation and inserts can be achieved in the good old sequelize way

const person = Person.build({name: ‘bob’, age: 99}); person.save(); Person.create({name: ‘bob’, age: 99});

and

Find and update

Finding and updating entries does also work like using native sequelize. So see sequelize docs for more details.

Person .findOne() .then(person => { person.age = 100; return person.save(); }); Person .update({ name: ‘bobby’ }, {where: {id: 1}}) .then(() => { });

It looks like you should be on the clear but I don’t know if things won’t break at some point for you :slight_smile:

Let us know how it goes if you try it!

1 Like

So, we indeed tried this. It’s in production now =)

Here is what our models look like:

@Table({ tableName: 'ChatMessages', schema: process.env.DATABASE_SCHEMA })
export default class ChatMessage extends Model {
  @PrimaryKey
  @Column(DataType.UUID)
  Id: string

  @CreatedAt
  CreatedAt: Date

  @UpdatedAt
  UpdatedAt: Date

  @Column
  Body: string

  @BelongsTo(() => User, 'UserId')
  Author: User

  @BelongsTo(() => Chatroom, 'ChatroomId')
  Chatroom: Chatroom
}

This makes creating, updating and working with the models much, much easier now.

The main drawback is that many API endpoints for forest like record getters and smart field definitions still default to any for types, without taking into account the actual model used.

For example:

import { collection } from 'forest-express-sequelize'

collection('User', { // magic string so we're not starting well there for any typing
  actions: [],
  fields: [
    {
      field: 'FullName',
      type: 'String',
      get: (user) => { // yep, user is any
        return user.FirstName + ' ' + user.LastName // no type check
      }
    }
  ],
  segments: []
})

Another from your TypeScript definitions:

export class RecordCreator<M extends Sequelize.Model> extends AbstractRecordTool<M> {
  // ouch. Not using _creationAttributes means
  // we don't get any help when creating a record
  create(record: Record<string, unknown>): Promise<M>;
}

Of course we can still type by force:

collection('User', { // magic string
  actions: [],
  fields: [
    {
      field: 'FullName',
      type: 'String',
      get: (user: User) => { // user is a User
        return user.FirstName + ' ' + user.LastName // type checked!
      }
    },
// ...

Which makes me think we could go even further, like so:

collection(User, { // BAM, pass the model constructor!
  actions: [],
  fields: [
    {
      field: 'FullName',
      type: 'String',
      get: (user) => { // user is instance of the model
        return user.FirstName + ' ' + user.LastName // type checks for everyone
      }
    },
// ...

But it breaks native Forest project scaffolds by asking of developers that they export their models instead of blob-importing all of them.

We’ve actually been exporting all our models since switching the project to TypeScript and typescript-sequelize, so we’ve been tempted to write typed wrappers around Forest APIs…

Is there anything like this in your roadmap?