Skip to content

Creating record with deeper nested relation fails schema validation #348

Closed

Description

@bmg-sander

The following code fails to create a record:

  const contactSchema = z.object({ email: z.string() })
  const userSchema = z.object({ id: z.number(), contact: contactSchema })
  const postSchema = z.object({
    title: z.string(),
    author: userSchema,
  })

  const contacts = new Collection({ schema: contactSchema })
  const users = new Collection({ schema: userSchema })
  const posts = new Collection({ schema: postSchema })

  users.defineRelations(({ one }) => ({
    contact: one(contacts),
  }))
  posts.defineRelations(({ one }) => ({
    author: one(users),
  }))

  const contact = await contacts.create({ email: 'test@example.com' })
  const user = await users.create({ id: 1, contact })
  const post = await posts.create({ title: 'First', author: user })

I receive the following validation error from this code:

[
  {
    expected: 'object',
    code: 'invalid_type',
    path: [ 'author', 'contact' ],
    message: 'Invalid input: expected object, received undefined'
  }
]

Is this expected or am I doing something wrong?

Activity

  1. kettanaito commented on Nov 21, 2025

    @kettanaito
    Member

    Hi, @bmg-sander. Thanks for reporting this.

    This is a bug caused by our sanitization logic put in place to prevent infinitely self-referencing schemas as no schema libraries are capable of creating object literals from those schemas (see #318).

    In your case, that sanitization logic interferes with the create method although there are no self-referencing schemas here. It is caused by this line:

    return [key, relation.getDefaultValue()]

    What it does is sets any nested record references from initialValues to their default values based on the relation type: undefined for one relations, [] for many. That is precisely so when your schema library creates these objects it won't be stuck in a potential infinite loop.

    The thing is, I'm not sure what's the best direction in fixing this. We should not have that sanitization logic, to begin with (see #342). Schema libraries should support creating self-referencing objects as you can both describe such objects in them and create them in plain JavaScript (via getters). I've opened a pull request to Zod to support this use case: colinhacks/zod#5347.

    The record creation is self-contained, I don't see how we would implement sanitization to affect only self-referencing records. But I am open to ideas!

  2. kettanaito commented on Nov 21, 2025

    @kettanaito
    Member

    It's worth mentioning that you can circumvent this by treating user.contact as potentially undefined in your schema:

    const userSchema = z.object({ id: z.number(), contact: contactSchema.optional() })

    This isn't nice and I don't want for the library to dictate how you should describe your data, but given the current technical limitation, this might be a solution for you to consider.

    A bigger problem here is that there's no way to tell your schema library to ignore certain object leaves. That's what I added in the Zod's pull request above. It keeps track of visited leaves and ignores their validation if they have already been validated. That allows validating infinitely-referencing objects, making our sanitization obsolete, and, tangentially, fixing this issue, too.

  3. kettanaito commented on Nov 21, 2025

    @kettanaito
    Member

    I'm putting this use case into an automated test in #349.

  4. kettanaito commented on Mar 24, 2026

    @kettanaito
    Member

    Fixed by #349

  5. bmg-sander commented on Apr 15, 2026

    @bmg-sander
    Author

    @kettanaito I would like to try out the fix, but it seems the github release workflow is broken. Did you have a look at this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions