npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

Iโ€™ve always been into building performant and accessible sites, but lately Iโ€™ve been taking it extremely seriously. So much so that Iโ€™ve been building a tool to help me optimize and monitor the sites that I build to make sure that Iโ€™m making an attempt to offer the best experience to those who visit them. If youโ€™re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, ๐Ÿ‘‹, Iโ€™m Ryan Hefnerย  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If youโ€™re interested in other things Iโ€™m working on, follow me on Twitter or check out the open source projects Iโ€™ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soonโ€“ish.

Open Software & Tools

This site wouldnโ€™t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you ๐Ÿ™

ยฉ 2026 โ€“ย Pkg Stats / Ryan Hefner

@traversable/zod

v0.0.57

Published

<br> <h1 align="center">แฏ“๐˜๐—ฟ๐—ฎ๐˜ƒ๐—ฒ๐—ฟ๐˜€๐—ฎ๐—ฏ๐—น๐—ฒ/๐˜‡๐—ผ๐—ฑ</h1> <br>

Downloads

25,230

Readme

Requirements

@traversable/zod has a peer dependency on Zod (v4).

What's it all about?

Read the blog post, Introducing: @traversable/zod (3 min read).

Getting started

$ pnpm add @traversable/zod zod

Here's an example of importing the library:

import { z } from 'zod'
import { zx } from '@traversable/zod'

// see below for specific examples

Table of contents

Fuzz-tested, production ready

Experimental

Advanced

Features

zx.check

zx.check converts a zod-schema into a super-performant type-guard.

Notes

  • Better performance than z.parse and z.safeParse
  • Works in any environment that supports defining functions using the Function constructor, including (as of May 2025) Cloudflare workers ๐ŸŽ‰

Performance comparison

Here's a Bolt sandbox if you'd like to run the benchmarks yourself.

z.parse and z.safeParse clone the object they're parsing, and return an array of issues if any are encountered.

Those features are useful in certain contexts.

But in contexts where all you need is to know whether a value is valid or not, it'd be nice to have a faster alternative, that doesn't allocate.

zx.check takes a zod schema, and returns a type guard. It's performance is an order of magnitude faster than z.parse and z.safeParse in almost every case.

                     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                     โ”‚        Average  โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  z.parse (v4)      โ”‚  20.41x faster  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  z.safeParse (v4)  โ”‚  21.05x faster  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const Address = z.object({
  street1: z.string(),
  street2: z.optional(z.string()),
  city: z.string(),
})

const addressCheck = zx.check(Address)

addressCheck({ street1: '221B Baker St', city: 'London' }) // => true
addressCheck({ street1: '221B Baker St' })                 // => false

See also

zx.check.writeable

zx.check converts a zod-schema into a super-performant type-guard.

Compared to zx.check, zx.check.writeable returns the check function in stringified ("writeable") form.

Notes

  • Useful when you're consuming a set of zod schemas and writing them all to disc
  • Also useful for testing purposes or for troubleshooting, since it gives you a way to "see" exactly what the check functions check
  • Since you're presumably writing to disc a build-time, works with Cloudflare workers

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const addressCheck = zx.check.writeable(
  z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
  }),
  { typeName: 'Address' }
)

console.log(addressCheck) 
// =>
// type Address = { street1: string; street2?: string; city: string; }
// function check(value: Address) {
//   return (
//     !!value &&
//     typeof value === "object" &&
//     typeof value.street1 === "string" &&
//     (!Object.hasOwn(value, "street2") || typeof value?.street2 === "string") &&
//     typeof value.city === "string"
//   );
// }

See also

zx.deepClone

zx.deepClone lets users derive a specialized "deep copy" function that works with values that have been already validated.

Because the values have already been validated, clone times are significantly faster than alternatives like window.structuredClone and Lodash.cloneDeep.

Performance comparison

Here's a Bolt sandbox if you'd like to run the benchmarks yourself.

                           โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                           โ”‚         (avg)   โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Lodash.cloneDeep        โ”‚  30.64x faster  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  window.structuredClone  โ”‚  50.26x faster  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

This article goes into more detail about what makes zx.deepClone so fast.

Example

import { assert } from 'vitest'
import { z } from 'zod'
import { zx } from '@traversable/zod'

const Address = z.object({
  street1: z.string(),
  street2: z.optional(z.string()),
  city: z.string(),
})

const clone = zx.deepClone(Address)

const sherlock = { street1: '221 Baker St', street2: '#B', city: 'London' }
const harry = { street1: '4 Privet Dr', city: 'Little Whinging' }

const sherlockCloned = clone(sherlock)
const harryCloned = clone(harry)

// values are deeply equal:
assert.deepEqual(sherlockCloned, sherlock) // โœ…
assert.deepEqual(harryCloned, harry)       // โœ…

// values are fresh copies:
assert.notEqual(sherlockCloned, sherlock)  // โœ…
assert.notEqual(harryCloned, harry)        // โœ…

See also

zx.deepClone.writeable

zx.deepClone lets users derive a specialized "deep clone" function that works with values that have been already validated.

Compared to zx.deepClone, zx.deepClone.writeable returns the clone function in stringified ("writeable") form.

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const Address = z.object({
  street1: z.string(),
  street2: z.optional(z.string()),
  city: z.string(),
})

const deepClone = zx.deepClone.writeable(
  z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
  }), 
  { typeName: 'Address' }
)

