|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { ChannelCredentials } from './channel-credentials'; |
|
import { ChannelOptions } from './channel-options'; |
|
import { Client } from './client'; |
|
import { UntypedServiceImplementation } from './server'; |
|
|
|
export interface Serialize<T> { |
|
(value: T): Buffer; |
|
} |
|
|
|
export interface Deserialize<T> { |
|
(bytes: Buffer): T; |
|
} |
|
|
|
export interface ClientMethodDefinition<RequestType, ResponseType> { |
|
path: string; |
|
requestStream: boolean; |
|
responseStream: boolean; |
|
requestSerialize: Serialize<RequestType>; |
|
responseDeserialize: Deserialize<ResponseType>; |
|
originalName?: string; |
|
} |
|
|
|
export interface ServerMethodDefinition<RequestType, ResponseType> { |
|
path: string; |
|
requestStream: boolean; |
|
responseStream: boolean; |
|
responseSerialize: Serialize<ResponseType>; |
|
requestDeserialize: Deserialize<RequestType>; |
|
originalName?: string; |
|
} |
|
|
|
export interface MethodDefinition<RequestType, ResponseType> |
|
extends ClientMethodDefinition<RequestType, ResponseType>, |
|
ServerMethodDefinition<RequestType, ResponseType> {} |
|
|
|
|
|
export type ServiceDefinition< |
|
ImplementationType = UntypedServiceImplementation |
|
> = { |
|
readonly [index in keyof ImplementationType]: MethodDefinition<any, any>; |
|
}; |
|
|
|
|
|
export interface ProtobufTypeDefinition { |
|
format: string; |
|
type: object; |
|
fileDescriptorProtos: Buffer[]; |
|
} |
|
|
|
export interface PackageDefinition { |
|
[index: string]: ServiceDefinition | ProtobufTypeDefinition; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
serviceName: string; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function isPrototypePolluted(key: string): boolean { |
|
return ['__proto__', 'prototype', 'constructor'].includes(key); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function makeClientConstructor( |
|
methods: ServiceDefinition, |
|
serviceName: string, |
|
classOptions?: {} |
|
): ServiceClientConstructor { |
|
if (!classOptions) { |
|
classOptions = {}; |
|
} |
|
|
|
class ServiceClientImpl extends Client implements ServiceClient { |
|
static service: ServiceDefinition; |
|
static serviceName: string; |
|
[methodName: string]: Function; |
|
} |
|
|
|
Object.keys(methods).forEach(name => { |
|
if (isPrototypePolluted(name)) { |
|
return; |
|
} |
|
const attrs = methods[name]; |
|
let methodType: keyof typeof requesterFuncs; |
|
|
|
if (typeof name === 'string' && name.charAt(0) === '$') { |
|
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; |
|
|
|
Object.assign(ServiceClientImpl.prototype[name], attrs); |
|
if (attrs.originalName && !isPrototypePolluted(attrs.originalName)) { |
|
ServiceClientImpl.prototype[attrs.originalName] = |
|
ServiceClientImpl.prototype[name]; |
|
} |
|
}); |
|
|
|
ServiceClientImpl.service = methods; |
|
ServiceClientImpl.serviceName = serviceName; |
|
|
|
return ServiceClientImpl; |
|
} |
|
|
|
function partial( |
|
fn: Function, |
|
path: string, |
|
serialize: Function, |
|
deserialize: Function |
|
): Function { |
|
|
|
return function (this: any, ...args: any[]) { |
|
return fn.call(this, path, serialize, deserialize, ...args); |
|
}; |
|
} |
|
|
|
export interface GrpcObject { |
|
[index: string]: |
|
| GrpcObject |
|
| ServiceClientConstructor |
|
| ProtobufTypeDefinition; |
|
} |
|
|
|
function isProtobufTypeDefinition( |
|
obj: ServiceDefinition | ProtobufTypeDefinition |
|
): obj is ProtobufTypeDefinition { |
|
return 'format' in obj; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
export function loadPackageDefinition( |
|
packageDef: PackageDefinition |
|
): GrpcObject { |
|
const result: GrpcObject = {}; |
|
for (const serviceFqn in packageDef) { |
|
if (Object.prototype.hasOwnProperty.call(packageDef, serviceFqn)) { |
|
const service = packageDef[serviceFqn]; |
|
const nameComponents = serviceFqn.split('.'); |
|
if (nameComponents.some((comp: string) => isPrototypePolluted(comp))) { |
|
continue; |
|
} |
|
const serviceName = nameComponents[nameComponents.length - 1]; |
|
let current = result; |
|
for (const packageName of nameComponents.slice(0, -1)) { |
|
if (!current[packageName]) { |
|
current[packageName] = {}; |
|
} |
|
current = current[packageName] as GrpcObject; |
|
} |
|
if (isProtobufTypeDefinition(service)) { |
|
current[serviceName] = service; |
|
} else { |
|
current[serviceName] = makeClientConstructor(service, serviceName, {}); |
|
} |
|
} |
|
} |
|
return result; |
|
} |
|
|