Autogenerated types are missing hasMany Relations

I have defined a model “house” and a model “room” where a house hasMany room. Forest schema update then generated the following:

    House.hasMany(models.room, {
      foreignKey: {
        name: 'houseIdKey',
        field: 'house_id'
      },
      as: 'houseRooms'
    })

When I now use the new forest agent and create the typings.ts file, in the schema for house I only see keys in the nested object that are belongsTo, hasOne, but I don’t see the relations that are hasMany. Why are these not generated?

I tried adding it manually:

import { CollectionCustomizer } from '@forestadmin/agent'
import { Schema } from './autogenerated'

export default (transactions: CollectionCustomizer<Schema, 'house'>) =>
  transactions.addOneToManyRelation('houseRooms', 'room', { foreignKey: 'house_id' })

A question that arises here seeing the code: How do I add the name (in this case houseIdKey) of the foreign key in the new model, if the old code somewhere used a custom name?

And finally, I used the types like this, but I guess it should be imported differently for the customizations and everything to work?

import { HouseRecord } from '../typings.ts

function getRooms( house: HouseRecord ): Array<Room> {
  // ....
}

So questions:

  1. How to get the hasMany relations in my generated types?
  2. How to migrate the name of the foreign keys in relations?
  3. How to properly import the types in my .ts files?

Your support is highly appreciated, thank you!

Best
Markus

Hello @marksrr ,

  1. hasMany relations are not included as a field in your records. They are related data that are fetched when needed. You cannot get them directly from the record. To get relations, you’ll need to get them from the collection object. For example from an action execute context:
      context.dataSource
        .getCollection('room')
        .list(
          { conditionTree: { field: 'house_id', operator: 'Equal', value: house.id } },
          ['room_name'],
        );
  1. use a renameField to rename any field of a record, including some foreign key:
      collection..renameField('house_id', 'houseIdKey')
  1. You’re doing it right: import { HouseRecord } from '../typings.ts'

Kind regards

Hi Enki,

thank you for your response. Regarding 1.:

Previously my code looked like this:

CLI Generated model definition

// This model was generated by Forest CLI. However, you remain in control of your models.
// Learn how here: https://docs.forestadmin.com/documentation/reference-guide/models/enrich-your-models
module.exports = (sequelize, DataTypes) => {
  const { Sequelize } = sequelize
  // This section contains the fields of your model, mapped to your table's columns.
  // Learn more here: https://docs.forestadmin.com/documentation/reference-guide/models/enrich-your-models#declaring-a-new-field-in-a-model
  const House = sequelize.define('house', {
    createdAt: {
      type: DataTypes.DATE,
      defaultValue: Sequelize.literal('now()')
    },
    updatedAt: {
      type: DataTypes.DATE,
      defaultValue: Sequelize.literal('now()')
    },
    name: {
      type: DataTypes.STRING,
      allowNull: false
    }
  }, {
    tableName: 'house',
    underscored: true,
    schema: process.env.DATABASE_SCHEMA
  })

  // This section contains the relationships for this model. See: https://docs.forestadmin.com/documentation/reference-guide/relationships#adding-relationships.
  (models) => {
    House.hasMany(models.room, {
      foreignKey: {
        name: 'houseIdKey',
        field: 'houseId'
      },
      as: 'rooms'
    })
  } = House.associate

  return House
}

All code everywhere now uses this model definition. I have APIs that are not directly related to forest, or actions that are directly related.

An API might look something like this:

  app.get('/api/r/customer/:id',
    async (request, response, next) => {
      const houseObj = await models.house.findOne({
        where: {
          id: request.params.id,
        },
        include: [
          {
            model: models.room,
            as: 'rooms',
          }
        ]
      })

      return exportProductDatasheet(houseObj)
    }
 )

The export method then renders the data of a house to a pdf.

async function exportProductDatasheet (house) {
  // ... do some stuff
  const roomCount = house.rooms?.length ?? 0 // this shows an error if i define (house: HouseRecord) since rooms does not exist anymore
  // ... do more stuff, export pdf
}

If I now want to add typescript support to the exportProductDatasheet, the type that is actually incomming is the object shown above generated by the old forest cli. All calling methods might send different rooms as they apply where filters based on the API or the action or … Also, the context.dataSource is not available in these APIs like in actions.

In order to migrate to the new agent and the respective typescript models, I will have to change every location where I call the method and restructure everything? So that means that I have to change every single query in my whole application - which would be an immense amount of work and therefore probably too expensive to the client? Or is there a way to migrate the old the definitions and keep using the logic I already have implemented, with another type than HouseRecord then?

I am curious to hear what suggested route I should take!

Thank you
Markus

All your code using sequelize models can remain exactly the same, it is independent of the agent version.
Models generated by forest admin and your sequelize models aren’t the same.
Forest admin models are only to be used inside forest admin agent customizations (actions, fields, etc).

I’m not sure to understand where is supposed to be used your exportProductDatasheet function, as well as the type of the house argument ?