0

I'm working on a Zod schema for nodes where each node got a unique ID, unique type and an array of child nodes. Because of that I created a helper function acting as a base schema for nodes

function createNodeSchema<const NodeType extends string>(nodeType: NodeType) {
  return z.object({
    // ... base fields for nodes ...
    id: z.string(),
    type: z.literal(nodeType),
    // due to circular imports we can't use the discriminated union directly and have to type it manually
    get children(): z.ZodArray<
      z.ZodDiscriminatedUnion<[typeof childBarNodeSchema]>
    > {
      return z.array(z.discriminatedUnion('type', [childBarNodeSchema]));
    },
  });
}

Assuming there is a root node schema

const leadingFooNodeSchema = createNodeSchema('leadingFoo').extend({
  // ...fields for this node ...
  foo: z.string(),
});

and a child node schema

const childBarNodeSchema = createNodeSchema('followingBar').extend({
  // ...fields for this node ...
  bar: z.string(),
});

the whole tree will be bundled into a root schema

const rootNodeBaseSchema = z.discriminatedUnion('type', [
  leadingFooNodeSchema,
  // ...other leading nodes...
]);

const rootNodeSchema = rootNodeBaseSchema.refine(haveNodesUniqueIDs, {
  error: 'Nodes must have unique IDs',
});

The validation function haveNodesUniqueIDs checks if there are duplicate IDs in the tree

// I am really not sure about that one...
type RecursivePick<T, K extends keyof T> = {
  [P in Extract<keyof T, K>]: P extends 'children'
    ? T[P] extends Array<infer U>
      ? RecursivePick<U, Extract<keyof U, K>>[]
      : never
    : T[P];
};

// try to extract only "id" and "children" from the whole tree because we don't care for other fields
type NodeSchemaWithIDAndChildren = RecursivePick<
  z.infer<typeof rootNodeSchema>,
  'id' | 'children'
>;

function haveNodesUniqueIDs(leadingNode: NodeSchemaWithIDAndChildren) {
  // ... implementation goes here...
}

Everything is looking good so far. But when it comes to testing

describe('haveNodesUniqueIDs', () => {
  it('returns true ...', () => {
    expect(
      haveNodesUniqueIDs({
        id: 'a',
        children: [],
      })
    ).toBeTruthy();
  });
});

the testrunner fails with the following error

ReferenceError: Cannot access 'vite_ssr_import_1' before initialization

It's pointing at the createNodeSchema => children so maybe my schema is not correct yet.

I created a playground for that => https://stackblitz.com/edit/vitejs-vite-ue55oieh?file=test%2FhaveNodesUniqueIDs.test.ts&view=editor

Do you have any ideas how to fix this?

2
  • From what I saw and was able to work out it seems this error arises because your Zod schemas reference each other recursively at module initialisation time, creating a circular dependency that JavaScript cannot resolve. Commented Jul 23 at 9:29
  • Your createNodeSchema function's children getter references childBarNodeSchema, but childBarNodeSchema itself is created by calling createNodeSchema. This circular reference means when the module loads, the code tries to use childBarNodeSchema before it's fully initialised, causing the ReferenceError Commented Jul 23 at 9:30

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.