data icon indicating copy to clipboard operation
data copied to clipboard

Persist extention hydration

Open noveogroup-amorgunov opened this issue 2 months ago • 0 comments

Hi! First of all, thank you for releasing the new version of the library. I have two questions about how to properly use the persist extension.

Schema example
import { Collection } from '@msw/data'
import { persist } from '@msw/data/extensions/persist'
import { sync } from '@msw/data/extensions/sync'
import { z } from 'zod'

const userSchema = z.object({
  id: z.number(),
  email: z.email(),
  password: z.string(),
})

const wishlistSchema = z.object({
  id: z.number(),
  get user() {
    return userSchema
  },
  productIds: z.array(z.number()),
})

export async function createDb() {
  const users = new Collection({
    schema: userSchema,
    extensions: [
      persist(),
      sync(),
    ],
  })
  
  const wishlists = new Collection({
    schema: wishlistSchema,
    extensions: [persist(), sync()],
  })
  
  wishlists.defineRelations(({ one }) => ({
    user: one(users),
  }))

  return {
    users,
    wishlists,
  }
}

////

createDb().then(
  db => startDatabaseMigration(db)
)

 1 – Asynchronous hydration

If I call a function to seed fixtures from the global scope, queries against the collections always return empty results, since the data is hydrated asynchronously.

export async function startDatabaseMigration() {
  const users = db.users.findMany(q => q.where({ id: value => Boolean(value) }))

  // users is always an empty array

To work around this, I can add a small delay — but this isn’t obvious from the docs and feels a bit hacky (it might also break logic that expects the collections to be ready synchronously):

export async function startDatabaseMigration() {
+  await new Promise(resolve => setTimeout(resolve, 10))

  const users = db.users.findMany(q => q.where({ id: value => Boolean(value) }))

  // users is correctly restored from localStorage

Is this the correct approach to handle the issue, or am I misusing the extension?

2 – Hydrating related entities

When I am trying to hydrate a related entity, I get an error saying that the user entity doesn’t exist yet at the time of hydration:

collection-BJduKbwC-….js?v=aa5ed28a:2330 Uncaught (in promise) OperationError$1: Failed to create a new record with initial values: does not match the schema. Please see the schema validation errors above.
    at async Promise.all (:5174/index 0)
await in create		
(anonymous)	@	db.ts:28

With errors like this:

Image

What I’ve discovered is that when closing the browser tab, the user inside wishlists record is not saved. So during the next hydration, the errors above occur:

Image

You can see in the screenshot that there’s no user field.

Here’s how I create the entities:

  const user = await db.users.create({ ...userData, id: generateId('user') })
  await wait(10)

  await db.wishlists.create({
    id: generateId('wishlist'),
    user,
    productIds: [3, 4, 5, 6, 7],
  })

On the first run (without reloading the page), everything works correctly:

Image

Is this a bug, or am I doing something wrong?

If needed, I can create a CodePen reproduction for both issues.

noveogroup-amorgunov avatar Oct 24 '25 17:10 noveogroup-amorgunov