RPC
RPCs can accept an optional argument and may return a value, depending on the type of the RPC.
To define arguments and/or responses, pass the message type to the RPC decorator:
import { UnaryCall } from './rpc.decorator';
import { Payload } from './param.decorator';
@Service()
class Service {
@UnaryCall({ argument: MyArgument, return: MyReturn })
private async MyRpc(@Payload() payload: MyArgument): Promise<MyReturn> {
// [...]
}
}
In this example, MyArgument is a message decorated with a @Message() decorator. The same applies to MyReturn.
Types of RPCs
Unary call
Unary calls are the simplest RPC type. I can receive an input and produce an output.
To produce a result, just return the value from the handler, but be careful to provide all the required fields.
It can be defined using the @UnaryCall() decorator.
Example
import { UnaryCall, Service } from '@lumineer/core';
@Service()
class Service {
@UnaryCall({ argument: /* Omitted */, return: /* Omitted */ })
private async UnaryCallHandler() {
// [...]
}
}
Client stream
It can be defined using the @ClientStreamCall() decorator.
Here we expect a stream coming from the client. To handle this, we can iterate over the readable stream provided by the @Stream() decorator.
Example
import { ClientStreamCall, Service } from '@lumineer/core';
import type { Readable } from 'node:stream';
@Service()
class Service {
@ClientStreamCall({ argument: /* Omitted */, return: /* Omitted */ })
private async OtherClientStreamCallHandler(@Stream() stream: Readable) {
for await (const chunk of stream) {
// Do something with the chunk
}
// Optionally return a value, like done on the unary call example
}
}
Server Stream
Server stream can be defined by using the @ServerStreamCall() decorator.
To handle this type we can use the writable stream provided by the @Stream() decorator.
Example
import { ClientStreServerStreamCall, Service, Stream } from '@lumineer/core';
import type { Writable } from "node:stream";
@Service()
class Service {
constructor(private readonly repository: MyRepository) { }
@ServerStreamCall({ argument: /* Omitted */, return: /* Omitted */ })
private async OtherServerStreamCallHandler(@Stream() stream: Writable) {
const dos = await this.repository.getDocs();
for (const doc of docs) {
stream.write(doc)
}
stream.end();
}
}
Bidirectional stream
Bidirectional RPCs use the @BidirectionalStreamCall() decorator, it relies on Duplex streams (it acts like a Readable and a Writable stream at the same time) to handle these calls. Use the stream parameters value, provided by @Stream().
Example
import { BidirectionalStreamCall } from '@lumineer/core';
import type { Duplex } from "node:stream";
@Service()
class Service {
@BidirectionalStreamCall()
private async BidirectionalStreamCallHandler(@Stream() stream: Duplex) {
for await(const chunk of stream) {
const doc = await this.repository.getDoc(chunk)
stream.write(doc);
}
stream.end();
}
}
Parameters
Payload
Payload is the data that may come with a call if the argument option was supplied to the call decorator.
To access the request payload, use the @Payload() decorator.
Example
import { UnaryCall, Payload, Service } from '@lumineer/core';
@Service()
class MyService {
@UnaryCall({ argument: MyPayload })
private async DoFoo(@Payload() payload: MyPayload) {
// Process the payload
}
}
Metadata
Metadata work like headers in a REST API.
The metadata instance is shared across layers, so it is a good place to put relevant information to other parts of your project, like authorization and user details.
To access the call's metadata, use the @Metadata() decorator.
Example
import { UnaryCall, Metdata, MetadataContent, Service } from '@lumineer/core';
@Service()
class MyService {
@UnaryCall()
private async DoFoo(@Metadata() metadata: MetadataContent) {
const apiKey = metadata.get('authorization');
// Process the payload
}
}
Stream
Streams are present in all call types except for unary calls (as the name suggests).
To access the stream instance, use the @Stream() decorator.
Each call type has a different stream type, be careful to use the correct one:
- Client streams (
@ClientStreamCall()): usesReadablestreams; - Server streams (
@ServerStreamCall()): usesWritablestreams; - Bidirectional streams (
@BidirectionalStreamCall()): usesDuplexstreams;
For more information about streams, check Node.js Stream API.
Example
import { ClientStream, Service, Stream } from '@lumineer/core';
import type { Readable } from "node:stream"
@Service()
class MyService {
@ClientStreamCall()
private async DoFoo(@Stream() stream: Readable) {
// Process the data
}
}