npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

kiss-rpc

v1.1.5

Published

Library for Typed RPC via Typescript heavily relying on general types in TypeScript

Downloads

348

Readme

KISS-RPC

KISS-RPC (Keep It Stupid Simple RPC) is a simple and lightweight library for implementing remote procedure call (RPC) in TypeScript/JavaScript. It uses a custom JSON based format for messages, similar to JSON-RPC, but with fewer overheads and optimized for high throughput. It works both in browser and Node.js environments.

Transport

KISS-RPC is designed to be transport agnostic, so you can use it with any transport layer, such as WebSocket, HTTP, TCP, stdio, message brokers... you name it. Also, the protocol is very simple, so you can implement your own client or server in any language.

Installation

npm install kiss-rpc 

Type Safety

The main goal of KISS-RPC is to provide a Type Safe RPC library using TypeScript built-in type primitives. It also provides a simple and easy to use API for both client and server.

Protocol Specification

KISS-RPC has four types of messages: Request, Response, Error Response and Notification. All messages are encoded in JSON format. The message type is determined by the first element of the array. The following table shows the message format for each type.

Request        [MessageType, Id, Method, Params]
Response       [MessageType, Id, Result]
Error Response [MessageType, Id, ErrorResult]
Notification   [MessageType, Method, Params]
  • Request is a stateful message, which requires a response.
  • Response or Error Response are one of two possible outcomes of request.
  • Notification is a stateless message, which does not require a response.

Integration

Library provides two simple classes in order to exchange messages that are hooks to/from the transport layer:

  • instance.registerToTransportCallback allows to register user defined callback when library needs to send a message to the other side.
  • instance.handleMessage allows to pass a message received from the other side to the library.
type ServerRpcMethods = { 
  add: (a: number, b: number) => number;
};

const client = new KissRpc<ServerRpcMethods>({
  requestTimeout: 5000
});

client.registerToTransportCallback((message) => {
  console.log(message);
  // Logic to send message to transport.
});

myTransport.on('message', (message : string) => {
  client.fromTransport(message);
});

Usage Example

When Kiss-RPC is instantiated it provide a user to define three generic types with following signature:

class KissRpc<RequestMethods, HandlersMethods = RequestMethods, AppDataType = undefined>
  • RequestMethods is a type that defines methods that can be called using request or notify methods on remote party. Mandatory argument.
  • HandlersMethods is a type that defines methods that we can handle using registerHandler. Optional argument. If not provided, it will be the same as RequestMethods, which means that we may handle the same set of methods as we can call on remote.
  • AppDataType is a type that defines additional data that can be passed to the library and can be accessed from the handlers. Optional argument. If not provided, it will be undefined. Useful when you want to pass some data to the handlers i.e. some kind of context like user session, or socket information.

All examples omit transport layer implementation for simplicity.

Simple Example

// Define methods that can be called on remote party.
type ServerRpcMethods = {
  add: (a: number, b: number) => number;
  log: (message: string) => void;
};
// Create client and server instances.
const client = new KissRpc<ServerRpcMethods>({
  // Optional. Default value is 5000.
  // If no response is received within this time, 
  // request will be rejected.
  requestTimeout: 5000
});

const server = new KissRpc<ServerRpcMethods>({
  requestTimeout: 5000
});

// Register callback that will be called when library needs to send a message to the other side.
client.registerToTransportCallback((message) => {
  // Logic to send message to transport. 
  // In this example we just forward it to the server instance.
  server.fromTransport(message);
});

server.registerToTransportCallback((message) => {
  client.fromTransport(message);
});

// Register handlers on the server. 
// Typseript will make sure that you are following ServerRpcMethods interface.
server.registerHandler('add', (a, b) => a + b);
server.registerHandler('log', (message) => console.log(message));

// Call method on the server using request.
// Typseript will make sure that you are following ServerRpcMethods interface.
const result = await client.request('add', [1, 2]);