console.log(deepClone) 
// =>
// type Address = { street1: string; street2?: string; city: string; }
// function deepClone(prev: Address) {
//   return {
//     street1: prev.street1,
//     ...prev.street2 !== undefined && { street2: prev.street2 },
//     city: prev.city
//   }
// }

See also

zx.deepEqual

zx.deepEqual lets users derive a specialized "deep equal" function that works with values that have been already validated.

Because the values have already been validated, comparison times are significantly faster than alternatives like NodeJS.isDeepStrictEqual and Lodash.isEqual.

Performance comparison

Here's a Bolt sandbox if you'd like to run the benchmarks yourself.

                             โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                             โ”‚   Array (avg)  โ”‚  Object (avg)  โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  NodeJS.isDeepStrictEqual  โ”‚  40.3x faster  โ”‚  56.5x faster  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Lodash.isEqual            โ”‚  53.7x faster  โ”‚  60.1x faster  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

This article goes into more detail about what makes zx.deepEqual so fast.

Notes

  • Works in any environment that supports defining functions using the Function constructor, including (as of May 2025) Cloudflare workers ๐ŸŽ‰

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const deepEqual = zx.deepEqual(
  z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
  })
)

deepEqual(
  { street1: '221B Baker St', city: 'London' },
  { street1: '221B Baker St', city: 'London' }
) // => true

deepEqual(
  { street1: '221B Baker St', city: 'London' },
  { street1: '4 Privet Dr', city: 'Little Whinging' }
) // => false

See also

zx.deepEqual.writeable

Notes

  • Useful when you're consuming a set of zod schemas and writing them all to disc
  • Also useful for testing purposes or for troubleshooting, since it gives you a way to "see" exactly what the deep equal functions are doing

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const deepEqual = zx.deepEqual.writeable(
  z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
  }),
  { typeName: 'Address' }
)

console.log(deepEqual)
// =>
// type Address = { street1: string; street2?: string; city: string; }
// function deepEqual(x: Address, y: Address) {
//   if (x === y) return true;
//   if (x.street1 !== y.street1) return false;
//   if (x.street2 !== y.street2) return false;
//   if (x.city !== y.city) return false;
//   return true;
// }

See also

zx.deepEqual.classic

Notes

  • This option is provided as a fallback in case users cannot work with either #1 or #2

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'
import * as vi from 'vitest'

const deepEqual = zx.deepEqual.classic(
  z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
  })
)

deepEqual(
  { street1: '221B Baker St', city: 'London' },
  { street1: '221B Baker St', city: 'London' },
) // => true

deepEqual(
  { street1: '221B Baker St', city: 'London' },
  { street1: '4 Privet Dr', city: 'Little Whinging' },
) // => false

See also

zx.convertCaseCodec

Convert a Zod schema into a codec that applies a bi-directional key transformation to all object schemas recursively.

Example

[!NOTE] You can play with this example on StackBlitz

import * as z from 'zod'
import { zx } from '@traversable/zod'

const createAllCapsCodec = zx.convertCaseCodec({
  decodeKeys: (k) => k.toUpperCase(),
  encodeKeys: (k) => k.toLowerCase(),
})

const ALL_CAPS = createAllCapsCodec(
  z.object({
    abc: z.string(),
    def: z.object({
      ghi: z.array(
        z.object({
          jkl: z.boolean()
        })
      )
    })
  })
)

console.log(
  ALL_CAPS.decode({
    abc: 'hi how are you',
    def: {
      ghi: [
        { jkl: false },
        { jkl: true },
      ]
    }
  })
) 
/* {
  ABC: "hi how are you",
  DEF: {
    GHI: [
      { "JKL": false },
      { "JKL": true }
    ]
  }
} */

console.log(
  ALL_CAPS.encode({
    ABC: "hi how are you",
    DEF: {
      GHI: [
        { "JKL": false },
        { "JKL": true }
      ]
    }
  })
)
/* {
  abc: 'hi how are you',
  def: {
    ghi: [
      { jkl: false },
      { jkl: true },
    ]
  }
} */

See also

zx.deepCamelCaseCodec

[!WARNING] Support for this feature is experimental (๐Ÿ”ฌ).

Convert a Zod schema into a codec that decodes any objects's keys to camel case and encode any object's keys to snake case, recursively.

[!NOTE] This feature was implemented in terms of zx.convertCaseCodec.

Example

[!NOTE] You can play with this example on StackBlitz

import * as z from 'zod'
import { zx } from '@traversable/zod'

const CAMEL = zx.deepCamelCaseCodec(
  z.object({
    abc_def: z.string(),
    ghi_jkl: z.object({
      mno_pqr: z.number(),
      stu_vwx: z.array(
        z.object({
          y_z: z.boolean()
        })
      )
    })
  })
)

console.log(
  CAMEL.decode({
    abc_def: 'hi how are you',
    ghi_jkl: {
      mno_pqr: 123,
      stu_vwx: [
        {
          y_z: true
        },
        {
          y_z: false
        }
      ]
    }
  })
)
/* {
  abcDef: "hi how are you",
  ghiJkl: {
    mnoPqr: 123,
    stuVwx: [
      {
        yZ: true,
      },
      {
        yZ: false,
      },
    ],
  },
} */

console.log(
  CAMEL.encode({
    abcDef: "hi how are you",
    ghiJkl: {
      mnoPqr: 123,
      stuVwx: [
        {
          yZ: true,
        },
        {
          yZ: false,
        },
      ],
    },
  })
)
/* {
  abc_def: 'hi how are you',
  ghi_jkl: {
    mno_pqr: 123,
    stu_vwx: [
      {
        y_z: true
      },
      {
        y_z: false
      }
    ]
  }
} */

