mirror of https://github.com/grpc/grpc-node.git
Merge pull request #206 from kjin/l-p-d
grpc-js: add makeClientConstructor and loadPackageDefinition
This commit is contained in:
commit
5b392fadb9
|
@ -8,20 +8,24 @@ import {ChannelCredentials} from './channel-credentials';
|
||||||
import {Status} from './constants';
|
import {Status} from './constants';
|
||||||
import {Metadata} from './metadata';
|
import {Metadata} from './metadata';
|
||||||
|
|
||||||
|
// This symbol must be exported (for now).
|
||||||
|
// See: https://github.com/Microsoft/TypeScript/issues/20080
|
||||||
|
export const kChannel = Symbol();
|
||||||
|
|
||||||
export interface UnaryCallback<ResponseType> {
|
export interface UnaryCallback<ResponseType> {
|
||||||
(err: ServiceError|null, value?: ResponseType): void;
|
(err: ServiceError|null, value?: ResponseType): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Client {
|
export class Client {
|
||||||
private readonly channel: Channel;
|
private readonly [kChannel]: Channel;
|
||||||
constructor(
|
constructor(
|
||||||
address: string, credentials: ChannelCredentials,
|
address: string, credentials: ChannelCredentials,
|
||||||
options: Partial<ChannelOptions> = {}) {
|
options: Partial<ChannelOptions> = {}) {
|
||||||
this.channel = new Http2Channel(address, credentials, options);
|
this[kChannel] = new Http2Channel(address, credentials, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
this.channel.close();
|
this[kChannel].close();
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForReady(deadline: Date|number, callback: (error: Error|null) => void):
|
waitForReady(deadline: Date|number, callback: (error: Error|null) => void):
|
||||||
|
@ -29,7 +33,7 @@ export class Client {
|
||||||
let cb: (error: Error|null) => void = once(callback);
|
let cb: (error: Error|null) => void = once(callback);
|
||||||
let callbackCalled = false;
|
let callbackCalled = false;
|
||||||
let timer: NodeJS.Timer | null = null;
|
let timer: NodeJS.Timer | null = null;
|
||||||
this.channel.connect().then(() => {
|
this[kChannel].connect().then(() => {
|
||||||
if (timer) {
|
if (timer) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
}
|
}
|
||||||
|
@ -140,7 +144,7 @@ export class Client {
|
||||||
this.checkOptionalUnaryResponseArguments<ResponseType>(
|
this.checkOptionalUnaryResponseArguments<ResponseType>(
|
||||||
metadata, options, callback));
|
metadata, options, callback));
|
||||||
const call: CallStream =
|
const call: CallStream =
|
||||||
this.channel.createStream(method, metadata, options);
|
this[kChannel].createStream(method, metadata, options);
|
||||||
const message: Buffer = serialize(argument);
|
const message: Buffer = serialize(argument);
|
||||||
const writeObj: WriteObject = {message: message};
|
const writeObj: WriteObject = {message: message};
|
||||||
writeObj.flags = options.flags;
|
writeObj.flags = options.flags;
|
||||||
|
@ -178,7 +182,7 @@ export class Client {
|
||||||
this.checkOptionalUnaryResponseArguments<ResponseType>(
|
this.checkOptionalUnaryResponseArguments<ResponseType>(
|
||||||
metadata, options, callback));
|
metadata, options, callback));
|
||||||
const call: CallStream =
|
const call: CallStream =
|
||||||
this.channel.createStream(method, metadata, options);
|
this[kChannel].createStream(method, metadata, options);
|
||||||
this.handleUnaryResponse<ResponseType>(call, deserialize, callback);
|
this.handleUnaryResponse<ResponseType>(call, deserialize, callback);
|
||||||
return new ClientWritableStreamImpl<RequestType>(call, serialize);
|
return new ClientWritableStreamImpl<RequestType>(call, serialize);
|
||||||
}
|
}
|
||||||
|
@ -222,7 +226,7 @@ export class Client {
|
||||||
options?: CallOptions): ClientReadableStream<ResponseType> {
|
options?: CallOptions): ClientReadableStream<ResponseType> {
|
||||||
({metadata, options} = this.checkMetadataAndOptions(metadata, options));
|
({metadata, options} = this.checkMetadataAndOptions(metadata, options));
|
||||||
const call: CallStream =
|
const call: CallStream =
|
||||||
this.channel.createStream(method, metadata, options);
|
this[kChannel].createStream(method, metadata, options);
|
||||||
const message: Buffer = serialize(argument);
|
const message: Buffer = serialize(argument);
|
||||||
const writeObj: WriteObject = {message: message};
|
const writeObj: WriteObject = {message: message};
|
||||||
writeObj.flags = options.flags;
|
writeObj.flags = options.flags;
|
||||||
|
@ -246,7 +250,7 @@ export class Client {
|
||||||
options?: CallOptions): ClientDuplexStream<RequestType, ResponseType> {
|
options?: CallOptions): ClientDuplexStream<RequestType, ResponseType> {
|
||||||
({metadata, options} = this.checkMetadataAndOptions(metadata, options));
|
({metadata, options} = this.checkMetadataAndOptions(metadata, options));
|
||||||
const call: CallStream =
|
const call: CallStream =
|
||||||
this.channel.createStream(method, metadata, options);
|
this[kChannel].createStream(method, metadata, options);
|
||||||
return new ClientDuplexStreamImpl<RequestType, ResponseType>(
|
return new ClientDuplexStreamImpl<RequestType, ResponseType>(
|
||||||
call, serialize, deserialize);
|
call, serialize, deserialize);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,40 @@
|
||||||
export * from './call-credentials';
|
|
||||||
export * from './channel-credentials';
|
import { CallCredentials } from './call-credentials';
|
||||||
export * from './client';
|
import { ChannelCredentials } from './channel-credentials';
|
||||||
export * from './constants';
|
import { Client } from './client';
|
||||||
export * from './metadata';
|
import { Status} from './constants';
|
||||||
|
import { makeClientConstructor, loadPackageDefinition } from './make-client';
|
||||||
|
import { Metadata } from './metadata';
|
||||||
|
|
||||||
|
const notImplementedFn = () => { throw new Error('Not implemented'); };
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
export { Metadata };
|
||||||
|
|
||||||
|
// Client credentials
|
||||||
|
|
||||||
|
export const credentials = {
|
||||||
|
createSsl: ChannelCredentials.createSsl,
|
||||||
|
createFromMetadataGenerator: CallCredentials.createFromMetadataGenerator,
|
||||||
|
createFromGoogleCredential: notImplementedFn /*TODO*/,
|
||||||
|
combineChannelCredentials: (first: ChannelCredentials, ...additional: CallCredentials[]) => additional.reduce((acc, other) => acc.compose(other), first),
|
||||||
|
combineCallCredentials: (first: CallCredentials, ...additional: CallCredentials[]) => additional.reduce((acc, other) => acc.compose(other), first),
|
||||||
|
createInsecure: ChannelCredentials.createInsecure
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
|
||||||
|
export {
|
||||||
|
Status as status
|
||||||
|
// TODO: Other constants as well
|
||||||
|
};
|
||||||
|
|
||||||
|
// Client
|
||||||
|
|
||||||
|
export {
|
||||||
|
Client,
|
||||||
|
loadPackageDefinition,
|
||||||
|
makeClientConstructor,
|
||||||
|
makeClientConstructor as makeGenericClientConstructor
|
||||||
|
};
|
||||||
|
export const closeClient = (client: Client) => client.close();
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
import { Metadata } from "./metadata";
|
||||||
|
import { Client, UnaryCallback } from "./client";
|
||||||
|
import { CallOptions } from "./call-stream";
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import { ChannelCredentials } from "./channel-credentials";
|
||||||
|
import { ChannelOptions } from "./channel";
|
||||||
|
|
||||||
|
export interface Serialize<T> {
|
||||||
|
(value: T): Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Deserialize<T> {
|
||||||
|
(bytes: Buffer): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MethodDefinition<RequestType, ResponseType> {
|
||||||
|
path: string;
|
||||||
|
requestStream: boolean;
|
||||||
|
responseStream: boolean;
|
||||||
|
requestSerialize: Serialize<RequestType>;
|
||||||
|
responseSerialize: Serialize<ResponseType>;
|
||||||
|
requestDeserialize: Deserialize<RequestType>;
|
||||||
|
responseDeserialize: Deserialize<ResponseType>;
|
||||||
|
originalName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceDefinition {
|
||||||
|
[index: string]: MethodDefinition<object, object>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PackageDefinition {
|
||||||
|
[index: string]: ServiceDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultValues<T>(metadata?: Metadata, options?: T): {
|
||||||
|
metadata: Metadata;
|
||||||
|
options: Partial<T>;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
metadata: metadata || new Metadata(),
|
||||||
|
options: options || {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map with short names for each of the requester maker functions. Used in
|
||||||
|
* makeClientConstructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const requesterFuncs = {
|
||||||
|
unary: Client.prototype.makeUnaryRequest,
|
||||||
|
server_stream: Client.prototype.makeServerStreamRequest,
|
||||||
|
client_stream: Client.prototype.makeClientStreamRequest,
|
||||||
|
bidi: Client.prototype.makeBidiStreamRequest
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ServiceClient extends Client {
|
||||||
|
[methodName: string]: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceClientConstructor {
|
||||||
|
new(address: string, credentials: ChannelCredentials,
|
||||||
|
options?: Partial<ChannelOptions>): ServiceClient;
|
||||||
|
service: ServiceDefinition;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a constructor for a client with the given methods, as specified in
|
||||||
|
* the methods argument. The resulting class will have an instance method for
|
||||||
|
* each method in the service, which is a partial application of one of the
|
||||||
|
* [Client]{@link grpc.Client} request methods, depending on `requestSerialize`
|
||||||
|
* and `responseSerialize`, with the `method`, `serialize`, and `deserialize`
|
||||||
|
* arguments predefined.
|
||||||
|
* @param methods An object mapping method names to
|
||||||
|
* method attributes
|
||||||
|
* @param serviceName The fully qualified name of the service
|
||||||
|
* @param classOptions An options object.
|
||||||
|
* @return New client constructor, which is a subclass of
|
||||||
|
* {@link grpc.Client}, and has the same arguments as that constructor.
|
||||||
|
*/
|
||||||
|
export function makeClientConstructor(
|
||||||
|
methods: ServiceDefinition, serviceName: string,
|
||||||
|
classOptions?: {}): ServiceClientConstructor {
|
||||||
|
if (!classOptions) {
|
||||||
|
classOptions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceClientImpl extends Client implements ServiceClient {
|
||||||
|
static service: ServiceDefinition;
|
||||||
|
[methodName: string]: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
_.each(methods, (attrs, name) => {
|
||||||
|
let methodType: keyof typeof requesterFuncs;
|
||||||
|
// TODO(murgatroid99): Verify that we don't need this anymore
|
||||||
|
if (_.startsWith(name, '$')) {
|
||||||
|
throw new Error('Method names cannot start with $');
|
||||||
|
}
|
||||||
|
if (attrs.requestStream) {
|
||||||
|
if (attrs.responseStream) {
|
||||||
|
methodType = 'bidi';
|
||||||
|
} else {
|
||||||
|
methodType = 'client_stream';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (attrs.responseStream) {
|
||||||
|
methodType = 'server_stream';
|
||||||
|
} else {
|
||||||
|
methodType = 'unary';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const serialize = attrs.requestSerialize;
|
||||||
|
const deserialize = attrs.responseDeserialize;
|
||||||
|
const methodFunc = _.partial(requesterFuncs[methodType], attrs.path,
|
||||||
|
serialize, deserialize);
|
||||||
|
ServiceClientImpl.prototype[name] = methodFunc;
|
||||||
|
// Associate all provided attributes with the method
|
||||||
|
_.assign(ServiceClientImpl.prototype[name], attrs);
|
||||||
|
if (attrs.originalName) {
|
||||||
|
ServiceClientImpl.prototype[attrs.originalName] = ServiceClientImpl.prototype[name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ServiceClientImpl.service = methods;
|
||||||
|
|
||||||
|
return ServiceClientImpl;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GrpcObject = {
|
||||||
|
[index: string]: GrpcObject | ServiceClientConstructor;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function loadPackageDefinition(packageDef: PackageDefinition) {
|
||||||
|
const result: GrpcObject = {};
|
||||||
|
for (const serviceFqn in packageDef) {
|
||||||
|
const service = packageDef[serviceFqn];
|
||||||
|
const nameComponents = serviceFqn.split('.');
|
||||||
|
const serviceName = nameComponents[nameComponents.length-1];
|
||||||
|
let current = result;
|
||||||
|
for (const packageName in nameComponents.slice(0, -1)) {
|
||||||
|
if (!current[packageName]) {
|
||||||
|
current[packageName] = {};
|
||||||
|
}
|
||||||
|
current = current[packageName] as GrpcObject;
|
||||||
|
}
|
||||||
|
current[serviceName] = makeClientConstructor(service, serviceName, {});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
Loading…
Reference in New Issue