Gaurab Paul

Polyglot software developer & consultant passionate about web development, distributed systems and open source technologies

Support my blog and open-source work

Tags

Generating API docs for zod-types
Posted  2 years ago

I often like to use zod for validating user provided options in the public API in typescript projects. Zod offers a really nice validation & type inference support and it is nice to be able to fail early for invalid options even if consumer is not using typescript.

Generating API documentation is a bit of trouble though, for these types. Typedoc is a nice documentation generator I frequently use, but it does not have any specialized zod support and works best when types are defined through normal typescript interfaces.

So what do we do ?

Interestingly, typescript is able to propagate comments through inheritance hierarchy and mapped types.

So if we have a zod-type as follows:

import * as z from "zod";

const UserSchema = z.object({
    /** Full name of user */
    name: z.string()
})

and later define an interface that extends the type inferred from this:

interface User extends z.TypeOf<typeof UserSchema> {}

The generated documentation of User will contain the comments for name property.

This unfortunately works only for Object types as long as we don't introduce any unions and intersections - any base type of an interface must have statically known members. This also doesn't work well for nested object types as typedoc will pick only the types at top level.

For more complex types, a simple workaround is to extract interfaces for constituent types and link them in the documentation.

So instead of:

const UserDetailsSchema = z.object({
    profile: z.object({
        name: z.string()
    }).nullish()
});

We'd write:

const ProfileSchema = z.object({
    /** Full name of user */
    name: z.string()
});

interface Profile extends z.TypeOf<typeof ProfileSchema> {}

const UserDetailsSchema = z.object({
    /** See {@link Profile} */
    profile: Profile.nullish()
})

interface UserDetails extends z.TypeOf<typeof UserDetailsSchema> {}

This increases the boilerplate a bit, but is otherwise quite practical.