See also

zx.deepSnakeCaseCodec

[!WARNING] Support for this feature is experimental (๐Ÿ”ฌ).

Convert a Zod schema into a codec that decodes any objects's keys to snake case and encode any object's keys to camel case, recursively.

[!NOTE] This feature was implemented in terms of zx.convertCaseCodec.

Example

[!NOTE] You can play with this example on StackBlitz

import * as z from 'zod'
import { zx } from '@traversable/zod'

const SNAKE = zx.deepSnakeCaseCodec(
  z.object({
    abc_def: z.string(),
    ghi_jkl: z.object({
      mno_pqr: z.number(),
      stu_vwx: z.array(
        z.object({
          y_z: z.boolean()
        })
      )
    })
  })
)

console.log(
  SNAKE.decode({
    abcDef: "hi how are you",
    ghiJkl: {
      mnoPqr: 123,
      stuVwx: [
        {
          yZ: true,
        },
        {
          yZ: false,
        },
      ],
    },
  })
)
/* {
  abc_def: 'hi how are you',
  ghi_jkl: {
    mno_pqr: 123,
    stu_vwx: [
      {
        y_z: true
      },
      {
        y_z: false
      }
    ]
  }
} */

console.log(
  SNAKE.encode({
    abc_def: 'hi how are you',
    ghi_jkl: {
      mno_pqr: 123,
      stu_vwx: [
        {
          y_z: true
        },
        {
          y_z: false
        }
      ]
    }
  })
)
/* {
  abcDef: "hi how are you",
  ghiJkl: {
    mnoPqr: 123,
    stuVwx: [
      {
        yZ: true,
      },
      {
        yZ: false,
      },
    ],
  },
} */

See also

zx.fromConstant

Convert a blob of JSON data into a zod schema that represents its least upper bound.

Example

import { zx } from '@traversable/zod'

let example = zx.fromConstant({ abc: 'ABC', def: [1, 2, 3] })
//  ^? let example: z.ZodType<{ abc: 'ABC', def: [1, 2, 3] }>

console.log(zx.toString(example))
// => z.object({ abc: z.literal("ABC"), def: z.tuple([ z.literal(1), z.literal(2), z.literal(3) ]) })

See also

zx.fromConstant.writeable

Convert a blob of JSON data into a stringified zod schema that represents its least upper bound.

Example

import { zx } from '@traversable/zod'

let ex_01 = zx.fromConstant.writeable({ abc: 'ABC', def: [1, 2, 3] })

console.log(ex_01)
// => z.object({ abc: z.literal("ABC"), def: z.tuple([ z.literal(1), z.literal(2), z.literal(3) ]) })

See also

zx.fromJson

Convert a blob of JSON data into a zod schema that represents its greatest lower bound.

Example

import type { z } from 'zod'
import { zx } from '@traversable/zod'

let ex_01 = zx.fromJson({ abc: 'ABC', def: [] })
//  ^? let ex_01: z.ZodObject<{ abc: z.ZodString, def: z.ZodArray<z.ZodUnknown> }>

console.log(zx.toString(ex_01))
// => z.object({ abc: z.string(), def: z.array(z.unknown()) })

let ex_02 = zx.fromJson({ abc: 'ABC', def: [123] })
//  ^? let ex_01: z.ZodObject<{ abc: z.ZodString, def: z.ZodArray<z.ZodUnknown> }>

console.log(zx.toString(ex_02))
// => z.object({ abc: z.string(), def: z.array(z.number()) })

let ex_03 = zx.fromJson({ abc: 'ABC', def: [123, null]})
//  ^? let ex_01: z.ZodObject<{ abc: z.ZodString, def: z.ZodArray<z.Union<[z.ZodNumber, z.ZodNull]>> }>

console.log(zx.toString(ex_03))
// => z.object({ abc: z.string(), def: z.array(z.union([z.number(), z.null()])) })

See also

zx.fromJson.writeable

Convert a blob of JSON data into a stringified Zod schema that represents its greatest lower bound.

Example

import type { z } from 'zod'
import { zx } from '@traversable/zod'

let ex_01 = zx.fromJson.writeable({ abc: 'ABC', def: [] })

console.log(ex_01)
// => z.object({ abc: z.string(), def: z.array(z.unknown()) })

let ex_02 = zx.fromJson.writeable({ abc: 'ABC', def: [123] })

console.log(ex_02)
// => z.object({ abc: z.string(), def: z.array(z.number()) })

let ex_03 = zx.fromJson.writeable({ abc: 'ABC', def: [123, null]})

console.log(ex_03)
// => z.object({ abc: z.string(), def: z.array(z.union([z.number(), z.null()])) })

See also

zx.deepPartial

Prior art

Credit goes to @jaens for their work to detect circular schemas and prevent stack overflow.

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = zx.deepPartial(z.object({ a: z.number(), b: z.object({ c: z.string() }) }))

type MySchema = z.infer<typeof MySchema>
//   ^? type MySchema = { a?: number, b?: { c?: string } }

See also

zx.deepPartial.writeable

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = z.object({ a: z.number(), b: z.object({ c: z.string() }) })

console.log(zx.deepPartial.writeable(MySchema))
// =>
// z.object({
//   a: z.number().optional(),
//   b: z.object({
//     c: z.string().optional(),
//     d: z.array(z.boolean()).optional()
//   }).optional()
// }).optional()

