rpc_ts
Version:
Remote Procedure Calls in TypeScript made simple
224 lines • 8.83 kB
JavaScript
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
;