mswjs / data

Data modeling and relation library for testing JavaScript applications.

Home Page:https://npm.im/@mswjs/data

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Handle Undefined Object in `isInternalEntity`

JoseRFelix opened this issue · comments

Problem

When adding a non-entity in a relation property, the following error occurs:

  TypeError: Cannot use 'in' operator to search for '__type' in null

This also breaks dropping a db.

Expected Outcome

The code should not throw because of a null object, but error or warn the user about him using a normal object instead of an entity for a relation.

Hy, @JoseRFelix. Thanks for raising this.

This is a valid concern and I agree that the library should warn/error when attempting to assign a plain compatible object as the relational property. This looks like a partial follow-up of #93.

Have just stumbled upon this issue. Do I understand correctly that the following thing is not possible at the moment:

const db = factory({
  project: {
    id: primaryKey(String),
    name: String,
    owner: oneOf('user'),
  },
  user: {
    username: primaryKey(String),
    name: String,
  },
})

[
  { username: 'username1', name: 'Jack' },
  { username: 'username2', name: 'John' }
].forEach(user => {
  db.user.create(user)
})

[
  { id: '1', name: 'Project 1', owner: 'username1' },
  { id: '2', name: 'Project 2', owner: 'username2' }
].forEach(project => {
  const owner = db.user.findFirst({ where: { username: { equals: project.owner } } })
  db.project.create({ ...project, owner })
})

because findFirst method returns plain object and not an entity, right?

Interesting, that when I run this code i get this TypeError: Cannot use 'in' operator to search for '__type' in null not every time, but once in a while.

Hm... seems like even you do it like in docs

const db = factory({
  project: {
    id: primaryKey(String),
    name: String,
    owner: oneOf('user'),
  },
  user: {
    username: primaryKey(String),
    name: String,
  },
})

const user = db.user.create({ username: 'username1', name: 'Jack' })
const project = db.project.create({ id: '1', name: 'Project 1', owner: user })

You can still get this error once in a while

@smashercosmo, if I'm not mistaken, assigning plain objects as relational values isn't supported at the moment. It doesn't error, but won't be treated as a node reference, as opposed to when you assign the result of db[model].create(). This is raised in #92 as well, where updating a relational property with a plain object doesn't update the references. I didn't have time to get to this, so I'd appreciate any thoughts/brainstorming on how to make this happen and will support the effort as much as I can given the time constraints.

Ok, but that's another issue, I guess. Currently there is an error raised occasionally even if you assign a result of db[model].create(). Seems like there is some racing condition going on. Does db[model].create() happens synchronously?

Currently, if I do this

const user = db.user.create({ username: 'username1', name: 'Jack' })
const project = db.project.create({ id: '1', name: 'Project 1', owner: user })

then once in 3-5 refreshes this error TypeError: Cannot use 'in' operator to search for '__type' in null is being raised

Well, probably I'll just try to provide minimal reproduction.

Ok, I figured out what my issue was about. You get this error if you try to assign null to any of the model's fields. Don't know, if it's a bug or by design. But can't think of any reason why null can't be a valid value.

I've opened a pull request that'd ensure the value passed to isInternalEntity is always a defined object (#117). Passing anything else is unexpected and shouldn't happen. Since the values of the models are defined by the end-user, the function may still get an arbitrary value that violates its call signature.

@smashercosmo, using null as the model property value is not correct. All model properties are nullable and don't have any value by default. There's no reason to use explicit null. Instead, you should use a getter function that either resolves to an initial value ({ name: () => 'John' }), or indicates the data type that can be inferred { name: String }.

@kettanaito I'm not sure that model properties are nullable. At least typescript doesn't know about this.

const db = factory({
  entity: {
    id: primaryKey(datatype.uuid),
    name: String,
  }
})
  
const instance = db.entity.create({ name: null }) // <- TS2322: Type 'null' is not assignable to type 'Partial   >> | undefined'.

@smashercosmo, I think at the moment you can set undefined instead of null. I'm open to discussion to support Vaule | null instead.