See also

zx.defaultValue

zx.defaultValues converts a Zod schema into a "default value' that respects the structure of the schema.

A common use case for zx.defaultValue is creating default values for forms.

[!NOTE] By default, zx.defaultValue does not make any assumptions about what "default" means for primitive types, which is why it returns undefined when it encounters a leaf value. This behavior is configurable.

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = z.object({
  a: z.number(),
  b: z.object({
    c: z.string(),
    d: z.array(z.boolean())
  })
})

// by default, primitives are initialized as `undefined`:
const defaultOne = zx.defaultValue(MySchema)
console.log(defaultOne) // => { a: undefined, b: { c: undefined, d: [] } }

// to configure this behavior, use the `fallbacks` property:
const defaultTwo = zx.defaultValue(MySchema, { fallbacks: { number: 0, string: '' } })
console.log(defaultTwo) // => { a: 0, b: { c: '', d: [] } }

zx.toPaths

zx.toPaths converts a zod schema into an array of "paths" that represent the schema.

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

console.log(
  zx.toPaths(z.object({ a: z.object({ c: z.string() }), b: z.number() }))
) // => [["a", "c"], ["b"]]

zx.toString

Convert a Zod schema into a string that constructs the same zod schema.

Useful for writing/debugging tests that involve randomly generated schemas.

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

console.log(
  zx.toString(
    z.templateLiteral([1n])
  )
) // => z.templateLiteral([1n])

console.log(
  zx.toString(
    z.map(z.array(z.boolean()), z.set(z.number().optional()))
  )
) // => z.map(z.array(z.boolean()), z.set(z.number().optional()))

console.log(
  zx.toString(
    z.tuple([
      z.number().min(0).lt(2),
      z.number().multipleOf(2).nullable(),
    ])
  )
) // => z.tuple([z.number().min(0).lt(2), z.number().multipleOf(2).nullable()])

zx.toType

Convert a Zod schema into a string that represents its type.

To preserve JSDoc annotations for object properties, pass preserveJsDocs: true in the options object. If the property's metadata includes an example property, the example will be escaped and included as an @escape tag.

[!NOTE] By default, the type will be returned as an "inline" type. To give the type a name, use the typeName option.

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

console.log(
  zx.toType(
    z.object({
      a: z.optional(z.literal(1)),
      b: z.literal(2),
      c: z.optional(z.literal(3))
    })
  )
) // => { a?: 1, b: 2, c?: 3 }

console.log(
  zx.toType(
    z.intersection(
      z.object({ a: z.literal(1) }),
      z.object({ b: z.literal(2) })
    )
  )
) // => { a: 1 } & { b: 2 }

console.log(
  zx.toType(
    z.templateLiteral([
      z.literal(['a', 'b']),
      ' ',
      z.literal(['c', 'd']),
      ' ',
      z.literal(['e', 'f'])
    ])
  )
) // => "a c e" | "a c f" | "a d e" | "a d f" | "b c e" | "b c f" | "b d e" | "b d f"

// To give the generated type a name, use the `typeName` option:
console.log(
  zx.toType(
    z.object({ a: z.optional(z.number()) }),
    { typeName: 'MyType' }
  )
) // => type MyType = { a?: number }

// To preserve JSDoc annotations, use the `preserveJsDocs` option:
console.log(
  zx.toType(
    z.object({
      street1: z.string().meta({ describe: 'Street 1 name' }),
      street2: z.string().optional().meta({ describe: 'Street 2 name', example: 'Unit B' }),
      city: z.string(),
    }),
    { typeName: 'Address', preserveJsDocs: true }
  )
) 
// => 
// type Address = {
//   /**
//    * Street 1 name
//    */
//   street1: string 
//   /**
//    * Street 2 name
//    * @example "Unit B"
//    */
//   street2?: string
//   city: string
// }

zx.deepNoDefaults

Recursively removes any z.default nodes.

Unless you opt out, if the node is an object property, the property will be wrapped with z.optional.

To opt out, pass { replaceWithOptional: false } as the second argument to zx.deepNoDefaults.

Example

import { z } from 'zod'
import { zx } from "@traversable/zod"

const withoutDefaults = zx.deepNoDefaults(
  z.object({
    a: z.number().default(0),
    b: z.boolean().default(false).optional(),
    c: z.boolean().optional().default(false),
    d: z.union([z.string().default(''), z.number().default(0)]),
    e: z.array(
      z.object({
        f: z.number().default(0),
        g: z.boolean().default(false).optional(),
        h: z.boolean().optional().default(false),
        i: z.union([z.string().default(''), z.number().default(0)]),
      }).default({
        f: 0,
        g: false,
        h: false,
        i: '',
      })
    ).default([])
  })
)

console.log(
  zx.toString(withoutDefaults)
) 
// =>
// z.object({
//  a: z.number().optional(),
//  b: z.boolean().optional(),
//  c: z.boolean().optional(),
//  d: z.union([z.string(), z.number()]).optional(),
//  e: z
//    .array(
//      z.object({
//        f: z.number().optional(),
//        g: z.boolean().optional(),
//        h: z.boolean().optional(),
//        i: z.union([z.string(), z.number()]).optional(),
//      }),
//    )
//    .optional(),
// })

See also

zx.deepNoDefaults.writeable

Recursively removes any z.default nodes, and returns the transformed schema in string form.

Unless you opt out, if the node is an object property, the property will be wrapped with z.optional.

To opt out, pass { replaceWithOptional: false } as the second argument to zx.deepNoDefaults.

