The RPC Module

Describe service interactions using effects, call your server from the browser and forget that API even exists.

Installation

yarn add express @matechs/express @matechs/rpc @matechs/http-client
yarn add -D @types/express

// one http implementation like
yarn add @matechs/http-client-libcurl

Module

import { effect as T } from "@matechs/effect";
import { FunctionN } from "fp-ts/lib/function";
import * as H from "@matechs/http-client";
import * as E from "@matechs/express";
import { array } from "fp-ts/lib/Array";
import { right } from "fp-ts/lib/Either";
import { Exit } from "@matechs/effect/lib/original/exit";
import { Do } from "fp-ts-contrib/lib/Do";
import { isSome } from "fp-ts/lib/Option";

// environment entries
const clientConfigEnv: unique symbol = Symbol();
const serverConfigEnv: unique symbol = Symbol();

// describe an environment that supports RPC
export type Remote<T> = Record<
  keyof T,
  Record<string, FunctionN<any, T.Effect<any, any, any>>>
>;

// describe a client configuration
interface ClientConfig<M, K extends keyof M> {
    [k in K]: {
      baseUrl: string;
    };
  };
}

// create a client configuration
export function clientConfig<M, K extends keyof M>(
  _m: M,
  k: K
): (c: ClientConfig<M, K>[typeof clientConfigEnv][K]) => ClientConfig<M, K>

// describe a server configuration
interface ServerConfig<M, K extends keyof M> {
    [k in K]: {
      scope: string;
    };
  };
}

// create a server configuration
export function serverConfig<M, K extends keyof M>(
  _m: M,
  k: K
): (c: ServerConfig<M, K>[typeof serverConfigEnv][K]) => ServerConfig<M, K> 

// create a client for module M and entry K
export function client<M extends Remote<M>, K extends keyof M>(m: M, k: K): Client<M, K> 

// merge environment requirements for the whole module
export type Runtime<M> = M extends {
  [h: string]: (...args: any[]) => T.Effect<infer Q & E.RequestContext, any, any>;
}
  ? Q
  : never;

// request object
interface RPCRequest {
  args: unknown[];
}

// response object
interface RPCResponse {
  value: Exit<unknown, unknown>;
}

// bind module M and entry K to express
export function bind<M extends Remote<M>, K extends keyof M>(
  m: M,
  k: K
): T.Effect<
  E.ExpressEnv & Runtime<M[K]> & ServerConfig<M, K> & M,
  T.NoErr,
  void
>

Usage

Notes

You can wire as many modules as you need! Keep in mind both arguments and return types (error/success) must be serializable in order for RPC to do its magic.

Full example with complete separation of server/client and authentication available at https://github.com/mikearnaldi/matechs-effect/tree/master/packages/rpc/demo.

Last updated

Was this helpful?