UNPKG

rpc_ts

Version:

Remote Procedure Calls in TypeScript made simple

224 lines 8.83 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * @module ModuleRpcClient * * @license * Copyright (c) Aiden.ai * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const common_1 = require("../common"); const errors_1 = require("./errors"); const stream_1 = require("./stream"); const utils_1 = require("../utils/utils"); const events = require("events"); const stream_retrier_1 = require("./stream_retrier"); /** * Default list of [[ModuleRpcCommon.RpcErrorType]] error types that * should lead to an immediate failure and not to a retry. */ exports.ERROR_TYPES_TO_FAIL_IMMEDIATELY = [ common_1.ModuleRpcCommon.RpcErrorType.invalidArgument, common_1.ModuleRpcCommon.RpcErrorType.notFound, common_1.ModuleRpcCommon.RpcErrorType.alreadyExists, common_1.ModuleRpcCommon.RpcErrorType.permissionDenied, common_1.ModuleRpcCommon.RpcErrorType.failedPrecondition, common_1.ModuleRpcCommon.RpcErrorType.unimplemented, common_1.ModuleRpcCommon.RpcErrorType.internal, common_1.ModuleRpcCommon.RpcErrorType.unauthenticated, ]; /** * Default `shouldRetry` predicate. * * With this predicate, all errors lead to a retry except errors of * class [[ClientProtocolError]] and of class [[ClientRpcError]] with * type included in a specific subset of [[ModuleRpcCommon.RpcErrorType]]. * * @see [[Service.withRetry]] */ exports.DEFAULT_SHOULD_RETRY = (err) => { if (err instanceof errors_1.ClientProtocolError) { return false; } else if (err instanceof errors_1.ClientRpcError && exports.ERROR_TYPES_TO_FAIL_IMMEDIATELY.includes(err.errorType)) { return false; } return true; }; /** * Implements a service from a [[StreamProducer]]. The StreamProducer * is provided by a streaming protocol, for instance through [[getGrpcWebClient]] in the * case of the gRPC-Web protocol. * * @typeparam serviceDefinition The definition of the service, enumerating all the * methods and their request and response types. * @typeparam ResponseContext The type of the response context that is transmitted along * with the response to the client. */ class Service { /** * @param serviceDefinition The definition of the service. * @param streamProducer A handler that produces a stream of [[ResponseWithContext]] * objects given a method name and an RPC request to which the transportation is delegated. */ constructor(serviceDefinition, streamProducer) { this.serviceDefinition = serviceDefinition; this.streamProducer = streamProducer; } /** * Retrieves a server stream from a method using a request. * * @return An object containing the response of the RPC and the context of * the response (meta-information). The response context contains the information * that is related to the "remote" aspect of the procedure call: it would be empty if * the call were to be made within the same OS process. */ stream(method, request) { return this.streamProducer(method, request); } /** * Calls a unary method on a request. * * The underlying server stream is retrieved and transformed into a promise. * * @return An object containing the response of the RPC and the context of * the response (meta-information). The response context contains the information * that is related to the "remote" aspect of the procedure call: it would be empty if * the call were to be made within the same OS process. * * @throws [[ClientProtocolError]] if the remote implementation was not that of a unary method. * @throws `Error` if errors were encountered during the streaming. * * @see [[streamAsPromise]] */ call(method, request) { return new Promise((accept, reject) => { let message; this.stream(method, request) .on('message', msg => { if (message) { reject(new errors_1.ClientProtocolError(method, 'expected unary method, but got more than one message from server')); } else { message = msg; } }) .on('error', err => { reject(err); }) .on('complete', () => { if (!message) { reject(new errors_1.ClientProtocolError(method, 'expected unary method, but got no message from server')); } else { accept(message); } }) .on('canceled', () => { reject(new errors_1.ClientRpcError(common_1.ModuleRpcCommon.RpcErrorType.canceled)); }) .start(); }); } /** * Returns a service wrapped with a given retry policy that applies to all * the methods of this service. * * @param backoffOptions Options for the exponential backoff. * @param shouldRetry Determines whether an error should be retried (the * function returns `true`) or the method should fail immediately. */ withRetry(backoffOptions = {}, shouldRetry = exports.DEFAULT_SHOULD_RETRY) { return new ServiceRetrierImpl(this, backoffOptions, shouldRetry); } /** * Return a "method map" service interface with which it is possible to call RPCs * as "normal" JavaScript functions. * * @example ```Typescript * // Before * const service: Service<...> = ...; * service.stream('serverStream', { foo: 'bar' }) * .on('message', ({ response, responseContext }) => { ... }) * .start(); * const { response, responseContext } = await service.call('unaryMethod', { foo: 'bar' }); * * // After * const methodMap = service.methodMap(); * methodMap.serverStream({ foo: 'bar' }) * .on('message', response => { ... }) * .start(); * const response = await methodMap.unaryMethod({ foo: 'bar' }); * ``` */ methodMap() { const methods = utils_1.mapValuesWithStringKeys(this.serviceDefinition, (methodDefinition, method) => { if (methodDefinition.type === common_1.ModuleRpcCommon.ServiceMethodType.serverStream) { return (request) => stream_1.transformStream(this.stream(method, request), ({ response }) => response); } else { return async (request) => { // Any-cast is the simplest way since it is difficult to type assert // the fact that it is a unary method. const { response } = await this.call(method, request); return response; }; } }); return Object.assign(Object.assign({}, methods), { [serviceKey]: this }); } } exports.Service = Service; /** * Symbol used as a property name on a [[ServiceMethodMap]] to access the * full service interface from a "method map" service interface. * * This symbol is private to this module as [[serviceInstance]] should * be used externally instead of directly accessing the service instance * using the symbol. */ const serviceKey = Symbol('serviceKey'); /** * Get the full service instance from a "method map" service interface. * * Implementation detail: The service instance is stored in a "method map" through a * symbol-named property. * * @example ```Typescript * const service: Service<...> = ...; * const methodMap = service.methodMap(); * // serviceInstance(methodMap) === service * ``` */ function serviceInstance(methodMap) { return methodMap[serviceKey]; } exports.serviceInstance = serviceInstance; class ServiceRetrierImpl extends events.EventEmitter { constructor(baseService, backoffOptions, shouldRetry) { super(); this.baseService = baseService; this.backoffOptions = backoffOptions; this.shouldRetry = shouldRetry; this.stream = (method, request) => { return stream_retrier_1.retryStream(() => this.baseService.stream(method, request), this.backoffOptions, this.shouldRetry) .on('ready', () => { this.emit('serviceReady', method, request); }) .on('retryingError', (err, retries, abandoned) => { this.emit('serviceError', err, retries, abandoned, method, request); }) .on('complete', () => { this.emit('serviceComplete', method, request); }); }; } service() { return new Service(this.baseService.serviceDefinition, this.stream); } } //# sourceMappingURL=service.js.map