Mohammed Anas
Mohammed Anas13mo ago

How does trpc typing work

I'm curious to know how trpc generates type without a code gen step , i am trying to acheive something like this
export class API {
private schema:z.ZodSchema = z.undefined()
input<T>(inp:z.ZodSchema<T>){
this.schema = inp
return this
}
get(input:z.infer<typeof this.schema>){
console.log(("ok"));
}
}
const api = new API()
api.input(z.object({
name:z.string()
}))

// I need a way to typically extract this {name:string} type without a codegen step
export class API {
private schema:z.ZodSchema = z.undefined()
input<T>(inp:z.ZodSchema<T>){
this.schema = inp
return this
}
get(input:z.infer<typeof this.schema>){
console.log(("ok"));
}
}
const api = new API()
api.input(z.object({
name:z.string()
}))

// I need a way to typically extract this {name:string} type without a codegen step
3 Replies
Mohammed Anas
Mohammed AnasOP13mo ago
My approach might be wrong, i'm trying to build something similar . Any help is appreciated Later on whenever i try to invoke a method similar to mutate , i need the typing of the input to be in available in the parameter of the mutate method .
LittleLily
LittleLily13mo ago
You need the class itself to have a generic arg And you probably want to use method chaining like trpc does, which means instead of setting a property on a class, you have a method which returns a new class instance containing the functions that can be called from that point (I prefer implementing that without classes at all and just returning objects with functions inside them) Something like this:
const api = () => ({
input: <T extends ZodTypeAny>(inputSchema: T) => ({
mutate: <U>(mutateFn: (data: z.infer<T>) => U) =>
(data: unknown) =>
mutateFn(inputSchema.parse(data))
})
})

// Create and use the api
const numberApi = api().input(z.number().min(1).max(10)).mutate(data => `Hello ${data}`)
const x = numberApi(9) // returns "Hello 9"
const y = numberApi(100) // throws Zod validation error since schema had max(10)
const api = () => ({
input: <T extends ZodTypeAny>(inputSchema: T) => ({
mutate: <U>(mutateFn: (data: z.infer<T>) => U) =>
(data: unknown) =>
mutateFn(inputSchema.parse(data))
})
})

// Create and use the api
const numberApi = api().input(z.number().min(1).max(10)).mutate(data => `Hello ${data}`)
const x = numberApi(9) // returns "Hello 9"
const y = numberApi(100) // throws Zod validation error since schema had max(10)
The benefit of having a nested structure like that is the inner functions have easy access to all the generic types and variables/args from the outer functions
Mohammed Anas
Mohammed AnasOP13mo ago
will give that a try .