Example

import { z } from 'zod'
import { zx } from "@traversable/zod"

const withoutDefaults = zx.deepNoDefaults.writeable(
  z.object({
    a: z.number().default(0),
    b: z.boolean().default(false).optional(),
    c: z.boolean().optional().default(false),
    d: z.union([z.string().default(''), z.number().default(0)]),
    e: z.array(
      z.object({
        f: z.number().default(0),
        g: z.boolean().default(false).optional(),
        h: z.boolean().optional().default(false),
        i: z.union([z.string().default(''), z.number().default(0)]),
      }).default({
        f: 0,
        g: false,
        h: false,
        i: '',
      })
    ).default([])
  })
)

console.log(withoutDefaults) 
// =>
// z.object({
//  a: z.number().optional(),
//  b: z.boolean().optional(),
//  c: z.boolean().optional(),
//  d: z.union([z.string(), z.number()]).optional(),
//  e: z
//    .array(
//      z.object({
//        f: z.number().optional(),
//        g: z.boolean().optional(),
//        h: z.boolean().optional(),
//        i: z.union([z.string(), z.number()]).optional(),
//      }),
//    )
//    .optional(),
// })

See also

zx.deepLoose

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = zx.deepLoose(
  z.object({
    a: z.number(),
    b: z.object({
      c: z.string()
    }) 
  })
)

See also

zx.deepLoose.writeable

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = z.object({
  a: z.number(),
  b: z.object({
    c: z.string()
  })
})

console.log(zx.deepLoose.writeable(MySchema))
// =>
// z.looseObject({
//   a: z.number(),
//   b: z.looseObject({
//     c: z.string()
//   })
// })

See also

zx.deepNonLoose

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = zx.deepNonLoose(
  z.looseObject({
    a: z.number(),
    b: z.looseObject({
      c: z.string()
    })
  })
)

See also

zx.deepNonLoose.writeable

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = z.looseObject({ 
  a: z.number(),
  b: z.looseObject({
    c: z.string() 
  }) 
})

console.log(zx.deepNonLoose.writeable(MySchema))
// =>
// z.object({
//   a: z.number(),
//   b: z.object({
//     c: z.string()
//   })
// })

See also

zx.deepRequired

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = zx.deepRequired(z.object({ a: z.number().optional(), b: z.object({ c: z.string().optional() }) }))

type MySchema = z.infer<typeof MySchema>
//   ^? type MySchema = { a: number, b: { c: string } }

See also

zx.deepRequired.writeable

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = z.object({
  a: z.number().optional(),
  b: z.optional(
    z.object({
      c: z.string(),
      d: z.array(z.boolean()).optional()
    })
  )
})

console.log(zx.deepRequired.writeable(MySchema))
// =>
// z.object({
//   a: z.number(),
//   b: z.object({
//     c: z.string(),
//     d: z.array(z.boolean())
//   })
// })

See also

zx.deepNullable

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = zx.deepNullable(z.object({ a: z.number(), b: z.object({ c: z.string() }) }))

type MySchema = z.infer<typeof MySchema>
//   ^? type MySchema = { a: number | null, b: { c: string | null } | null }

See also

zx.deepNullable.writeable

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = z.object({
  a: z.number(),
  b: z.object({
    c: z.string(),
    d: z.array(z.boolean())
  })
})

console.log(zx.deepNullable.writeable(MySchema))
// =>
// z.object({
//   a: z.number().nullable(),
//   b: z.object({
//     c: z.string().nullable(),
//     d: z.array(z.boolean()).nullable()
//   }).nullable()
// })

See also

zx.deepNonNullable

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = zx.deepNonNullable(
  z.object({
    a: z.number().nullable(),
    b: z.object({
      c: z.string().nullable(),
    }),
  })
)

type MySchema = z.infer<typeof MySchema>
//   ^? type MySchema = { a: number, b: { c: string } }

See also

zx.deepNonNullable.writeable

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = z.object({
  a: z.number().nullable(),
  b: z.object({
    c: z.string().nullable(),
  })
})

console.log(zx.deepNonNullable.writeable(MySchema))
// =>
// z.object({
//   a: z.number(),
//   b: z.object({
//     c: z.string(),
//   })
// })

See also

zx.deepReadonly

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = zx.deepReadonly(z.object({ a: z.number(), b: z.object({ c: z.string() }) }))

type MySchema = z.infer<typeof MySchema>
//   ^? type MySchema = { readonly a: number, readonly b: { readonly c: string } }

See also

zx.deepReadonly.writeable

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = z.object({ a: z.number(), b: z.object({ c: z.string() }) })

console.log(zx.deepReadonly.writeable(MySchema))
// =>
// z.object({
//   a: z.number().readonly(),
//   b: z.object({
//     c: z.string().readonly()
//   }).readonly()
// }).readonly()

See also

zx.deepStrict

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = zx.deepStrict(
  z.object({
    a: z.number(),
    b: z.object({
      c: z.string()
    }) 
  })
)

See also

zx.deepStrict.writeable

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = z.object({
  a: z.number(),
  b: z.object({
    c: z.string()
  })
})

console.log(zx.deepStrict.writeable(MySchema))
// =>
// z.strictObject({
//   a: z.number(),
//   b: z.strictObject({
//     c: z.string()
//   })
// })

See also

zx.deepNonStrict

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = zx.deepNonStrict(
  z.strictObject({
    a: z.number(),
    b: z.strictObject({
      c: z.string()
    }) 
  })
)

