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

@atek-cloud/node-rpc

v4.2.0

Published

Atek RPC client/server for node.js

Downloads

5

Readme

Atek RPC

RPC library for Atek applications.

npm install @atek-cloud/node-rpc

Client

JSON-RPC

The RPC client is a "proxy object" which automatically translates methods to JSON-RPC calls. Using it is simple: create an instance with the API metadata (including the API ID) and then call methods on the object.

import { rpc } from '@atek-cloud/node-rpc'

const myApi = rpc('example.com/my-api')
await myApi.hello('World') // => {value: 'Hello World'}

Websocket

The Websocket stream is an unopinionated socket to the target service. It should be used when a custom wire protocol is required.

import { ws } from '@atek-cloud/node-rpc'

const socket = ws({api: 'example.com/my-api'})
socket.write(Buffer.from('Hello world', 'utf8'))

Server

Create an RPC server by passing the functions into a server constructor:

import { createRpcServer } from '@atek-cloud/node-rpc'

const myApiServer = createRpcServer({
  hello (who) {
    return {value: `Hello ${who}`}
  }
})

You handle RPC requests by passing them into the server's handle function:

const app = express()
app.use('/_api', express.json())
app.post('/_api/my-api', (req, res) => myApiServer.handle(req, res, req.body))
app.listen(PORT)

Request context

You can access the request, response, and body (which are passed by you into handle()) from the this of any method handler.

import { createRpcServer } from '@atek-cloud/node-rpc'

const myApiServer = createRpcServer({
  someFn () {
    // this.req - The request object
    // this.res - The response object
    // this.body - The body object
  }
})

This is particularly useful for handling authentication information which is passed by headers.

Validation and types

If you would like to add validation to your server, you can pass an object as the second parameter to the constructor which defines the expected params and responses:

import { createRpcServer } from '@atek-cloud/node-rpc'

const myApiServer = createRpcServer({
  hello (who) {
    return {value: `Hello ${who}`}
  }
}, {
  hello: {
    params: [{type: 'string'}],
    response: {type: 'object', properties: {value: {type: 'string'}}}
  }
})

If you have a class that represents a param or response, set the static .schema value on the class and then pass the constructor into the validation config:

import { createRpcServer } from '@atek-cloud/node-rpc'

export class HelloResponse {
  constructor (value) {
    this.value = value
  }
  static schema = {
    type: 'object',
    properties: {
      value: {type: 'string'}
    }
  }
}

const myApiServer = createRpcServer({
  hello (who) {
    return {value: `Hello ${who}`}
  }
}, {
  hello: {
    params: [{type: 'string'}],
    response: HelloResponse
  }
})

You may want to separate your API server's validation from the actual handler implementation. (This is recommended for any RPC API as it encourages reuse.) Here's how you create an API Server "spec" which defines the validation but not the implementation:

import { createRpcServer } from '@atek-cloud/node-rpc'

export class HelloResponse {
  constructor (value) {
    this.value = value
  }
  static schema = {
    type: 'object',
    properties: {
      value: {type: 'string'}
    }
  }
}

export function createMyApiServer (handlers) {
  return createRpcServer(handlers, {
    hello: {
      params: [{type: 'string'}],
      response: HelloResponse
    }
  })
}

// to implement:
const myApiServer = createMyApiServer({
  hello (who) {
    return new HelloResponse(`Hello ${who}`)
  }
})

If you're using Typescript want to give your client the correct types, you can pass an interface as a type parameter:

import { rpc } from '@atek-cloud/node-rpc'

export class HelloResponse {
  constructor (public value: string) {}
  static schema = {
    type: 'object',
    properties: {
      value: {type: 'string'}
    }
  }
}

export interface MyApi {
  hello (who: string): Promise<HelloResponse>
}

export function myApi () {
  return rpc<MyApi>('example.com/my-api')
}

Complete Typescript example

Here is a complete example of an API server and client module in typescript, using the recommended patterns:

import { rpc, createRpcServer } from '@atek-cloud/node-rpc'

export const ID = 'example.com/my-api'

export class HelloResponse {
  constructor (public value: string) {}
  static schema = {
    type: 'object',
    properties: {
      value: {type: 'string'}
    }
  }
}

export interface MyApi {
  hello (who: string): Promise<HelloResponse>
}

export function myApi () {
  return rpc<MyApi>('example.com/my-api')
}

export function createMyApiServer (handlers) {
  return createRpcServer(handlers, {
    hello: {
      params: [{type: 'string'}],
      response: HelloResponse
    }
  })
}

export default myApi()

Here's how a client can use this:

import myApi from '@example-com/my-api'

await myApi.hello('world') // => 'Hello world'

And here's how a server can use this:

import { createMyApiServer, HelloResponse } from '@example-com/my-api'

const myApiServer = createRpcServer({
  hello (who: string) {
    return new HelloResponse(`Hello ${who}`)
  }
})

const app = express()
app.use('/_api', express.json())
app.post('/_api/my-api', (req, res) => myApiServer.handle(req, res, req.body))
app.listen(PORT)

Configuration

Set the endpoint

Atek uses an API gateway, which this library will send requests to by default. You can change a client's destination of the API endpoint with $setEndpoint()

import { client } from '@atek-cloud/node-rpc'

const myApi = client('example.com/my-api')
myApi.$setEndpoint('http://localhost:1234/_api')

The API gateway also requires an authorization header. You can change the header with $setAuthHeader()

API

rpc(apiDesc: string|object, proto = 'http', hostname = 'localhost', port = ATEK_HOST_PORT)

Create an RPC client. The returned RPC object is a proxy object which automatically translates method-calls to RPC calls.

In typescript, you can set the interface of your RPC API by passing the type:

export interface MyApi {
  hello (who: string): Promise<string>
}

const client = rpc<MyApi>('example.com/my-api')

ws(apiDesc: string|object, proto = 'ws', hostname = 'localhost', port = ATEK_HOST_PORT)

Create a websocket stream.

getUrl(apiDesc: string|object, proto = 'http', hostname = 'localhost', port = ATEK_HOST_PORT)

Construct an endpoint URL.

rpcClient.$setEndpoint(opts: {proto?: string, hostname?: string, port?: number})

Set the endpoint for an rpc client.

rpcClient.$setAuthHeader(auth: string)

Set the auth header for an rpc client.

rpcClient.$url

The current endpoint URL of a client.

rpcClient.$desc

The current api description/metadata of a client, used by atek to route the request.

rpcClient.$auth

The current auth header.

rpcClient.$rpc(methodName: string, params: any[] = []): Promise<any>

Make an RPC call.

createRpcServer(handlers: AtekRpcServerHandlers, validators?: AtekRpcServerValidators)

Create an RPC server.

rpcServer.handle(req: http.IncomingRequest, res: http.ServerResponse, body: object)

Handle an HTTP request.