@pawsteam/ts-jsonrpc-server
Advanced tools
Comparing version 0.1.2 to 0.2.0
{ | ||
"name": "@pawsteam/ts-jsonrpc-server", | ||
"version": "0.1.2", | ||
"version": "0.2.0", | ||
"description": "A framework for easily building JSONRPC simple servers in TS", | ||
"main": "dist/server.js", | ||
"types": "dist/server.d.ts", | ||
"dependencies": {}, | ||
"dependencies": { | ||
"amqplib": "^0.5.5", | ||
"reflect-metadata": "^0.1.13" | ||
}, | ||
"devDependencies": { | ||
@@ -29,5 +32,7 @@ "@types/jest": "^24.0.25", | ||
"test": "npm run test:units && npm run test:e2e", | ||
"watch:units": "node node_modules/.bin/jest --passWithNoTests --config jest-units.config.js --watch" | ||
"watch": "node node_modules/.bin/jest --passWithNoTests --config jest-units.config.js --watch" | ||
}, | ||
"files": ["dist/**/*"], | ||
"files": [ | ||
"dist/**/*" | ||
], | ||
"repository": { | ||
@@ -34,0 +39,0 @@ "type": "git", |
204
README.md
# 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](https://github.com/nodejs/node-gyp/blob/master/macOS_Catalina.md) | ||
All the rest, ```npm install```. | ||
All the rest, ```npm install @pawsteam/ts-jsonrpc-server```. | ||
## Basic usage | ||
```typescript | ||
@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: | ||
```typescript | ||
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 will return a promise, except for validators. | ||
It is revolving around stages/hooks in the life of a request: | ||
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: | ||
![](docs/Request%20Diagram.svg) | ||
1. Input validators | ||
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({ | ||
@@ -31,3 +94,2 @@ port: 2000, | ||
export class AppMain { | ||
... | ||
} | ||
@@ -40,3 +102,3 @@ export class HeaderExistsValidator implements ValidatorInterface { | ||
validate(params: any, request): boolean { | ||
... | ||
return !!request.headers[this.headerName]; | ||
} | ||
@@ -46,12 +108,22 @@ } | ||
The input validators implement ValidatorInterface and return a boolean value. | ||
#### Method specific validators | ||
2. Input transformers - they transform the request data into objects that are passed to the methods | ||
Any number of input transformer can exist per method. | ||
```typescript | ||
@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. | ||
It must return a promise of one parameter. | ||
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. | ||
The order of validators will give the order of parameters in the method call. | ||
The transformers implement the TransformerInterface and return a Promise to the type of object they are for. | ||
``` | ||
@@ -91,5 +163,7 @@ export class BucketTransformer implements TransformerInterface { | ||
3. The method itself - this is where the app logic lives | ||
### The method itself | ||
It must also return a promise of a HttpResponse type of object, this object will be returned to the user. | ||
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 | ||
@@ -104,4 +178,98 @@ ``` | ||
4. TODO: Response serializer - this is where you can decide what exactly ends up in the response. | ||
### 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. | ||
In the near future, a functionality to make the server emit events will be implemented. | ||
The serializers must implement **SerializerInterface**. | ||
They must return an object of type JsonRpcResponseType. | ||
```typescript | ||
// 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. | ||
```typescript | ||
// 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 { | ||
} | ||
``` | ||
```typescript | ||
// 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 | ||
```typescript | ||
{ | ||
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() | ||
```typescript | ||
@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: | ||
```typescript | ||
{ | ||
sum: 8, | ||
product: 15 | ||
} | ||
``` | ||
Notice how the returned keys are _sum, prod and excluded_, and the event only has _sum and product_. | ||
46950
271
2
+ Addedamqplib@^0.5.5
+ Addedreflect-metadata@^0.1.13
+ Addedamqplib@0.5.6(transitive)
+ Addedbitsyntax@0.1.0(transitive)
+ Addedbluebird@3.7.2(transitive)
+ Addedbuffer-more-ints@1.0.0(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addeddebug@2.6.9(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedisarray@0.0.1(transitive)
+ Addedms@2.0.0(transitive)
+ Addedquerystringify@2.2.0(transitive)
+ Addedreadable-stream@1.1.14(transitive)
+ Addedreflect-metadata@0.1.14(transitive)
+ Addedrequires-port@1.0.0(transitive)
+ Addedsafe-buffer@5.1.2(transitive)
+ Addedstring_decoder@0.10.31(transitive)
+ Addedurl-parse@1.4.7(transitive)