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

@source-repo/msgrpc

v1.0.2

Published

TypeScript RPC system

Downloads

199

Readme

@source-repo/msgrpc

Modular TypeScript communications and RPC system. Use msgrpc to communicate between Node.JS instances, or between a browser page and a server.

npm install @source-repo/msgrpc

msgrpc works best with TypeScript, but plain JavaScript works although it is more error prone.

High level interface

RPC server

Start by creating a server in NodeJS. The first example here uses the default transport WebSocket (socket.io) on port 3000.

const rpcServer = new RpcServer()

Add some RPC functionality

class TestRpc {
  async square(n: number) {
    return n * n
  }
}

const testRpc = new TestRpc()
rpcServer.exposeClassInstance(testRpc, 'testRpc')

RPC client

The create a RPC client in another NodeJS app, a web app or the same NodeJS app (for testing).

const rpcClient = new RpcClient()
await rpcClient.ready()

Now get a proxy for the server functionality

const proxy = await rpcClient.proxy<TestRpc>('testRpc')

And finally call the server method

proxy.remote.square(3)

Advanced usage

A RPC server can have multiple transports defined in an optional RpcServerOptions object. This can make the RPC functionality available on multiple WebSocket channels and also via MQTT messaging.

When using MQTT a network of RPC clients and servers can be easily realized. The different servers are addressed using their name property.

interface RpcServerOptions {   
    name: string
    transports: (HttpServerOptions | ExternalServerOptions | MqttServerOptions | GenericModule)[]
    useMsgPack: boolean
}

name: The server name used when routing RPC messages

transports: An array of one or more transport methods:

export interface ServerOptions {
    description: string
}

export interface HttpServerOptions extends ServerOptions {
    port: number
    https: boolean
    path: string
}

export interface ExternalServerOptions extends ServerOptions {
    server: Server
    path: string
}

export interface MqttServerOptions extends ServerOptions {
    brokerurl: string
}

useMsgPack: MsgPack is default, set to false for JSON

Low level interface

Additional transports, message formats and connections can be implemented using msgrpc Modules.

Modules

msgrpc works by plugging together modules in order to solve the desired messaging task.

A module can receive, process and send messages. In this context, sending does not mean that it goes over a network, but rather from one module to another within the same environment. Also, a message in this context can be any JavaScript value - not just strings or binary data.

By creating a chain of modules by combining the included modules, as well as creating new ones if needed, you can create a customized solution.

Using modules

A module must implement the base IGenericModule interface. This interface declares a receive function which, as the name suggests, is the function that you want to call whenever the module should receive a message.

So, to send a message to a module, you would call the receive function on that module. In reality you would usually not call this function directly - you would instead pipe two modules together.

Piping

Modules also have a function named pipe. This function will tell the module that it should send its messages to the module passed as parameter, effectively creating a connection from one module to the next.

let module1 = new MyModule()
let module2 = new MyModule()
module1.pipe(module2)

In this example, when module1 sends a message, it reaches out to module2 and calls its receive function, along with the message.

There is also a shorthand for this:

let module1 = new MyModule()
let module2 = new MyModule([module1])

This example is exactly the same as the one above, but shorter.

You can also pipe a module into a function. The function will be called for each message that the module wants to send.

let module1 = new MyModule()
module1.pipe((message) => {
    console.log('module1 wanted to send: ', message)
})

Exceptions

When a module receives a message and an exception is thrown, it is propagated back each pipe, back to the original sender. You can catch these errors either at the original sender when calling this.send, or by using the TryCatch module.

let module1 = new MyModule()
let tryCatch = new TryCatch([module1])
let module2 = new ModuleThatThrows([module2])

tryCatch.on('caught', (message, err) => {
    console.log('The error was caught!')
})

In this example, if module2 would throw an exception, the error would not be propagated back to module1. Instead, the event listener would fire and we would see an output in our log.

The receive function of a module can be asynchronous (return a Promise), and if the promise rejects, it would also be propagated in the same way as an exception would.

Creating modules

To create a module, extend the base GenericModule class (or technically, the base IGenericModule interface). The base class takes care of piping.

To send a message from your module to all pipes, use this.send(message). This is a protected method only accessible from within the module instance.

Take a look within the source code for examples on how to create modules.

Utility modules

There are a few basic utility modules included with msgrpc. These are:

  • Converter - Takes a function as a parameter. For each received message, the function is called and the return value is sent to each piped module.
  • Filter - Takes a function which returns a boolean as a parameter. For each received message, the function is called and if the function returns a true, the message is sent to each piped module. If not, the message is not sent.
  • Switch - Allows messages to be sent to a specific target.
  • Targeter - Adds a target to a message (suitable for sending to a switch).
  • TryCatch - Catches exceptions (more above).

There are also a few more complex modules included:

  • RpcServer / RpcClient - Remote procedure call.
  • SocketIoTransport - WebSocket for both Node.JS and the browser.
  • MqttTransport - MQTT transport

WebSocket example

Let's look at a real-world example.

import { SocketIoClientTransport } from '@source-repo/msgrpc'

// Create a WebSocket client
let transport = new SocketIoClientTransport('ws://localhost:3000')

transport.pipe((message) => {
    console.log('Received message: ' + message)
})

transport.receive('Sending this message over WS')

The SocketIoClientTransport will pass a message through the pipe each time it receives a message over the WebSocket connection. This example will open up a WebSocket connection to localhost, send a message and log each incoming message.

RPC over WebSocket

The power of modules is shown when you want to process messages. Here is an example of an RPC server using WebSocket.

import { SocketIoClientTransport, Converter, RpcServerHandler, TryCatch } from '@source-repo/msgrpc'

// Create a server which listents on 0.0.0.0:3000
const server = new SocketIoClientTransport('ws://localhost:3000)

// Parse each incoming message using
const parser = new Converter([server], message => {
    return JSON.parse(message.toString()
})

// Send each parsed message to an RPC server
const rpcServerHandler = new RpcServerHandler('server', [parser])

// Serialize each outgoing message using JSON.stringify
const stringifier = new Converter([rpcServer], message => {
    return JSON.stringify(message)
})

// Try to send the message back. If we fail (probably the client disconnected), do nothing.
const tryCatch = new TryCatch([stringifier])
tryCatch.pipe(server)

// Expose a function
rpcServerHandler.manageRpc.exposeObject({
    Hello: () => {
        return 'World!'
    }
}, 'MyRpc')

And here is the client:

import { SocketIoClientTransport, JsonParser, RpcClienthandler, JsonStringifier } from '@source-repo/msgrpc'

// Create a WebSocket client which connects to the server
// For use in broser, use BrowserWebSocketTransport instead
let transport = new SocketIoClientTransport('ws://localhost:3000')

// Parse each incoming message
let parser = JsonParser([transport])

// Send each parsed message to a RPC client
let rpcClientHandler = new RpcClientHandler('client', [parser])

// Serialize each outgoing message
let stringifier = JsonStringifier([rpcClient])
stringifier.pipe(transport)

// Create a JavaScript proxy object which allows us to call the RPC functions. The service name should match the exposed object on the server ("MyRpc").
let proxy = rpcClientHandler.proxy('MyRpc')

// Should output Hello World!
console.log('Hello ' + await proxy.remote.hello())