zod-sdk v0.2.51
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()
orservice.makeCommand()
- Instead of
server.makeProcedure() orserver.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
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
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago