UNPKG

@dolittle/sdk.services

Version:

Dolittle is a decentralized, distributed, event-driven microservice platform built to harness the power of events.

166 lines (154 loc) 7.3 kB
// Copyright (c) Dolittle. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. import * as grpc from '@grpc/grpc-js'; import { Observable, Subject } from 'rxjs'; import { concatMap } from 'rxjs/operators'; import { Cancellation } from '@dolittle/sdk.resilience'; import { CouldNotConnectToRuntime } from './CouldNotConnectToRuntime'; import { ClientStreamMethod, DuplexMethod, ServerStreamMethod, UnaryMethod } from './GrpcMethods'; import { isGrpcError } from './GrpcError'; /** * Performs a unary call. * @param {grpc.Client} client - The Runtime client. * @param {UnaryMethod<TArgument, TResponse>} method - The method to call. * @param {TArgument} argument - The argument to send to the server. * @param {Cancellation} cancellation - Used to cancel the call. * @returns {Observable<TResponse>} The response from the server. * @template TArgument - The type of the argument. * @template TResponse - The type of the response. */ export function reactiveUnary<TArgument, TResponse>(client: grpc.Client, method: UnaryMethod<TArgument, TResponse>, argument: TArgument, cancellation: Cancellation): Observable<TResponse> { const subject = new Subject<TResponse>(); const metadata = new grpc.Metadata(); const call = method.call(client, argument, metadata, {}, (error: grpc.ServiceError | null, message?: TResponse) => { if (error) { subject.error(getErrorFrom(error, client.getChannel().getTarget())); } else { subject.next(message); subject.complete(); } }); handleCancellation(call, cancellation); return subject; } /** * Performs a client streaming call. * @param {grpc.Client} client - The Runtime client. * @param {ClientStreamMethod<TRequest, TResponse>} method - The method to call. * @param {Observable<TRequest>} requests - The requests to send to the server. * @param {Cancellation} cancellation - Used to cancel the call. * @returns {Observable<TResponse>} The response from the server. * @template TRequest - The type of the argument. * @template TResponse - The type of the response. */ export function reactiveClientStream<TRequest, TResponse>(client: grpc.Client, method: ClientStreamMethod<TRequest, TResponse>, requests: Observable<TRequest>, cancellation: Cancellation): Observable<TResponse> { const subject = new Subject<TResponse>(); const metadata = new grpc.Metadata(); const stream = method.call(client, metadata, {}, (error: grpc.ServiceError | null, message?: TResponse) => { if (error) { subject.error(getErrorFrom(error, client.getChannel().getTarget())); } else { subject.next(message); subject.complete(); } }); handleCancellation(stream, cancellation); handleClientRequests(stream, requests, subject, client.getChannel().getTarget()); return subject; } /** * Performs a server streaming call. * @param {grpc.Client} client - The Runtime client. * @param {ServerStreamMethod<TArgument, TResponse>} method - The method to call. * @param {TArgument} argument - The argument to send to the server. * @param {Cancellation} cancellation - Used to cancel the call. * @returns {Observable<TResponse>} The responses from the server. * @template TArgument - The type of the argument. * @template TResponse - The type of the response. */ export function reactiveServerStream<TArgument, TResponse>(client: grpc.Client, method: ServerStreamMethod<TArgument, TResponse>, argument: TArgument, cancellation: Cancellation): Observable<TResponse> { const subject = new Subject<TResponse>(); const metadata = new grpc.Metadata(); const stream = method.call(client, argument, metadata, {}); handleCancellation(stream, cancellation); handleServerResponses(stream, subject); return subject; } /** * Performs a duplex streaming call between the client and the Runtime. * @param {grpc.Client} client - The Runtime client. * @param {DuplexMethod<TRequest, TResponse>} method - The method to call. * @param {Observable<TRequest>} requests - The requests to send to the Runtime. * @param {Cancellation} cancellation - Used to cancel the call. * @returns {Observable<TResponse>} The responses from the Runtime and errors from the requests. * @template TRequest - The type of the argument. * @template TResponse - The type of the response. */ export function reactiveDuplex<TRequest, TResponse>(client: grpc.Client, method: DuplexMethod<TRequest, TResponse>, requests: Observable<TRequest>, cancellation: Cancellation): Observable<TResponse> { const subject = new Subject<TResponse>(); const metadata = new grpc.Metadata(); const stream = method.call(client, metadata, {}); handleCancellation(stream, cancellation); handleClientRequests(stream, requests, subject, client.getChannel().getTarget()); handleServerResponses(stream, subject); return subject; } function handleCancellation(call: grpc.Call, cancellation: Cancellation) { const subscription = cancellation.subscribe({ complete: () => { call.cancel(); } }); call.on('end', () => { subscription.unsubscribe(); }); } /** * Handles writing requests to the Runtime. If the request error, it cancels the stream and errors the * subject containing the responses from the Runtime. * @param {grpc.ClientWritableStream<TRequest>} stream - The stream between client and Runtime. * @param {Observable<TRequest>} requests - The requests to write to the Runtime. * @param {Subject<TResponse>} subject - The Subject which contains the responses from the Runtime. * @param {string} address - The address of the Runtime that was connected to. * @template TRequest - The type of the argument. * @template TResponse - The type of the response. */ function handleClientRequests<TRequest, TResponse>(stream: grpc.ClientWritableStream<TRequest>, requests: Observable<TRequest>, subject: Subject<TResponse>, address: string) { requests.pipe(concatMap((message: TRequest) => { const subject = new Subject<void>(); stream.write(message, undefined, () => { subject.complete(); }); return subject; })).subscribe({ complete: () => { stream.end(); }, error: (error: any) => { stream.cancel(); subject.error(getErrorFrom(error, address)); } }); } /** * Handles the responses coming from the Runtime. * @param {grpc.ClientWritableStream} stream - The stream between client and Runtime. * @param {Subject} subject - The Subject to notify about the responses from the Runtime. */ function handleServerResponses<TResponse>(stream: grpc.ClientReadableStream<TResponse>, subject: Subject<TResponse>) { stream.on('data', (message: TResponse) => { subject.next(message); }); stream.on('end', () => { subject.complete(); }); stream.on('error', (error: Error) => { subject.error(error); }); } function getErrorFrom(error: any, address: string): any { if (isGrpcError(error) && error.code === grpc.status.UNAVAILABLE) { return new CouldNotConnectToRuntime(address); } return error; }