// Call method on the server using notify. No response is expected.
// Typseript will make sure that you are following ServerRpcMethods interface.
client.notify('log', 'Hello World!');

App Data

KISS-RPC provides a way to pass additional data to the handlers. This can be useful when you want to pass some kind of context like user session, or socket information.

type ServerRpcMethods = {
  add: (a: number, b: number) => number;
};

type User = {
  userId: string;
  userRole: string;
  isAuthenticated: boolean;
};

const client = new KissRpc<ServerRpcMethods>({
  requestTimeout: 5000
});

// We are adding appData to the server instance.
// this will force us to pass appData to the registerHandler method and handleMessage.
// Typseript will make sure that you are following AppData interface.
const server = new KissRpc<ServerRpcMethods, ServerRpcMethods, User>({
  requestTimeout: 5000
});

client.registerToTransportCallback((message) => {
  // Let's assume that we are receiving user data from the transport layer.
  const user: User = { userId: '1', userRole: 'admin', isAuthenticated: true };
  server.fromTransport(message, user);
});

server.registerToTransportCallback((message) => {
	client.fromTransport(message);
});
// We are passing appData to the registerHandler method.
// Notice that we don't need to annotate this in ServerRpcMethods.
// But library will make sure to fore us to pass appData to the handleMessage method.
server.registerHandler('add', (a, b, user) => {
  // Error thrown from the handler will be returned as ErrorResult.
  if (!user.isAuthenticated) {
    throw new Error('User is not authenticated');
  }
  if (!user.userRole !== 'admin') {
    throw new Error('User is not affiliated');
  }	

  return a + b
});

const result = await client.request('add', [1, 2]);

Handler Guards

KISS-RPC provides a way to protect your handlers from being executed. This can be seen as a middleware for the handlers. Each handler can have one or more guards. Guards are functions that are executed before the handler. If any of the guards returns false, the handler will not be executed. Guards are executed in the order they are registered. There are three types of guards:

  • Guard - a function callback function that will be called with params and appData.
  • ParamGuard - a function callback function that will be called with params only.
  • AppDataGuard - a function callback function that will be called with appData only.

If any of guards throws an error, the error will be returned as ErrorResult, and handler will not be executed.

Guards can also be used as interceptors, can be useful for logging.

type ServerRpcMethods = {
  add: (a: number, b: number) => number;
};

type User = {
  userId: string;
  userRole: string;
  isAuthenticated: boolean;
};

const client = new KissRpc<ServerRpcMethods>({
  requestTimeout: 5000
});

const server = new KissRpc<ServerRpcMethods, ServerRpcMethods, User>({
  requestTimeout: 5000
});

client.registerToTransportCallback((message) => {
  const user: User = { userId: '1', userRole: 'admin', isAuthenticated: true };
  server.fromTransport(message, user);
});

server.registerToTransportCallback((message) => {
  client.fromTransport(message);
});

function logRequest(a: number, b: number, user: User) {
  console.log(`User ${user.userId} is calling add method with params ${a} and ${b}`);
}

function validateParams(a: number, b: number) {
  if (params[0] < 0 || params[1] < 0) {
    throw new Error('Params must be positive');
  }
}

function validateUser(user: User) {
  if (!user.isAuthenticated) {
    throw new Error('User is not authenticated');
  }
  if (!user.userRole !== 'admin') {
    throw new Error('User is not affiliated');
  }
}

// registerHandler retuns an instance of handler, which can be used to register guards.
server.registerHandler('add', (a, b, user) => {
  return a + b
}).addGuard(validateUser)
  .addParamsGuard(validateParams)
  .addAppDataGuard(logRequest);

const result = await client.request('add', [1, 2]);

Error Handling

KISS-RPC provides an Error class that can be used to return custom errors. You can throw any error from the handler, and it will be returned as ErrorResult. All the errors from the library are instances of KissRpcError class. If you throw from handler, its message will be wrapped in KissRpcError as errorMessage.