0.1.3 • Published 2 years ago

@iamdimka/base-rpc v0.1.3

Weekly downloads
-
License
ISC
Repository
-
Last release
2 years ago

@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