See also

zx.deepNonStrict.writeable

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

const MySchema = z.strictObject({
  a: z.number(),
  b: z.strictObject({
    c: z.string()
  })
})

console.log(zx.deepNonStrict.writeable(MySchema))
// =>
// z.object({
//   a: z.number(),
//   b: z.object({
//     c: z.string()
//   })
// })

See also

zx.typeof

zx.typeof returns the "type" (or tag) of a Zod schema.

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

console.log(zx.typeof(z.string())) // => "string"

zx.tagged

zx.tagged lets you construct a type-guard that identifies the type of Zod schema you have.

Example

import { z } from 'zod'
import { zx } from '@traversable/zod'

zx.tagged('object', z.object({})) // true
zx.tagged('array', z.string())    // false

zx.makeLens

[!NOTE] zx.makeLens still experimental (๐Ÿ”ฌ). Use in production with care.

zx.makeLens accepts a zod schema (classic, v4) as its first argument, and a "selector function" as its second argument.

An optic is a generalization of a lens, but since most people use "lens" to refer to optics generally, they are sometimes used interchangeably in this document.

With zx.makeLens, you use a selector function to build up an optic via a series of property accesses.

Let's look at a few examples to make things more concrete.

Example #1: Lens

For our first example, let's create a lens that focuses on a structure's "a[0]" path:

import { z } from 'zod'
import { zx } from '@traversable/zod'

//////////////////////////
///  example #1: Lens  ///
//////////////////////////

const Schema = z.object({ a: z.tuple([z.string(), z.bigint()]) })

//    Use autocompletion to "select" what you want to focus:
//                                    โ††โ††โ††โ††โ††โ††
const Lens = zx.makeLens(Schema, $ => $.a[0])

Lens
// ^? const Lens: zx.Lens<{ a: [string, bigint] }, string>
//                         ๐™˜___________________๐™˜   ๐™˜____๐™˜
//                                     structure         focus

// Lenses have 3 properties:

///////////////
// #1:
// Lens.get -- Given a structure,
//             returns the focus

const ex_01 = Lens.get({ a: ['hi', 0n] })
//                      ๐™˜_____________๐™˜
//                         structure

console.log(ex_01) // => "hi"
//                        ๐™˜๐™˜
//                      focus


///////////////
// #2:
// Lens.set -- Given a new focus and a structure,
//             sets the new focus & returns the structure

const ex_02 = Lens.set(`hey, ho, let's go`, { a: ['', 0n] })
//                      ๐™˜_______________๐™˜    ๐™˜___________๐™˜
//                          new focus          structure

console.log(ex_02) // => { a: ["hey, ho, let's go", 0n] }
//                              ๐™˜_______________๐™˜
//                                  new focus


/////////////////
// #3:
// Lens.modify -- Given a "modify" callback and a structure,
//                applies the callback to the focus & returns the structure

const ex_03 = Lens.modify((str) => str.toUpperCase(), { a: [`hey, ho`, 0n] })
//                         ๐™˜_______________________๐™˜   ๐™˜__________________๐™˜
//                                 callback                 structure

console.log(ex_03) // => { a: ["HEY, HO", 0n] }
//                              ๐™˜_____๐™˜
//                             new focus

// Note that if your callback changes the focus type,
// that will be reflected in the return type as well:

const ex_04 = Lens.modify((str) => str.length > 0, { a: ['', 0n] })
//                         ๐™˜____________________๐™˜   ๐™˜___________๐™˜
//                                callback            structure

console.log(ex_04) // => { a: [false, 0n] }
//           ^? const ex_04: { a: [boolean, bigint] }
//                                 ๐™˜_____๐™˜
//                                new focus

Example #2: Prism

When you use zx.makeLens on a union type, you get back a different kind of lens called a prism.

Let's see how prisms differ from lenses:

import { z } from 'zod'
import { zx } from '@traversable/zod'

///////////////////////////
///  example #2: Prism  ///
///////////////////////////

const Schema = z.union([
  z.object({ tag: z.literal('ONE'), ghi: z.number() }),
  z.object({ tag: z.literal('TWO') })
])

// Let's focus on the first union member's "ghi" property.

// If a discriminant can be inferred, autocompletion allows
// you to select that member by its discriminant,
// prefixed by `๊–›`:
//
//                                       โ††โ††โ††โ††โ††
const Prism = zx.makeLens(Schema, $ => $.๊–›ONE.ghi)

Prism
// ^? Prism: zx.Prism<{ tag: "ONE", ghi: number } | { tag: "TWO" }, number | undefined>
//                     ๐™˜________________________________________๐™˜   ๐™˜________________๐™˜
//                                          structure                          focus

// Prisms have the same 3 properties as lenses,
// but they behave like **pattern matchers**
// instead of _property accessors_

///////////////
// #1:
// Prism.get -- Given a matching structure,
//              returns the focus

const ex_01 = Prism.get({ tag: 'ONE', ghi: 123 })
//                       ๐™˜____________________๐™˜
//                            structure

console.log(ex_01) // => 123
//                       ๐™˜๐™˜๐™˜
//                      focus

// Prism.get -- If the match fails,
//              returns undefined

const ex_02 = Prism.get({ tag: 'TWO' })
//                       ๐™˜___________๐™˜
//                         structure

console.log(ex_02) // => undefined
//                          ๐™˜๐™˜๐™˜
//                       no match


///////////////
// #2:
// Prism.set -- Given a new focus and a matching structure,
//              sets the new focus & returns the structure

