Created
November 9, 2024 17:19
-
-
Save AlexanderHott/f0621a24289269bcd2cab8ba217e5c1d to your computer and use it in GitHub Desktop.
A tiny trpc.io client from https://trpc.io/blog/tinyrpc-client
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { z } from "zod"; | |
import type { | |
AnyProcedure, | |
inferProcedureInput, | |
inferProcedureOutput, | |
AnyQueryProcedure, | |
AnyMutationProcedure, | |
ProcedureRouterRecord, | |
AnyRouter, | |
} from "@trpc/server"; | |
import { initTRPC, TRPCError } from "@trpc/server"; | |
import type { TRPCResponse } from "@trpc/server/rpc"; | |
type Post = { id: string; title: string }; | |
const posts: Post[] = []; | |
const t = initTRPC.create({}); | |
const router = t.router; | |
const publicProcedure = t.procedure; | |
function uuid() { | |
return "uuid"; | |
} | |
const appRouter = router({ | |
post: router({ | |
byId: publicProcedure | |
.input(z.object({ id: z.string() })) | |
.query(({ input }) => { | |
const post = posts.find((p) => p.id === input.id); | |
if (!post) throw new TRPCError({ code: "NOT_FOUND" }); | |
return post; | |
}), | |
byTitle: publicProcedure | |
.input(z.object({ title: z.string() })) | |
.query(({ input }) => { | |
const post = posts.find((p) => p.title === input.title); | |
if (!post) throw new TRPCError({ code: "NOT_FOUND" }); | |
return post; | |
}), | |
create: publicProcedure | |
.input(z.object({ title: z.string() })) | |
.mutation(({ input }) => { | |
const post = { id: uuid(), ...input }; | |
posts.push(post); | |
return post; | |
}), | |
}), | |
}); | |
// =========== | |
// Tiny Client | |
// =========== | |
type Resolver<TProcedure extends AnyProcedure> = ( | |
input: inferProcedureInput<TProcedure>, | |
) => Promise<inferProcedureOutput<TProcedure>>; | |
type DecorateProcedure<TProcedure> = TProcedure extends AnyQueryProcedure | |
? { | |
query: Resolver<TProcedure>; | |
} | |
: TProcedure extends AnyMutationProcedure | |
? { | |
mutate: Resolver<TProcedure>; | |
} | |
: never; | |
type AppRouter = typeof appRouter; | |
type PostById = Resolver<AppRouter["post"]["byId"]>; | |
type DecoratedProcedureRecord<TProcedures extends ProcedureRouterRecord> = { | |
[TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter | |
? DecoratedProcedureRecord<TProcedures[TKey]["_def"]["record"]> | |
: TProcedures[TKey] extends AnyProcedure | |
? DecorateProcedure<TProcedures[TKey]> | |
: never; | |
}; | |
type ProxyCallbackOptions = { | |
path: readonly string[]; | |
args: readonly unknown[]; | |
}; | |
type ProxyCallback = (opts: ProxyCallbackOptions) => unknown; | |
function createRecursiveProxy( | |
callback: ProxyCallback, | |
path: readonly string[], | |
) { | |
const proxy: unknown = new Proxy(() => {}, { | |
get(_obj, key) { | |
if (typeof key !== "string") return undefined; | |
return createRecursiveProxy(callback, [...path, key]); | |
}, | |
apply(_1, _2, args) { | |
return callback({ path, args }); | |
}, | |
}); | |
return proxy; | |
} | |
export const createTinyTRPCClient = <TRouter extends AnyRouter>( | |
baseUrl: string, | |
) => | |
createRecursiveProxy(async (opts) => { | |
const path = [...opts.path]; | |
const method = path.pop()! as "query" | "mutate"; | |
const dotPath = path.join("."); | |
let uri = `${baseUrl}/${dotPath}`; | |
let [input] = opts.args; | |
const stringifiedInput = input !== undefined && JSON.stringify(input); | |
let body: string | undefined = undefined; | |
if (stringifiedInput !== false) { | |
if (method === "query") { | |
uri += `?input=${encodeURIComponent(stringifiedInput)}`; | |
} else { | |
body = stringifiedInput; | |
} | |
} | |
const json: TRPCResponse = await fetch(uri, { | |
method: method === "query" ? "GET" : "POST", | |
headers: { "Content-Type": "application/json" }, | |
body, | |
}).then((res) => res.json()); | |
if ("error" in json) { | |
throw new Error(`Error: ${json.error.message}`); | |
} | |
return json.result.data; | |
}, []) as DecoratedProcedureRecord<TRouter["_def"]["record"]>; | |
const client = createTinyTRPCClient<AppRouter>("https://api.example.com"); | |
const post1 = await client.post.byId.query({ id: "123" }); | |
const post2 = await client.post.byTitle.query({ title: "Hello world" }); | |
const newPost = await client.post.create.mutate({ title: "Foo" }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment