New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@pawsteam/ts-jsonrpc-server

Package Overview
Dependencies
Maintainers
3
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@pawsteam/ts-jsonrpc-server

A framework for easily building JSONRPC simple servers in TS

  • 0.2.0
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
1
decreased by-80%
Maintainers
3
Weekly downloads
 
Created
Source

Typescript JSONRPC server

This is a very simple & straight forward way of creating JSONRPC serices. It can also emit events that other services can subscribe to, in order to create an API event-based infrastructure.

Features

  • Input validators
  • Input transformers
  • Response serializer

Installation

For MacOS Catalina, please follow this link

All the rest, npm install @pawsteam/ts-jsonrpc-server.

Basic usage

@AppConfigDecorator({
    port: 2000,
    services: [SomeService],
    genericValidators: [
        new HeaderExistsValidator('content-type'),
        new HeaderValueContainsValidator('user-agent', 'Mozilla/5.0')
    ]
})
class AppMain {
}

@ServiceDecorator({path: '/hotels'})
class HotelsService {
    listHotels(): Promise<any> {
        return new Promise((resolve, reject) => {
            // fetch data.then((data) => {
            resolve(data);
            // })
        })
    }
}

Later, when running this app, you can make a JSONRPC request to the methods exposed by this endpoint:

request({
    method: 'post',
    url: 'http://localhost:2000/hotels'
    json: {
        id: Math.round(Math.random() * 1000),
        jsonrpc: '2.0',
        method: 'listHotels',
        params: {}
    }
}, (error, response, body) => {
    expect(body).toMatchObject({
        id: ...,
        jsonrpc: '2.0',
        result: {data}
    })
})

The Framework

The framework is aimed at making life easier when writing JSON-RPC servers. It is Promise-based, so everything returns a promise, with very few exceptions (validators). It is based on stages/hooks in the life of a request:

The life stages of a request can be intercepted with special classes that must implement specific interfaces.

Input validators

They check the request for required data and types. It's a place to define what parameters are needed for a method to be called.

Any number of Input Validators can be assigned for one method. Generic ones as well;

#### Generic validators

Generic validators are set on the app level, and they will be triggered on all requests for this app.

```typescript
@AppConfigDecorator({
    port: 2000,
    services: [SomeService],
    genericValidators: [
        new HeaderExistsValidator('content-type'),
        new HeaderValueContainsValidator('user-agent', 'Mozilla/5.0')
    ]
})
export class AppMain {
}
export class HeaderExistsValidator implements ValidatorInterface {
    headerName: string;
    constructor(headerName: string) {
        this.headerName = headerName;
    }
    validate(params: any, request): boolean {
       return !!request.headers[this.headerName];
    }
}
```

The input validators implement ValidatorInterface and return a boolean value.

Method specific validators
@ValidatorDecorator(new HeaderExistsValidator('x-content-type'))
private method1() {
    return Promise.resolve(true);
}

Similarily to the example above, here we define the validator for this particular method.

Input transformers

They transform the request data into objects that are passed to the methods. Any number of input transformer can exist per method.

  1. The transformer returns a promise of one parameter type.

  2. The order of validators will give the order of parameters in the method call.

  3. The transformers implement the TransformerInterface and return a Promise to the type of object they are for.

    export class BucketTransformer implements TransformerInterface {
    /**
    * The ParsedRpcObjectType is an object created from the body of the request, parsed,
    * and put into the form of a JSONRPC object:
    * {
    *    id: <id>,
    *    method: <methodname>,
    *    jsonrpc: '2.0',
    *    params: {...}
    * }
    */
       transform(request: IncomingMessage, jsonrpcObj: ParsedRpcObjectType): Promise<BucketType> {
           const newBucket = new BucketType(jsonrpcObj.params.path);
           return Promise.resolve(newBucket);
       }
    }
    

    The reason this method needs to return a promise is because we might need to fetch additional data in order to create the object. For instance, we might need to query a DB.

    Validation can also be done in the transformer. If the transformer rejects the promise or throws, the transformation is interpreted as failed, so validation failed.

    To use a transformer, you simply instantiate a new instance of your transformer:

    @TransformerDecorator(new CarTransformer())
    @TransformerDecorator(new EngineTransformer())
    method3(car: CarType, engine: EngineType, request: IncomingMessage, response: ServerResponse) {
        return Promise.resolve(car.wheels * engine.power);
    }
    

The method itself

This is where the app logic lives

It must also return a promise of any type of object, this object will be serialized/processed if needed, then replied to the user

```
@ValidatorDecorator(new BucketValidator())
@TransformerDecorator(new BucketTransformer())
createBucket(bucket: BucketType, context: RequestContextType): Promise<any> {
   return Promise.resolve({created: bucket.path});
}
```

Response serializer

This is where you can decide what exactly ends up in the response. Everything in the response can be changed at this step.

The serializers must implement SerializerInterface. They must return an object of type JsonRpcResponseType.

// example of serializer - removes all keys that contain the string 'test' from the response.
class RemoveTestsSerializer implements SerializerInterface {
   serialize(serializeParams: SerializerParameterType): JsonRpcResponseType {
       for (const key in serializeParams.objectToBeReturned) {
           if (key.indexOf('test') !== -1) {
               delete serializeParams.objectToBeReturned[key];
           }
       }
       return serializeParams.objectToBeReturned;
   }
}
@SerializerDecorator(new RemoveTestsSerializer())
method4(req, resp) {
    console.log('METHOD 4 called');
    return Promise.resolve({test2: 'test', ihaveatesttorun: 'Ihaveatesttorun', someKey: 16});
}

Events

Sending events

One of the core functionalities of the framework is to make sure that methods can asynchronously emit events, on API calls.

The messaging paradigm used is PUB/SUB. This means that the events are published and lost if no subscribers are present. On the other hand, if multiple subscribers subscribe to the same event, they will all receive the same events.

Easiest way to include the events functionality is through a simple decorator.

// step 1 - define the connection & transport
// for now only Rabbit transporters are allowed
@AppConfigDecorator({
    messaging: {
        serviceName: 'NewService',
        host: 'localhost'
    },
    port: 2000,
    services: [NewService]
})
class app {
}
// step 2 - in the service method, mention the decorator
@ServiceDecorator({
    path: '/newpath',
    serializer: new RemoveTestsSerializer()
})
export class NewService implements ServiceInterface {
    EventEmitterDecorator()
    method2() {
        return Promise.resolve({sum: 8, product: 15});
    }
}

By default, this will render a message similar to this

{
    eventType: 'method2',
    eventParams: {
        sum: 8,
        product: 15
    }
}

Alternatively, you can also select and change the keys that will be published in the event, by passing a json parameter to the EventEmitterDecorator()

@EventEmitterDecorator({
    sum: '',    // an empty string means the key's name will be used in the event keys
    prod: 'product'
})
method3() {
    return Promise.resolve({sum: 5+3, prod: 5*3, excluded: 5-3});
}

The above example would send a message on the queue which looks like this:

{
    sum: 8,
    product: 15
}

Notice how the returned keys are sum, prod and excluded, and the event only has sum and product.

FAQs

Package last updated on 15 Feb 2020

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc