Skip to content
This repository has been archived by the owner on Feb 17, 2022. It is now read-only.

Commit

Permalink
Merge pull request #9 from aiden/service_method_map
Browse files Browse the repository at this point in the history
Nice service => default method map on getRpcClient
  • Loading branch information
hchauvin authored Apr 1, 2019
2 parents 4f15cdb + 12aef3f commit cbb524a
Show file tree
Hide file tree
Showing 16 changed files with 144 additions and 94 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand Down
2 changes: 1 addition & 1 deletion docs/primer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion site/landing/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ http.createServer(app).listen(3000);
<div class="codeBlock">{% highlight TypeScript %}
const { text } = await ModuleRpcProtocolClient.getRpcClient(helloService, {
remoteAddress: 'http://localhost:3000'
}).nice().getHello({ language: 'Spanish' });
}).getHello({ language: 'Spanish' });
{% endhighlight %}</div>
</div>
</div>
Expand Down
111 changes: 63 additions & 48 deletions src/client/__tests__/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -179,16 +179,16 @@ 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<
typeof testServiceDefinition,
ResponseContext
>(testServiceDefinition, streamProducer);
const request = { foo: 'bar' };
const promise = service.nice().unary(request);
const promise = service.methodMap().unary(request);

const message = {
response: { value: 10 },
Expand All @@ -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);
},
);
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
62 changes: 49 additions & 13 deletions src/client/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
Expand All @@ -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<serviceDefinition> {
return mapValuesWithStringKeys(
methodMap(): ServiceMethodMap<serviceDefinition, ResponseContext> {
const methods = mapValuesWithStringKeys(
this.serviceDefinition,
(methodDefinition, method) => {
if (
Expand All @@ -180,6 +180,10 @@ export class Service<
}
},
) as any;
return {
...methods,
[serviceKey]: this,
};
}
}

Expand All @@ -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
Expand All @@ -212,9 +227,30 @@ export type NiceService<
}
? ServerStreamMethod<serviceDefinition, method>
: UnaryMethod<serviceDefinition, method>
} & {
[serviceKey]: Service<serviceDefinition, ResponseContext>;
};

/** 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<serviceDefinition, ResponseContext>) {
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<serviceDefinition>
Expand All @@ -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<serviceDefinition>
Expand Down
4 changes: 2 additions & 2 deletions src/examples/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -88,7 +88,7 @@ async function clientInteraction(remoteAddress: string) {
remoteAddress,
clientContextConnector: new AuthClientContextConnector('u2'),
},
).nice();
);

try {
await unauthenticatedClient.getBalance({});
Expand Down
13 changes: 5 additions & 8 deletions src/examples/server_stream/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@
* 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';
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 */
Expand Down Expand Up @@ -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 });
Expand All @@ -88,7 +85,7 @@ async function clientInteraction(remoteAddress: string) {
}

function streamNumbers(
client: ModuleRpcClient.NiceService<NumberService>,
client: ModuleRpcClient.ServiceMethodMap<NumberService>,
): ModuleRpcClient.Stream<
ModuleRpcCommon.ResponseFor<NumberService, 'streamNumbers'>
> {
Expand Down
Loading

0 comments on commit cbb524a

Please sign in to comment.