const ex_03 = Prism.set(9_000, { tag: 'ONE', ghi: 123 })
//                      ๐™˜___๐™˜   ๐™˜____________________๐™˜
//                    new focus        structure

console.log(ex_03) // => { tag: 'ONE', ghi: 9000 }
//                                          ๐™˜__๐™˜
//                                        new focus

// Prism.set -- If the match fails,
//              returns the structure unchanged

const ex_04 = Prism.set(9000, { tag: 'TWO' })

console.log(ex_04) // => { tag: 'TWO' }
//                        ๐™˜__________๐™˜
//                          no match


//////////////////
// #3:
// Prism.modify -- Given a "modify" callback and a matching structure,
//                 applies the callback to the focus & returns the structure

// Just like with lenses, if your callback changes the focus type,
// that will be reflected in the return type:

const ex_05 = Prism.modify((n) => [n, n], { tag: 'ONE', ghi: 123 })
//                         ๐™˜___________๐™˜   ๐™˜____________________๐™˜
//                            callback           structure

console.log(ex_05) // => { tag: 'ONE', ghi: [123, 123] }
//           ^? const ex_05: { tag: "ONE", ghi: number[] } | { tag: "TWO" }

// Prism.modify -- If the match fails,
//                 returns the structure unchanged

const ex_06 = Prism.modify((n) => n + 1, { tag: 'TWO' })
//                         ๐™˜__________๐™˜   ๐™˜___________๐™˜
//                           callback       structure

console.log(ex_06) // => { tag: 'TWO' }
//           ^? const ex_06: { tag: "ONE", ghi: number } | { tag: "TWO" }

Example #3: Traversal

When you use zx.makeLens on a collection type (such as z.array or z.record), you get back a different kind of lens called a traversal.

Let's see how traversals differ from lenses and prisms:

import { z } from 'zod'
import { zx } from '@traversable/zod'

///////////////////////////////
///  example #3: Traversal  ///
///////////////////////////////

const Schema = z.object({
  a: z.array(
    z.object({
      b: z.number(),
      c: z.string()
    })
  )
})

// Let's focus on the `"b"` property of each of the elements of the structure's `"a"` property:

// To indicate that you want to traverse the array,
// autocomplete the `แฃ”๊“ธ๊“ธ` field:
//                                                  โ††โ††
const Traversal = zx.makeLens(Schema, $ => $ => $.a.แฃ”๊“ธ๊“ธ.b)


Traversal
// ^? Traversal: zx.Traversal<{ a: { b: number, c: string }[] }, number>
//                             ๐™˜_____________________________๐™˜   ๐™˜____๐™˜
//                                       structure               focus

// Traversals have the same 3 properties as lenses and prisms,
// but they behave like **for-of loops**
// instead of _property accessors_ or _patterns matchers_


///////////////
// #1:
// Traversal.get -- Given a matching structure,
//                  returns all of the focuses

const ex_01 = Traversal.get({ a: [{ b: 0, c: '' }, { b: 1, c: '' }] })
//                           ๐™˜_____________________________________๐™˜
//                                         structure

console.log(ex_01) // => [0, 1]
//                        ๐™˜__๐™˜
//                       focus


///////////////
// #2:
// Traversal.set -- Given a new focus and a matching structure, sets all of the elements
//                  of the collection to the new focus & returns the structure

const ex_02 = Traversal.set(9_000, { a: [{ b: 0, c: '' }, { b: 1, c: '' }] })
//                          ๐™˜___๐™˜   ๐™˜_____________________________________๐™˜
//                        new focus               structure

console.log(ex_02) // => { a: [{ b: 9000, c: '' }, { b: 9000, c: '' }] }
//                                  ๐™˜__๐™˜                ๐™˜__๐™˜
//                                new focus           new focus


//////////////////
// #3:
// Traversal.modify -- Given a "modify" callback and a matching structure,
//                     applies the callback to _each_ focus & returns the structure

// Just like with lenses & prisms, if your callback changes the focus type,
// that will be reflected in the return type:

const ex_03 = Traversal.modify((n) => [n, n + 1], { a: [{ b: 0, c: '' }, { b: 1, c: '' }] })
//                             ๐™˜______________๐™˜    ๐™˜_____________________________________๐™˜
//                                 callback                      structure

console.log(ex_03) // => { a: [{ b: [0, 1], c: '' }, { b: [1, 2], c: '' }] }
//           ^? const ex_03: { a: { b: number[], c: string }[] }
//                                     ๐™˜______๐™˜
//                                    new focus

Advanced Features

zx.fold

[!NOTE] zx.fold is an advanced API.

Use zx.fold to define a recursive traversal of a Zod schema. Useful when building a schema rewriter.

zx.fold is a powertool. Most of @traversable/zod uses zx.fold under the hood.

Compared to the rest of the library, it's fairly "low-level", so unless you're doing something more advanced you probably won't need to use it directly.

Examples

  1. Example: Custom schema rewriter

Let's write a schema rewriter that takes an arbitrary Zod schema, and applies a custom transformation to only z.string schemas. For this contrived example, we'll be converting string values to uppercase.

[!NOTE]

You can play with this example on StackBlitz

import * as z from 'zod'
import { zx } from '@traversable/zod'

function rewriter<T extends z.ZodType>(type: T): T
function rewriter<T>(type: z.ZodType<T>) { 
  return fold<z.ZodType>((x) => {
    switch (true) {
      case zx.tagged('string')(x): return x.transform((v) => v.toUpperCase())
      default: return z.clone(x as z.ZodType, x._zod.def as z.core.$ZodTypeDef)
    }
  })(type)
}

