0.0.8 • Published 2 years ago

slim-rpc v0.0.8

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

Slim-RPC

Logo

Motivation

Trpc is a really great library! I wanted to see if I can develop it's core feature and DX my self and was surprised by the short amount of code it took and how well it worked for me So I decided to share it with the world.

Server

Augmenting the RpcContext

the RpcContext is an object that is being created for each RPC function handler invocation. In order to have intellisense available for each RPC function you will need to use Typescript augmentation for that in your server project root create a d.ts file and add inside RpcContext whatever you want

import { RpcContext } from "slim-rpc/lib/cjs/models";
import { UserService } from "./users/user.service";
import { AccountsService } from "./accounts/accounts.service";
declare module "slim-rpc/lib/cjs/models" {
  interface RpcContext {
    user_id:string;
    org_id:string;
    services: {
      users: UserService;
      accounts:AccountsService
    };
  }
}

Usage example

In this example we use express as a web framework and we have 3 modules

  • auth
  • users
  • accounts
// server/index.ts
import express from 'express'
import { create_rpc_server, RpcExpressAdapter } from "slim-rpc";
import { auth } from './auth/auth.rpc'
import { accounts } from './accounts/accounts.rpc'
import { create_accounts_service } from './accounts/accounts.service'
import { users } from './users/users.rpc'
import { create_user_service } from './users/users.service'

const app = express();
create_rpc_server({
  web_framework: RpcExpressAdapter(app),
  routes: {
    auth,
    accounts,
    users,
  },
  create_context: async (req) => {
    return {
      org_id:req.headers['org-id'],
      users_id:req.headers['user-id'],
      services: {
        users: create_user_service(req),
        users: create_accounts_service(req),
      },
    };
  },
});
// server/users/users.rpc.ts
import { RPC } from "slim-rpc";
import {z} from 'zod'


const user_scheme = z.object({
  name: z.string(),
  age: z.number(),
});

const list = RPC<{ count: number }, User[]>(
  z.object({ count: z.number().min(6) }),
  async ({ count }, { ctx }) => {
    const users_col = await ctx.services.users();
    const users = await users_col.list();
    return users.slice(0, count);
  }
);

const update = RPC<User, User[]>(
  user_scheme,
  async ({ age, name }, { ctx }) => {
    const users_col = await ctx.services.users();
    await users_col.create(name, age);
    const users_state = await users_col.list();
    return users_state;
  }
);

const create = RPC<{ name: string; age: number }, { id: string }>(
  user_scheme,
  async ({ age, name }, { ctx }) => {
    const users_col = await ctx.services.users();
    const id = await users_col.create(name, age);
    return { id };
  }
);

const remove = RPC<{ id: string }, User[]>(
  z.object({ id: z.string() }),
  async ({ id }, { ctx }) => {
    const users_col = await ctx.services.users();
    await users_col.remove(id);
    const users = await users_col.list();
    return users;
  }
);

export const users = {
  list,
  update,
  create,
  remove,
};

Error handling

input validation errors (400) are handled by the library using the error msgs from the zod scheme parser other errors will be returned as status 500 with the massage taken from the error object. 401 & 403 for the time being I suggest that those errors will be handled by middlewares before Slim-RPC is handling them. for ex when using express:

// handle 401
app.use(async (req,req,next)=>{
  if(req.path === '/login'){
    next();
  }else{
    const ia_authorized = await check_is_auth_user(req);
    if(!is_authorized){
      res.status(401).send('user is not authorized')
    }else{
      next();
    }
  }
})
// handle 403
// I want the library to have the RPC function handle the role based calls
// will be on the road map & impl soon I hope
app.use(async (req,req,next)=>{
  if(req.path == '/login'){
    next();
  }else{
    const role = extract_role(req);
    const is_allowed = check_is_user_allowed(role,req.path);
    if(is_allowed){
      next()
    }else{
      res.status(403).send("user doesn't have enough permission to perform the action")
    }
  }
})
// then init SlimRPC

Shared Router model

// router.model.ts
import { accounts } from "./server/accounts/accounts.rpc";
import { users } from "./server/users/users.rpc";
import {auth} from './server/auth/auth.rpc'

const appRouter = {
  auth,
  accounts,
  users,
};

export type AppRouter = typeof appRouter;

Client

Usage example

// client/state.ts
import { AppRouter } from "../router.model";
import { create_client,set_rpc_client_config } from "slim-rpc";

const client = create_client<AppRouter>(env.base_url);
const all_clients = ref([]); // vuejs example

const init = async(name:string,pass:string)=>{
    // get the authorization Bearer by login
    const auth_res = await client.auth.login({name,pass})
    if(auth_res.type == 'success'){
       set_rpc_client_config({
          headers: {
            authorization: auth_res.value,
          },
        });
      // get a list of 20 users
      const res = await client.users.list.query({count:20})  // full intellisense !
      if(res.type == 'success'){
         all_clients.value =  res.value;
      }
    }
}
// ...

Error handling

the RpcResponse response object for each call is of the form:

interface RpcSuccessResponse<T> {
  type: "success";
  value: T;
}

interface RpcErrorResponse {
  type: "error";
  code: number;
  reason?: string;
}

export type RpcResponse<T> = RpcSuccessResponse<T> | RpcErrorResponse;

so each error will be of type == 'error' and will have at least a code value.

Roadmap

  • Handle 401 globally
  • Enable handling of 403 (role based) within each RPC call definition
  • koa and fastify adapters
  • Batching mechanism for all in one go client-server round trip
  • Canceling requests mechanism
  • Server Sent events mechanism

Contributions

Not yet

0.0.8

2 years ago

0.0.5

2 years ago

0.0.4

2 years ago

0.0.7

2 years ago

0.0.6

2 years ago

0.0.3

2 years ago

0.0.2

2 years ago

0.0.1

2 years ago