1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | import { ChannelCredentials } from './channel-credentials';
|
19 | import { ChannelOptions } from './channel-options';
|
20 | import { Client } from './client';
|
21 | import { UntypedServiceImplementation } from './server';
|
22 |
|
23 | export interface Serialize<T> {
|
24 | (value: T): Buffer;
|
25 | }
|
26 |
|
27 | export interface Deserialize<T> {
|
28 | (bytes: Buffer): T;
|
29 | }
|
30 |
|
31 | export interface ClientMethodDefinition<RequestType, ResponseType> {
|
32 | path: string;
|
33 | requestStream: boolean;
|
34 | responseStream: boolean;
|
35 | requestSerialize: Serialize<RequestType>;
|
36 | responseDeserialize: Deserialize<ResponseType>;
|
37 | originalName?: string;
|
38 | }
|
39 |
|
40 | export interface ServerMethodDefinition<RequestType, ResponseType> {
|
41 | path: string;
|
42 | requestStream: boolean;
|
43 | responseStream: boolean;
|
44 | responseSerialize: Serialize<ResponseType>;
|
45 | requestDeserialize: Deserialize<RequestType>;
|
46 | originalName?: string;
|
47 | }
|
48 |
|
49 | export interface MethodDefinition<RequestType, ResponseType>
|
50 | extends ClientMethodDefinition<RequestType, ResponseType>,
|
51 | ServerMethodDefinition<RequestType, ResponseType> {}
|
52 |
|
53 |
|
54 | export type ServiceDefinition<
|
55 | ImplementationType = UntypedServiceImplementation
|
56 | > = {
|
57 | readonly [index in keyof ImplementationType]: MethodDefinition<any, any>;
|
58 | };
|
59 |
|
60 |
|
61 | export interface ProtobufTypeDefinition {
|
62 | format: string;
|
63 | type: object;
|
64 | fileDescriptorProtos: Buffer[];
|
65 | }
|
66 |
|
67 | export interface PackageDefinition {
|
68 | [index: string]: ServiceDefinition | ProtobufTypeDefinition;
|
69 | }
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | const requesterFuncs = {
|
77 | unary: Client.prototype.makeUnaryRequest,
|
78 | server_stream: Client.prototype.makeServerStreamRequest,
|
79 | client_stream: Client.prototype.makeClientStreamRequest,
|
80 | bidi: Client.prototype.makeBidiStreamRequest,
|
81 | };
|
82 |
|
83 | export interface ServiceClient extends Client {
|
84 | [methodName: string]: Function;
|
85 | }
|
86 |
|
87 | export interface ServiceClientConstructor {
|
88 | new (
|
89 | address: string,
|
90 | credentials: ChannelCredentials,
|
91 | options?: Partial<ChannelOptions>
|
92 | ): ServiceClient;
|
93 | service: ServiceDefinition;
|
94 | serviceName: string;
|
95 | }
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 | function isPrototypePolluted(key: string): boolean {
|
103 | return ['__proto__', 'prototype', 'constructor'].includes(key);
|
104 | }
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 | export function makeClientConstructor(
|
121 | methods: ServiceDefinition,
|
122 | serviceName: string,
|
123 | classOptions?: {}
|
124 | ): ServiceClientConstructor {
|
125 | if (!classOptions) {
|
126 | classOptions = {};
|
127 | }
|
128 |
|
129 | class ServiceClientImpl extends Client implements ServiceClient {
|
130 | static service: ServiceDefinition;
|
131 | static serviceName: string;
|
132 | [methodName: string]: Function;
|
133 | }
|
134 |
|
135 | Object.keys(methods).forEach((name) => {
|
136 | if (isPrototypePolluted(name)) {
|
137 | return;
|
138 | }
|
139 | const attrs = methods[name];
|
140 | let methodType: keyof typeof requesterFuncs;
|
141 |
|
142 | if (typeof name === 'string' && name.charAt(0) === '$') {
|
143 | throw new Error('Method names cannot start with $');
|
144 | }
|
145 | if (attrs.requestStream) {
|
146 | if (attrs.responseStream) {
|
147 | methodType = 'bidi';
|
148 | } else {
|
149 | methodType = 'client_stream';
|
150 | }
|
151 | } else {
|
152 | if (attrs.responseStream) {
|
153 | methodType = 'server_stream';
|
154 | } else {
|
155 | methodType = 'unary';
|
156 | }
|
157 | }
|
158 | const serialize = attrs.requestSerialize;
|
159 | const deserialize = attrs.responseDeserialize;
|
160 | const methodFunc = partial(
|
161 | requesterFuncs[methodType],
|
162 | attrs.path,
|
163 | serialize,
|
164 | deserialize
|
165 | );
|
166 | ServiceClientImpl.prototype[name] = methodFunc;
|
167 |
|
168 | Object.assign(ServiceClientImpl.prototype[name], attrs);
|
169 | if (attrs.originalName && !isPrototypePolluted(attrs.originalName)) {
|
170 | ServiceClientImpl.prototype[attrs.originalName] =
|
171 | ServiceClientImpl.prototype[name];
|
172 | }
|
173 | });
|
174 |
|
175 | ServiceClientImpl.service = methods;
|
176 | ServiceClientImpl.serviceName = serviceName;
|
177 |
|
178 | return ServiceClientImpl;
|
179 | }
|
180 |
|
181 | function partial(
|
182 | fn: Function,
|
183 | path: string,
|
184 | serialize: Function,
|
185 | deserialize: Function
|
186 | ): Function {
|
187 |
|
188 | return function (this: any, ...args: any[]) {
|
189 | return fn.call(this, path, serialize, deserialize, ...args);
|
190 | };
|
191 | }
|
192 |
|
193 | export interface GrpcObject {
|
194 | [index: string]:
|
195 | | GrpcObject
|
196 | | ServiceClientConstructor
|
197 | | ProtobufTypeDefinition;
|
198 | }
|
199 |
|
200 | function isProtobufTypeDefinition(
|
201 | obj: ServiceDefinition | ProtobufTypeDefinition
|
202 | ): obj is ProtobufTypeDefinition {
|
203 | return 'format' in obj;
|
204 | }
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | export function loadPackageDefinition(
|
212 | packageDef: PackageDefinition
|
213 | ): GrpcObject {
|
214 | const result: GrpcObject = {};
|
215 | for (const serviceFqn in packageDef) {
|
216 | if (Object.prototype.hasOwnProperty.call(packageDef, serviceFqn)) {
|
217 | const service = packageDef[serviceFqn];
|
218 | const nameComponents = serviceFqn.split('.');
|
219 | if (nameComponents.some((comp: string) => isPrototypePolluted(comp))) {
|
220 | continue;
|
221 | }
|
222 | const serviceName = nameComponents[nameComponents.length - 1];
|
223 | let current = result;
|
224 | for (const packageName of nameComponents.slice(0, -1)) {
|
225 | if (!current[packageName]) {
|
226 | current[packageName] = {};
|
227 | }
|
228 | current = current[packageName] as GrpcObject;
|
229 | }
|
230 | if (isProtobufTypeDefinition(service)) {
|
231 | current[serviceName] = service;
|
232 | } else {
|
233 | current[serviceName] = makeClientConstructor(service, serviceName, {});
|
234 | }
|
235 | }
|
236 | }
|
237 | return result;
|
238 | }
|