const Ex01 = rewriter(z.uuid())
//     ^? const Ex01: z.ZodUUID

console.log(Ex01.parse('fdbe3218-bba3-4cf9-95d6-0a0a3770fb64'))
// => "FDBE3218-BBA3-4CF9-95D6-0A0A3770FB64"

const Ex02 = rewriter(z.object({ id: z.uuid() }))
//     ^? const Ex02: z.ZodObject<{ id: z.ZodUUID }>

console.log(Ex02.parse({ id: '012f33de-023b-414e-a0a8-0ff9e1e53545' }))
// => { "id": "012F33DE-023B-414E-A0A8-0FF9E1E53545" }

[!NOTE]

Notice the use of z.clone: this is only necessary when your target is also a Zod schema. This is to ensure that none of the schema's class properties are lost in the traversal.

Thanks to @Refzlund for suggesting that we add this example to the docs!

  1. Example: Mock data generator

Let's write a function that takes an arbitrary Zod schema, and generates mock data that satisfies the schema (a.k.a. a "faker").

[!NOTE] You can play with this example on StackBlitz

import { z } from 'zod/v4'
import { F, tagged } from '@traversable/zod-types'
import { faker } from '@faker-js/faker'

type Fake = () => unknown

const fake = F.fold<Fake>((x) => {
  //                ๐™˜__๐™˜ this type parameter fills in the "holes" below
  switch (true) {
    case tagged('array')(x): return () => faker.helpers.multiple(
      () => x._zod.def.element()
      //                ^? method element: Fake
      //                                   ๐™˜__๐™˜
    )
    case tagged('never')(x): return () => void 0
    case tagged('unknown')(x): return () => void 0
    case tagged('any')(x): return () => void 0
    case tagged('void')(x): return () => void 0
    case tagged('null')(x): return () => null
    case tagged('undefined')(x): return () => undefined
    case tagged('nan')(x): return () => NaN
    case tagged('boolean')(x): return () => faker.datatype.boolean()
    case tagged('symbol')(x): return () => Symbol()
    case tagged('int')(x): return () => faker.number.int()
    case tagged('bigint')(x): return () => faker.number.bigInt()
    case tagged('number')(x): return () => faker.number.float()
    case tagged('string')(x): return () => faker.lorem.words()
    case tagged('date')(x): return () => faker.date.recent()
    case tagged('literal')(x): return () => faker.helpers.arrayElement(x._zod.def.values)
    case tagged('template_literal')(x): return () => faker.helpers.fromRegExp(x._zod.pattern)
    case tagged('enum')(x): return () => faker.helpers.arrayElement(Array.from(x._zod.values))
    case tagged('nonoptional')(x): return x._zod.def.innerType
    case tagged('nullable')(x): return x._zod.def.innerType
    case tagged('optional')(x): return x._zod.def.innerType
    case tagged('readonly')(x): return x._zod.def.innerType
    case tagged('catch')(x): return x._zod.def.innerType
    case tagged('default')(x): return x._zod.def.innerType
    case tagged('prefault')(x): return x._zod.def.innerType
    case tagged('success')(x): return x._zod.def.innerType
    case tagged('pipe')(x): return x._zod.def.out
    case tagged('lazy')(x): return x._zod.def.getter()
    case tagged('promise')(x): return x._zod.def.innerType
    case tagged('set')(x): return () => new Set([x._zod.def.valueType()])
    case tagged('map')(x): return () => new Map([[x._zod.def.keyType(), x._zod.def.valueType()]])
    case tagged('intersection')(x): return () => Object.assign({}, x._zod.def.left(), x._zod.def.right())
    case tagged('union')(x): return () => faker.helpers.arrayElement(x._zod.def.options.map((option) => option()))
    case tagged('tuple')(x): return () => x._zod.def.items.map((item) => item())
    case tagged('record')(x): return () => Object.fromEntries([[x._zod.def.keyType(), x._zod.def.valueType()]])
    case tagged('object')(x): return () => Object.fromEntries(Object.entries(x._zod.def.shape).map(([k, v]) => [k, v()]))
    case tagged('file')(x): return () => new File(faker.lorem.lines(10).split('\n'), faker.system.commonFileName())
    case tagged('custom')(x): { throw Error('Unsupported schema: z.custom') }
    case tagged('transform')(x): { throw Error('Unsupported schema: z.transform') }
    default: { x satisfies never; throw Error('Illegal state') }
    //         ๐™˜_______________๐™˜
    //        exhaustiveness check works
  }
})

// Let's test it out:
const mock = fake(
  z.object({
    abc: z.array(z.string()), 
    def: z.optional(
      z.tuple([
        z.number(), 
        z.boolean()
      ])
    )
  })
)

console.log(mock())
// => {
//  abc: [
//     'annus iure consequatur',
//     'aer suus autem',
//     'delectus patrocinor deporto',
//     'benevolentia tonsor odit',
//     'stabilis dolor tres',
//     'mollitia quibusdam vociferor'
//   ],
//   def: [-882, false]
// }

zx.Functor

[!NOTE] zx.Functor is an advanced API

zx.Functor is the primary abstraction that powers @traversable/zod.

zx.Functor is a powertool. Most of @traversable/zod uses zx.Functor under the hood.

Compared to the rest of the library, it's fairly "low-level", so unless you're doing something pretty advanced you probably won't need to use it directly.