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

Support transactions

maciejmyslinski opened this issue · comments

commented

Hey!

Thank you for creating this outstanding library, it really helps with testing.

One use case that I stumbled upon was the need to check if an object X exists in a database before I can create another object Y that has a relationship many to one with object X.

For example:

factory({
  reviewer: {
    id: primaryKey(Number),
    reporters: manyOf('reporter'),
  },
  reporter: {
    id: primaryKey(Number),
  }
})

Let's say I want to create a reporter for a given reviewer. First, I have to check if a reviewer exists, then I can create a reporter, then I can assign a reporter to a reviewer. Otherwise, if the reviewer would not exist, the code would fail on the last step and the reviewer would hang in the database unnecessarily.

In contract, with transacions, if it fails at any step, all operations would be rolled back.

Hey, @maciejmyslinski. Thank you for the kind feedback!
This is a great suggestion. Let's collaborate on it together.

With the current API, any relation flow is parent -> child, so in your scenario "create a reporter for a given reviewer" implies you're operating with an existing reviewer object:

const john = db.reviewer.create({
  reporters: [db.reporter.create({ firstName: 'Mary' })]
})

Even if you split this operation in steps and create a reporter first, you still need a physically existing reviewer object to assign its reporters property to a new value:

const mary = db.reporter.create({ firstName: 'Mary' })

const john = db.reviewer.create({ 
  reporters: [mary]
})

// or
john.update({
  reporters: [mary]
})

I think I fail to see a scenario when you could assign a reporter to a potentially non-existing reviewer. Could you please share with us your usage of the library that showcases this problematic scenario?

commented

In my use case, I have a createReporter endpoint that takes reporter name and reviewer id as an input.
Now, reviewer with a given id might not exist - we can't trust user input. So we either:

  • check if reviewer exists
  • create a reporter
  • update reviewer

With transactions we could skip the first step as the whole transaction would fail if reviewer does not exist.

You can describe this transaction using the lazy value getters during the reviewer update:

function createReporter(reviewerId, reporterName) {
  db.reviewer.update({
    which: {
      id: { equals: reviewerId }
    },
    data: {
      reporters(reporters) {
        const newReporter = db.reporter.create({
          name: reporterName
        })
        return reporters.concat([newReporter])
      }
    }
  })
}

Note that the data.reporters value is a function that produces the next value only when the entity (reviewer) exists.

This works because .update() method queries the database for the given entity first (reviewer in your case):

data/src/factory.ts

Lines 138 to 154 in 3016d6d

update({ strict, ...query }) {
const results = executeQuery(modelName, primaryKey, query, db)
const prevRecord = first(results)
if (!prevRecord) {
invariant(
strict,
`Failed to execute "update" on the "${modelName}" model: no entity found matching the query "${JSON.stringify(
query.where,
)}".`,
new OperationError(OperationErrorType.EntityNotFound),
)
return null
}
const nextRecord = updateEntity(prevRecord, query.data)

and updates it only when it exists. Since the lazy value getters are executed during the update phase, you are certain that no reporter is created if the queried reviewer doesn't exist.

acc[key] =
typeof value === 'function' ? value(entity[key], entity) : value

By default, the .update() method will do nothing if the entity cannot be queried. If you wish to raise an exception in this case, use the strict mode with the .update() method:

db.reviewer.update({
  which: { ... },
  data: { ... },
  strict: true
})

Using the strict method will throw an exception when querying a non-existing entity.

Would this cover your use case? If not, please provide additional context so I could understand your requirements better.