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

zod-sdk

v0.2.51

Published

![zod-sdk](./zod-sdk.png)

Readme

zod-sdk

Zod SDK is an RPC library. Like TRPC it's going to reflect types from your backend. Of course, it does more than that. Here's what:

What and why

Type narrowing your payloads

If you used prisma then you probably have enjoyed getting back a payload with relationship data you specified in an includes property on your query. This allows you to do that, if you're up to the task of writing the more advanced return types on your backend functions. Here's an example:

async function find<T extends 'foo' | 'bar'>(
  str: T
): Promise<T extends 'foo' ? 'found-foo' : 'found-bar'> {
  return (str === 'foo' ? 'found-foo' : 'found-bar') as any
}

const query = makeQuery(find)

const result = client.call(query, (find) => find('foo'))
// Result has type: Promise<"found-foo">

See how that could be useful? A word of warning: this does necessarily put the onus on the backend to write complex Typescript and equally complex, typesafe, and well-tested code. With great power comes great responsibility I suppose.

Asynchronous context tracking in Node

In order to achieve type narrowing, we had to leave your function "unadulterated". That might be a harsh word for how you must conform to the arguments provided to you in a TRPC fn:

// This is a TRPC snippet
userById: publicProcedure
  .input(z.string())
  .query(async ({ input, ctx }) => {
    const user = await db.user.findById(input);
    return user;
  })

You have to use TRPC's input and ctx properties. It all comes bundled in an opts argument. In doing so, TRPC can't pass around complex types, because in the source code they have to wrap them and unwrap/infer them again.

Zod SDK passes around the original function type, without alteration. But of course your backend may need data usually found in request headers or cookies, or you want to abstract some reusable code across many fns. Here's how you do that:

// Make a service
const service = server.makeService({
  makeContext: () => ({
    foo: 'bar',
  }),
})

async function findContextFoo(): 'bar' {
  const { foo } = service.useCtx() // See this!?
  return foo
}
const routes = {
  findFooOrBar: service.makeProcedure('query', findFooOrBar),
}
// ...and so on, see "Getting Started"

Where does zod come in?

Like TRPC, you can use schemas to validate query and command parameters from the client, or the payloads sent back from the server.

const addYear = server.makeProcedure('query', 
  async function (date: Date) {
    return new Date(date.getFullYear() + 1, date.getMonth(), date.getDate())
  },
  {
    parameter: z.date(),
    payload: z.date(),
  }
)

There's a big benefit to doing this! We can use zod to parse input and results and therefore stay consistent with our types.

Let's put that another way. If you want to use Date, Map, or Set objects, you have to use schemas in makeQuery or makeCommand.

Getting Started

pnpm add zod-sdk

On the server, make a router

Use the server export on the Node server.

  1. Pass your backend functions to server.makeProcedure() or server.makeCommand()
  2. Create a routes object
  3. Pass routes to server.makeRouter()
  4. Export typeof routes however you want.
// server.ts
import { server } from 'zod-sdk/server'

function findUserById(id: string) {
  const user = await db.user.findById(input);
  return user
}

const routes = {
  findUserById: server.makeProcedure('query', findUserById),
}

const router = server.makeRouter(routes)

export type Routes = typeof routes

That router returned from server.makeRouter() is a request procedure. You can use it directly or in NextJS you can do this:

export { GET, POST } from server.makeRouter(routes)

On the client, make a dispatcher

Client-side:

  1. Pass your routes type object to client.makeInterface(options: Options)
  2. Pass the appropriate procedure to client.call() or client.mutate()
import { client } from 'zod-sdk/client'

const sdk = client.makeInterface<Routes>({
  baseUrl: url,
})
const result = await client.call(sdk.findFooOrBar, (find) =>
  find('foo')
)

NOTE: The client does not necessarily have to be the browser by the way. The same methods are availble to you on the server:

import { server } from 'zod-sdk/server'

const sdk = server.makeInterface<Routes>({
  baseUrl: url,
})

useQuery in React

This is a wrapper around Vercel's swr data fetching library. You probably know it or @tanstack/react-query.

You'll have to import this separately from zod-sdk/client. Because it only works client side.

'use client'

import styles from './page.module.css'
import { useQuery, client  } from 'zod-sdk/client'
import { IRoutes } from './routes'

const client = client.makeInterface<IRoutes>({
  baseUrl: 'http://localhost:3000/api/sdk',
})

export function Data() {
  const { data } = useQuery(client.queries.hello, {
    fn: (hello) => hello(),
  })

  return (
    <pre>
      <code className={styles.code}>{JSON.stringify(data, null, 2)}</code>
    </pre>
  )
}

server

server.makeService

This enables you to share context and middleware across fns. Here's an example.

// Make a service
const service = server.makeService({
  makeContext: () => ({
    foo: 'bar',
  }),
  middleware: (req, next) => {
    // console.log(req.pathname)
    next()
  }
})

Then you call

  • service.makeProcedure() or service.makeCommand()
  • Instead of ~~server~~.makeProcedure() or ~~server.makeCommand()~~

server.makeRouter

Call this to make the router on the server. That's covered in Getting Started

server.makeQuery

You should use server/service.makeProcedure() to make a GET request. Whether you use makeQuery or makeCommand doesn't make a difference, but do what's intuitive! Read up on CQRS if you want some reasons why

client

client.makeInterface

See Make a dispatcher

client.call

See Getting Started.

Calling client.call with a procedure you created with makeQuery will throw a typescript error.

client.command

Works just like client.call except you pass it command procedures.

FAQ

  • Waiting on yours!

To do

  • [ ] More testing