diff --git a/README.md b/README.md
index 0fc0e205..7071ff38 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ const server = http.createServer(app).listen();
async function rpc() {
const { text } = await ModuleRpcProtocolClient.getRpcClient(helloServiceDefinition, {
remoteAddress: `http://localhost:${server.address().port}`
- }).nice().getHello({ language: 'Spanish' });
+ }).getHello({ language: 'Spanish' });
// (Notice that, with TypeScript typing, it is not possible to mess up the
// type of the request: for instance, `.getHello({ lang: 'Spanish' })`
// will error.)
diff --git a/docs/primer.md b/docs/primer.md
index c3e78048..498a0cc5 100644
--- a/docs/primer.md
+++ b/docs/primer.md
@@ -36,7 +36,7 @@ const server = http.createServer(app).listen();
async function rpc() {
const { text } = await ModuleRpcProtocolClient.getRpcClient(helloServiceDefinition, {
remoteAddress: `http://localhost:${server.address().port}`
- }).nice().getHello({ language: 'Spanish' });
+ }).getHello({ language: 'Spanish' });
// (Notice that, with TypeScript typing, it is not possible to mess up the
// type of the request: for instance, `.getHello({ lang: 'Spanish' })`
// will error.)
diff --git a/package.json b/package.json
index ad7647d3..a893b1d1 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
"type-zoo": "^1.2.1",
"typedoc": "^0.13.0",
"typedoc-plugin-external-module-name": "^1.1.3",
- "typescript": "3.2.2"
+ "typescript": "3.4.1"
},
"scripts": {
"clean": "rimraf lib dist es typedoc",
diff --git a/site/landing/index.md b/site/landing/index.md
index 7ac2417d..2a5ede3c 100644
--- a/site/landing/index.md
+++ b/site/landing/index.md
@@ -65,7 +65,7 @@ http.createServer(app).listen(3000);
{% highlight TypeScript %}
const { text } = await ModuleRpcProtocolClient.getRpcClient(helloService, {
remoteAddress: 'http://localhost:3000'
-}).nice().getHello({ language: 'Spanish' });
+}).getHello({ language: 'Spanish' });
{% endhighlight %}
diff --git a/src/client/__tests__/service.test.ts b/src/client/__tests__/service.test.ts
index 88190415..10d943f0 100644
--- a/src/client/__tests__/service.test.ts
+++ b/src/client/__tests__/service.test.ts
@@ -5,7 +5,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
-import { Service, ServiceRetrier } from '../service';
+import { Service, ServiceRetrier, serviceInstance } from '../service';
import * as sinon from 'sinon';
import { Stream } from '../stream';
import { EventEmitter } from 'events';
@@ -179,8 +179,8 @@ describe('rpc_ts', () => {
});
});
- describe('nice', () => {
- it('unary methods can be called', async () => {
+ describe('methodMap', () => {
+ specify('unary methods can be called', async () => {
const mockStream = new MockStream();
const streamProducer = sinon.stub().returns(mockStream);
const service = new Service<
@@ -188,7 +188,7 @@ describe('rpc_ts', () => {
ResponseContext
>(testServiceDefinition, streamProducer);
const request = { foo: 'bar' };
- const promise = service.nice().unary(request);
+ const promise = service.methodMap().unary(request);
const message = {
response: { value: 10 },
@@ -201,59 +201,74 @@ describe('rpc_ts', () => {
expect(streamProducer.calledOnce).to.be.true;
expect(streamProducer.args[0]).to.deep.equal(['unary', request]);
});
- });
- it('server streams can be called', async () => {
- const mockStream = new MockStream();
- const streamProducer = sinon.stub().returns(mockStream);
- const service = new Service<
- typeof testServiceDefinition,
- ResponseContext
- >(testServiceDefinition, streamProducer);
- const request = { foo: 'bar' };
- const stream = service.nice().stream(request);
+ specify('server streams can be called', async () => {
+ const mockStream = new MockStream();
+ const streamProducer = sinon.stub().returns(mockStream);
+ const service = new Service<
+ typeof testServiceDefinition,
+ ResponseContext
+ >(testServiceDefinition, streamProducer);
+ const request = { foo: 'bar' };
+ const stream = service.methodMap().stream(request);
- const message1 = {
- response: { value: 10 },
- responseContext: { qux: 'wobble' },
- };
- await new Promise((accept, reject) => {
- stream.on('message', message => {
- try {
- expect(message).to.deep.equal({ value: 10 });
- accept();
- } catch (err) {
- reject(err);
- }
+ const message1 = {
+ response: { value: 10 },
+ responseContext: { qux: 'wobble' },
+ };
+ await new Promise((accept, reject) => {
+ stream.on('message', message => {
+ try {
+ expect(message).to.deep.equal({ value: 10 });
+ accept();
+ } catch (err) {
+ reject(err);
+ }
+ });
+ mockStream.emit('message', message1);
});
- mockStream.emit('message', message1);
- });
- const message2 = {
- response: { value: 20 },
- responseContext: { qux: 'wobble2' },
- };
- await new Promise((accept, reject) => {
- stream.on('message', message => {
- try {
- expect(message).to.deep.equal({ value: 20 });
- accept();
- } catch (err) {
- reject(err);
- }
+ const message2 = {
+ response: { value: 20 },
+ responseContext: { qux: 'wobble2' },
+ };
+ await new Promise((accept, reject) => {
+ stream.on('message', message => {
+ try {
+ expect(message).to.deep.equal({ value: 20 });
+ accept();
+ } catch (err) {
+ reject(err);
+ }
+ });
+ mockStream.emit('message', message2);
});
- mockStream.emit('message', message2);
- });
- await new Promise(accept => {
- stream.on('complete', () => {
- accept();
+ await new Promise(accept => {
+ stream.on('complete', () => {
+ accept();
+ });
+ mockStream.emit('complete');
});
- mockStream.emit('complete');
+
+ expect(streamProducer.calledOnce).to.be.true;
+ expect(streamProducer.args[0]).to.deep.equal(['stream', request]);
});
- expect(streamProducer.calledOnce).to.be.true;
- expect(streamProducer.args[0]).to.deep.equal(['stream', request]);
+ specify(
+ 'serviceInstance(methodMap) gives the original, full service',
+ async () => {
+ const mockStream = new MockStream();
+ const streamProducer = sinon.stub().returns(mockStream);
+ const service = new Service<
+ typeof testServiceDefinition,
+ ResponseContext
+ >(testServiceDefinition, streamProducer);
+
+ const methodMap = service.methodMap();
+ expect(serviceInstance(methodMap)).to.equal(service);
+ },
+ );
});
});
});
diff --git a/src/client/client.ts b/src/client/client.ts
index e1cb2cfe..fb759982 100644
--- a/src/client/client.ts
+++ b/src/client/client.ts
@@ -16,7 +16,7 @@
export { ClientContextConnector } from './context_connector';
export * from './errors';
export { retryStream } from './stream_retrier';
-export { Service, NiceService } from './service';
+export { Service, ServiceMethodMap } from './service';
export {
StreamProducer,
Stream,
diff --git a/src/client/service.ts b/src/client/service.ts
index 5931acca..c88c76f9 100644
--- a/src/client/service.ts
+++ b/src/client/service.ts
@@ -138,10 +138,10 @@ export class Service<
}
/**
- * Return a "nice" service interface with which it is possible to call RPCs
+ * Return a "method map" service interface with which it is possible to call RPCs
* as "normal" JavaScript functions.
*
- * @example ```TypeScript
+ * @example ```Typescript
* // Before
* const service: Service<...> = ...;
* service.stream('serverStream', { foo: 'bar' })
@@ -150,15 +150,15 @@ export class Service<
* const { response, responseContext } = await service.call('unaryMethod', { foo: 'bar' });
*
* // After
- * const niceService = service.nice();
- * niceService.serverStream({ foo: 'bar' })
+ * const methodMap = service.methodMap();
+ * methodMap.serverStream({ foo: 'bar' })
* .on('message', response => { ... })
* .start();
- * const response = await niceService.unaryMethod({ foo: 'bar' });
+ * const response = await methodMap.unaryMethod({ foo: 'bar' });
* ```
*/
- nice(): NiceService {
- return mapValuesWithStringKeys(
+ methodMap(): ServiceMethodMap {
+ const methods = mapValuesWithStringKeys(
this.serviceDefinition,
(methodDefinition, method) => {
if (
@@ -180,6 +180,10 @@ export class Service<
}
},
) as any;
+ return {
+ ...methods,
+ [serviceKey]: this,
+ };
}
}
@@ -198,12 +202,23 @@ export interface ResponseWithContext<
}
/**
- * "Nice" service derived from a service definition.
+ * Symbol used as a property name on a [[ServiceMethodMap]] to access the
+ * full service interface from a "method map" service interface.
*
- * @see [[Service.nice]]
+ * This symbol is private to this module as [[serviceInstance]] should
+ * be used externally instead of directly accessing the service instance
+ * using the symbol.
*/
-export type NiceService<
- serviceDefinition extends ModuleRpcCommon.ServiceDefinition
+const serviceKey = Symbol('serviceKey');
+
+/**
+ * "Method map" service interface derived from a service definition.
+ *
+ * @see [[Service.methodMap]]
+ */
+export type ServiceMethodMap<
+ serviceDefinition extends ModuleRpcCommon.ServiceDefinition,
+ ResponseContext = any
> = {
[method in ModuleRpcCommon.MethodsFor<
serviceDefinition
@@ -212,9 +227,30 @@ export type NiceService<
}
? ServerStreamMethod
: UnaryMethod
+} & {
+ [serviceKey]: Service;
};
-/** A unary method typed as part of a "nice" service. */
+/**
+ * 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
+ * ```
+ */
+export function serviceInstance<
+ serviceDefinition extends ModuleRpcCommon.ServiceDefinition,
+ ResponseContext = any
+>(methodMap: ServiceMethodMap) {
+ return methodMap[serviceKey];
+}
+
+/** A unary method typed as part of a "method map" service interface. */
export interface UnaryMethod<
serviceDefinition extends ModuleRpcCommon.ServiceDefinition,
method extends ModuleRpcCommon.MethodsFor
@@ -224,7 +260,7 @@ export interface UnaryMethod<
>;
}
-/** A server-stream method typed as part of a "nice" service. */
+/** A server-stream method typed as part of a "method map" service interface. */
export interface ServerStreamMethod<
serviceDefinition extends ModuleRpcCommon.ServiceDefinition,
method extends ModuleRpcCommon.MethodsFor
diff --git a/src/examples/context/index.ts b/src/examples/context/index.ts
index 4332340f..2a04e189 100644
--- a/src/examples/context/index.ts
+++ b/src/examples/context/index.ts
@@ -66,7 +66,7 @@ async function clientInteraction(remoteAddress: string) {
remoteAddress,
clientContextConnector: new AuthClientContextConnector('u1'),
},
- ).nice();
+ );
const balanceResponseBefore = await client.getBalance({});
console.log('Balance is', balanceResponseBefore.value);
@@ -88,7 +88,7 @@ async function clientInteraction(remoteAddress: string) {
remoteAddress,
clientContextConnector: new AuthClientContextConnector('u2'),
},
- ).nice();
+ );
try {
await unauthenticatedClient.getBalance({});
diff --git a/src/examples/server_stream/index.ts b/src/examples/server_stream/index.ts
index 2787ff19..9d94c93c 100644
--- a/src/examples/server_stream/index.ts
+++ b/src/examples/server_stream/index.ts
@@ -6,7 +6,6 @@
* LICENSE file in the root directory of this source tree.
*/
import * as express from 'express';
-import { getGrpcWebClient } from '../../protocol/grpc_web/client/client';
import { numberServiceDefinition, NumberService } from './service';
import * as http from 'http';
import { getNumberHandler } from './handler';
@@ -14,8 +13,8 @@ import { ModuleRpcClient } from '../../client';
import { ModuleRpcCommon } from '../../common';
import { AssertionError } from 'assert';
import { ModuleRpcContextServer } from '../../context/server';
-import { ModuleRpcContextClient } from '../../context/client';
import { ModuleRpcProtocolServer } from '../../protocol/server';
+import { ModuleRpcProtocolClient } from '../../protocol/client';
main().catch(
/* istanbul ignore next */
@@ -59,11 +58,9 @@ function setupServer() {
}
async function clientInteraction(remoteAddress: string) {
- const client = getGrpcWebClient(
- numberServiceDefinition,
- new ModuleRpcContextClient.EmptyClientContextConnector(),
- { remoteAddress },
- ).nice();
+ const client = ModuleRpcProtocolClient.getRpcClient(numberServiceDefinition, {
+ remoteAddress,
+ });
// We call our unary method
const { value } = await client.increment({ value: 10 });
@@ -88,7 +85,7 @@ async function clientInteraction(remoteAddress: string) {
}
function streamNumbers(
- client: ModuleRpcClient.NiceService,
+ client: ModuleRpcClient.ServiceMethodMap,
): ModuleRpcClient.Stream<
ModuleRpcCommon.ResponseFor
> {
diff --git a/src/protocol/client/client.ts b/src/protocol/client/client.ts
index fa48df42..56a24628 100644
--- a/src/protocol/client/client.ts
+++ b/src/protocol/client/client.ts
@@ -33,7 +33,7 @@ export function getRpcClient<
>(
serviceDefinition: serviceDefinition,
options: RpcClientOptions,
-): ModuleRpcClient.Service;
+): ModuleRpcClient.ServiceMethodMap;
export function getRpcClient<
serviceDefinition extends ModuleRpcCommon.ServiceDefinition,
ResponseContext
@@ -44,7 +44,7 @@ export function getRpcClient<
ResponseContext
>;
},
-): ModuleRpcClient.Service;
+): ModuleRpcClient.ServiceMethodMap;
export function getRpcClient<
serviceDefinition extends ModuleRpcCommon.ServiceDefinition,
ResponseContext
@@ -55,15 +55,15 @@ export function getRpcClient<
ResponseContext
>;
},
-): ModuleRpcClient.Service {
+): ModuleRpcClient.ServiceMethodMap {
const clientContextConnector =
options.clientContextConnector ||
new ModuleRpcContextClient.EmptyClientContextConnector();
- return ModuleRpcProtocolGrpcWebClient.getGrpcWebClient(
+ return (ModuleRpcProtocolGrpcWebClient.getGrpcWebClient(
serviceDefinition,
clientContextConnector,
{
remoteAddress: options.remoteAddress,
},
- ) as ModuleRpcClient.Service;
+ ) as ModuleRpcClient.Service).methodMap();
}
diff --git a/src/protocol/grpc_web/__tests__/client_server.it.ts b/src/protocol/grpc_web/__tests__/client_server.it.ts
index 95a1f664..b99be769 100644
--- a/src/protocol/grpc_web/__tests__/client_server.it.ts
+++ b/src/protocol/grpc_web/__tests__/client_server.it.ts
@@ -266,7 +266,7 @@ describe('rpc_ts', () => {
{ remoteAddress: `http://example.test` },
);
try {
- await client.nice().unary({});
+ await client.methodMap().unary({});
throw new AssertionError({
message: 'expected an Error to be thrown',
});
@@ -307,7 +307,7 @@ describe('rpc_ts', () => {
codec: new InvalidCodec(),
},
);
- await client.nice().unary({});
+ await client.methodMap().unary({});
throw new AssertionError({
message: 'expected an Error to be thrown',
});
@@ -475,7 +475,7 @@ describe('rpc_ts', () => {
new ModuleRpcContextClient.EmptyClientContextConnector(),
{ remoteAddress: `http://localhost:${serverPort}/api` },
);
- await client.nice().unary({});
+ await client.methodMap().unary({});
throw new AssertionError({
message: 'expected an Error to be thrown',
});
@@ -523,7 +523,9 @@ describe('rpc_ts', () => {
new ModuleRpcContextClient.EmptyClientContextConnector(),
{ remoteAddress: `http://localhost:${serverPort}/api` },
);
- await ModuleRpcClient.streamAsPromise(client.nice().serverStream({}));
+ await ModuleRpcClient.streamAsPromise(
+ client.methodMap().serverStream({}),
+ );
throw new AssertionError({
message: 'expected an Error to be thrown',
});
diff --git a/src/protocol/grpc_web/client/client.ts b/src/protocol/grpc_web/client/client.ts
index e13cabee..226a29a6 100644
--- a/src/protocol/grpc_web/client/client.ts
+++ b/src/protocol/grpc_web/client/client.ts
@@ -52,8 +52,8 @@ export interface GrpcWebClientOptions {
/**
* Returns an RPC client for the gRPC-Web protocol.
*
- * @see [[getRpcClient]] offers a more generic interface and is enough
- * for most use cases.
+ * @see [[getRpcClient]] offers a more generic interface with a more intuitive "method map"
+ * service interface and is enough for most use cases.
*
* @param serviceDefinition The definition of the service for which to implement a client.
* @param clientContextConnector The context connector to use to inject metadata (such
diff --git a/src/protocol/grpc_web/private/grpc.ts b/src/protocol/grpc_web/private/grpc.ts
index c8a826b9..bde1725e 100644
--- a/src/protocol/grpc_web/private/grpc.ts
+++ b/src/protocol/grpc_web/private/grpc.ts
@@ -82,7 +82,7 @@ export const errorTypesToGrpcStatuses: {
/** Conversion table from gRPC statuses to error types. */
export const grpcStatusesToErrorTypes = _.fromPairs(
_.map(errorTypesToGrpcStatuses, (status, errorType) => [status, errorType]),
-);
+) as { [errorCode: number]: ModuleRpcCommon.RpcErrorType };
/** Get the error, or null in case the RPC succeeded, for the RPC call metadata. */
export function getGrpcWebErrorFromMetadata(
diff --git a/src/protocol/mock/__tests__/mock.test.ts b/src/protocol/mock/__tests__/mock.test.ts
index c4432867..59dc8d8e 100644
--- a/src/protocol/mock/__tests__/mock.test.ts
+++ b/src/protocol/mock/__tests__/mock.test.ts
@@ -26,7 +26,7 @@ describe('rpc_ts', () => {
{ response: { bar: 1 } },
]) as ModuleRpcClient.Stream;
});
- const { bar } = await client.nice().foo({});
+ const { bar } = await client.methodMap().foo({});
expect(bar).to.equal(1);
});
});
diff --git a/src/protocol/mock/mock.ts b/src/protocol/mock/mock.ts
index 82b9fc9f..9f2ae5c6 100644
--- a/src/protocol/mock/mock.ts
+++ b/src/protocol/mock/mock.ts
@@ -31,7 +31,7 @@ import { ModuleRpcCommon } from '../../common';
* { response: { bar: 1 } },
* ]) as ModuleRpcClient.Stream;
* });
- * const { bar } = await client.nice().foo({});
+ * const { bar } = await client.methodMap().foo({});
* expect(bar).to.equal(1);
* ```
*/
diff --git a/yarn.lock b/yarn.lock
index 3b248c5f..6aa69106 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3313,10 +3313,10 @@ typescript@3.1.x:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68"
integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==
-typescript@3.2.2:
- version "3.2.2"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5"
- integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==
+typescript@3.4.1:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6"
+ integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==
uglify-js@^3.1.4:
version "3.4.9"