@iamdimka/base-rpc v0.1.3
@iamdimka/base-rpc
Disclaimer: I've watched the video about trpc v10, loved the idea and have tried to challenge myself trying to create similar base api. It is not a complete solution for all your issues, but can be skeleton in developing your rpc system
Creating base API
import rpc from "@iamdimka/rpc"
import type { IncomingMessage, ServerResponse } from "node:http";
// you can define own context
interface Context<Input = undefined> {
input: Input;
request: IncomingMessage;
response: ServerResponse
};
export const api = rpc({
buffer: () => Buffer.from([1,2,3]),
hello: (data: Context<{name: string}>) => "hello " + data.input.name,
nested: rpc({
now: () => new Date()
}),
object: {
regExp: () => /hello/
}
});
export type API = typeof api // and you can export api for client
The result of calling rpc
is a simple object with null
prototype containing flatten keys with associated functions. i.e
{
"buffer": () => Buffer.from([1,2,3]),
"hello": (data: Context<{name: string}>) => "hello " + data.input.name,
"nested.now": () => new Date(),
"object.regExp": () => /hello/
}
so you can call Object.keys(api)
and get the available methods or check if the method exists using the native API "buffer" in api
Client
import createRequest from "@iamdimka/rpc/client"
import type { API } from "path/to/server/api"
const request = createRequest<API>((method, input) => {
// method is string like object.regExp
// data is required method input
// do here fetch or websocket call or whatever you need to perform the api request
})
const hello = request.hello({ name: "world" }); // Promise<string>
const buf = request.buffer(); // Promise<Buffer>
const nestedNow = request.nested.now(); // Promise<Date>
const regExp = request.object.regExp(); // Promise<RegExp>
But how to deal if we use, for example simply JSON.stringify(response)
in our API? For this case, you can decorate API with WithCodec
type:
import createRequest from "@iamdimka/rpc/client"
import type { WithCodec, JsonCodec } from "@iamdimka/rpc/client"
import type { API } from "path/to/server/api"
const request = createRequest<WithCodec<API, JsonCodec>>((method, input) => {/** ... **/})
const hello = request.hello({ name: "world" }); // Promise<string>
const buf = request.buffer(); // Promise<{type: "buffer", data: number[]}>
const nestedNow = request.nested.now(); // Promise<string>
const regExp = request.object.regExp(); // Promise<"{}">
You can describe own codec, but do not implement serialization.
type MyCodec = [
[from: Date; to: number],
[RegExp, string],
[Buffer, string]
]
If you need to implement invalidation or another api, that does not require input, you can use createInvalidate
:
import { createInvalidate } from "@iamdimka/rpc/client"
import type { API } from "path/to/server/api"
const invalidate = createInvalidate<API>((method) => { /** clean cache, emit reactive events **/ });
invalidate.hello(); // void
Server validation?
If you need to validate input, it's up to you: you can create decorators, or create a wrapper to your function, i.e:
import rpc from "@iamdimka/rpc"
import v from "your-favorite-validation-lib";
interface Context<Input = undefined> {
input: Input;
request: IncomingMessage;
response: ServerResponse
};
const validate = <T, R>(validate: (input: any) => T, resolve: (ctx: Context<T>) => R) => (ctx: Context<T>) => {
ctx.input = validate(ctx.input);
return resolve(ctx);
};
const api = rpc({
hello: validate(
v.object({
name: v.string()
}),
({ input }) => `hello, ${input.name}`
)
});
I also plan to add the next features
- API for Svelte: request/invalidate
- React use API: request/invalidate