5

Context: I'm testing a simple model into and out of the database. Not a real test, it's a precursor to some integration tests. Using Sequelize and findOne.

The problem: The direct data on the returned model instance, i.e. email.ulid, email.address, email.isVerified are undefined.

My question: Why are they undefined?

Sequelize: 5.15.0
Typescript: 3.5.3
mysql  Ver 15.1 Distrib 10.4.6-MariaDB

Record into database:

await testingDatabase.sync({
  force: true
}).then(async () => {
  await Email.create({
    ulid: the_ulid.bytes,
    address: "dave@world.com",
    isVerified: false
  })
})

Fetch the record back:

await Email.findOne({
  where: { 
    address: "dave@world.com"
  }
  , rejectOnEmpty: true //to shut typescript up
}).then( emailData => {
   console.log("Email: ", email)
})

Console.log of email instance:

    Email:  Email {                                                                                                                                                      
      dataValues: {                                                                                                                                                      
        ulid: <Buffer 01 6c a3 36 11 9c 1e 9b a6 ce 60 b7 33 3e e7 21>,                                                                                                  
        address: 'dave@world.com',                                                                                                                                    
        isVerified: false,                                                                                                                                               
        deletedAt: null,                                                                                                                                                 
        createdAt: 2019-08-18T05:32:05.000Z,                                                                                                                             
        updatedAt: 2019-08-18T05:32:05.000Z                                                                                                                              
      },                                                                                                                                                                 
      ...,
      isNewRecord: false,
      ulid: undefined,
      address: undefined,
      isVerified: undefined,
      createdAt: undefined,
      updatedAt: undefined,
      deletedAt: undefined
    }

^^^ As is clear, immediately above, all of the direct attributes on the instance are null.

The following works, but with the side-effect that isVerified is returned as a 0 rather than false and subsequently fails a direct comparison with the original data. Additionally, I lose other functionality of the model instance that will come in handy on more complex models:

Email.findOne({
  where: { address: "dave@world.com" }
, raw: true 
, rejectOnEmpty: true
})

These also work, but with the consequence that Typescript complains about the returned object not having the properties I then access (although they do exist and the test works):

.then( emailData => {
  console.log("All getters: ", emailData.get())
  // AND
  console.log("All getters(plain): ", emailData.get({plain: true}))
  // AND
  console.log("toJSON: ", emailData.toJSON())
})

This next one (suggested by Vivek), works insofar as the data is available, but JSON.parse fails to handle the Buffer properly:

const dataOnly = JSON.parse(JSON.stringify(emailData))

Others have complained (SE answer) that console.log somehow misprints the instance, yet every other way I access these properties they are still undefined.

Paul Parker
  • 1,163
  • 12
  • 23
  • I also tried `mapToModel` with `instance` set, in the hope that it would stimulate the model to populate. It did not. – Paul Parker Aug 18 '19 at 10:24
  • Watching https://github.com/sequelize/sequelize/issues/10917, although this merely addresses the side-effects of some of the workarounds found so far, not the actual question of "why are they undefined". – Paul Parker Aug 19 '19 at 01:44
  • Exact same issue [11326](https://github.com/sequelize/sequelize/issues/11326) as this question exists on sequelize github. – Paul Parker Aug 19 '19 at 05:19

3 Answers3

5

I hit the same issue but in a slightly different context.

My solution was not to use the ESNext target in my tsconfig.json. Using ES2021 or any other target solved the problem.

pihentagy
  • 5,975
  • 9
  • 39
  • 58
3

Solution:

The problem was with a Babel plugin.

Setting up Sequelize models as per the Sequelize/Typescript documentation, defines class properties on each model representing model fields.

The babel plugin:

"@babel/proposal-class-properties"

within:

plugins: [
  "@babel/proposal-class-properties"
, ...
]

Transforms these class properties into defineProperty() calls on the model Class object, called in turn by the helper _defineProperty():

  _defineProperty(this, "yourFieldName", void 0);

This initializes the property on the object with the value undefined.

Evidently, when findOne subsequently tried to update the model, it refused to overwrite these existing properties on the model.

Paul Parker
  • 1,163
  • 12
  • 23
  • 1
    Thanks for taking the time to investigate and answer your own question here to help others :) – Pedro A Aug 19 '19 at 13:23
  • Agreed, thanks for pointing me in the right direction to solving this. Configuring the plugin to exclude my models folder fixed this for me: https://stackoverflow.com/questions/61960885/how-to-make-typescript-transpile-my-sequelize-model – Andrew Keller Feb 04 '21 at 15:22
  • Sorry, but I am unable to figure out what the _solution_ is. What is the solution? – pihentagy Sep 23 '21 at 14:18
  • @pihentagy it's been a while, but I believe I removed the plugin above. – Paul Parker Sep 24 '21 at 04:37
0

Here you go, you can try out simply JSON.parse and JSON.stringify;

Email.findOne({
  where: { 
    address: "dave@world.com"
  }
}).then( emailData => {
   const dataOnly = JSON.parse(JSON.stringify(emailData));
   console.log(dataOnly); // <--- YOUR DATA
   console.log(emailData); // <--- YOUR INSTANCE
})

"Typescript complains about the returned object not having the properties I then access"

.then( emailData => {
  console.log("All getters: ", emailData.get())
  // AND
  console.log("All getters(plain): ", emailData.get({plain: true}))
  return emailData.get({plain: true}); // <-- I think you forgot to return this
});

I think that error is because you might not be returning the result back. Check the above code.

Vivek Doshi
  • 56,649
  • 12
  • 110
  • 122
  • 1
    That's a serviceable attempt, thanks. But unfortunately it falls into the growing list of not-quite-right solutions. JSON.parse doesn't handle the Buffer gracefully, instead it names the type and gives an array of decimal numbers. – Paul Parker Aug 18 '19 at 09:43
  • @PaulParker, where did you get this error : " but with the consequence that Typescript complains about the returned object not having the properties I then access" – Vivek Doshi Aug 18 '19 at 09:53
  • This is a typescript error. I can ignore it and the program still functions properly, it just clutters up my editor. I use `@babel/preset-typescript`, which discards typescript on compile, rather than trying to handle typescript errors. This allows production to continue, even in the face of an intractable Typescript problem. – Paul Parker Aug 18 '19 at 09:59
  • Check the updated answer that might solve your error and for you `emailData.get({plain: true})` is the perfect solution. @PaulParker – Vivek Doshi Aug 18 '19 at 10:07
  • I am returning it. The Typescript error arises because I try to delete fields from the data, and Typescript doesn't know of the existence of those fields. Both `.get()` and `.toJSON()` are not giving TS any type information. – Paul Parker Aug 18 '19 at 10:08
  • @PaulParker, in that case, you can ignore that error as its just affecting editors – Vivek Doshi Aug 18 '19 at 10:09