Skip to content

Commit 6b25dd7

Browse files
authored
Merge pull request #109 from paymog/patch-1
Add docs for classes with attributes
2 parents 18efcc1 + c1df1ed commit 6b25dd7

File tree

6 files changed

+256
-4
lines changed

6 files changed

+256
-4
lines changed

doc/server_impl_signature.md

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,6 @@ const server = new grpc.Server();
9999
server.addService(BookServiceService, Impl);
100100
```
101101

102-
This style is `recommended`. Since you can append more attributes in the `Impl` object, though they are not defined in `IBookServiceServer`, and you don't need to add `[name: string]: grpc.UntypedHandleCall` in your codes.
103-
104102
### Class Style
105103

106104
```typescript
@@ -120,4 +118,52 @@ const server = new grpc.Server();
120118
server.addService(BookServiceService, new Impl());
121119
```
122120

123-
This style is `NOT` recommended. Since only those already defined in the `IBookServiceServer` can be existing in this `Impl` class, and `[name: string]: grpc.UntypedHandleCall` is required for Class style.
121+
Only those already defined in the `IBookServiceServer` can be existing in this `Impl` class, and `[name: string]: grpc.UntypedHandleCall;` is required for Class style.
122+
123+
**Class style with magic generics**
124+
125+
The following is how you can break the restriction above, to add attributes to a class style implementation of a server:
126+
127+
```typescript
128+
// First we need to set up some magic generics inspired by https://github.com/agreatfool/grpc_tools_node_protoc_ts/issues/79#issuecomment-770173789
129+
type KnownKeys<T> = {
130+
[K in keyof T]: string extends K ? never : number extends K ? never : K
131+
} extends { [_ in keyof T]: infer U } ? U : never;
132+
133+
type KnownOnly<T extends Record<any, any>> = Pick<T, KnownKeys<T>>;
134+
135+
// Next we declare a new type using the above generics:
136+
type ITypedBookServer = KnownOnly<IBookServiceServer>;
137+
138+
// Now we declare the class
139+
class Impl implements ITypedBookServer {
140+
attr: string;
141+
142+
constructor(attr: string) {
143+
this.attr = attr;
144+
}
145+
146+
public getBook(call: grpc.ServerUnaryCall<GetBookRequest, Book>, callback: sendUnaryData<Book>): void {}
147+
148+
public getBooks(call: grpc.ServerDuplexStream<GetBookRequest, Book>) {}
149+
150+
public getBooksViaAuthor(call: grpc.ServerWritableStream<GetBookViaAuthor, Book>) {}
151+
152+
public getGreatestBook(call: grpc.ServerReadableStream<GetBookRequest, Book>, callback: sendUnaryData<Book>) {}
153+
}
154+
155+
// now we need to initialize the above Impl. First we need to extend grpc.Server
156+
class TypedServerOverride extends grpc.Server {
157+
addTypedService<TypedServiceImplementation extends Record<any,any>>(service: ServiceDefinition, implementation: TypedServiceImplementation): void {
158+
this.addService(service, implementation);
159+
}
160+
}
161+
162+
// now perform the actual initialization
163+
const server = new TypedServerOverride();
164+
server.addTypedService<ITypedBookServer>(BookServiceService, new Impl("hello world"));
165+
```
166+
167+
Runnable example could be found here:
168+
* [examples/bash/grpcjs/server.magic_generics.sh](https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/v5.3.1/examples/bash/grpcjs/server.magic_generics.sh)
169+
* [examples/src/grpcjs/server.magic_generics.ts](https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/v5.3.1/examples/src/grpcjs/server.magic_generics.ts)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
3+
BASEDIR=$(dirname "$0")
4+
cd ${BASEDIR}/../../
5+
6+
DEBUG=* node ./build/grpcjs/server.magic_generics.js

examples/build/grpcjs/server.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/build/grpcjs/server.magic_generics.js

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/build/grpcjs/server.magic_generics.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env node
2+
3+
import * as debug from "debug";
4+
import * as grpc from "@grpc/grpc-js";
5+
6+
import {BookServiceService, IBookServiceServer} from "./proto/book_grpc_pb";
7+
import { Book, GetBookRequest, GetBookViaAuthor } from "./proto/book_pb";
8+
9+
const log = debug("SampleServer");
10+
11+
type KnownKeys<T> = {
12+
[K in keyof T]: string extends K ? never : number extends K ? never : K
13+
} extends { [_ in keyof T]: infer U } ? U : never;
14+
15+
type KnownOnly<T extends Record<any, any>> = Pick<T, KnownKeys<T>>;
16+
17+
type ITypedBookServer = KnownOnly<IBookServiceServer>;
18+
19+
class TypedServerOverride extends grpc.Server {
20+
public addTypedService<TypedServiceImplementation extends Record<any, any>>(
21+
service: grpc.ServiceDefinition, implementation: TypedServiceImplementation,
22+
): void {
23+
this.addService(service, implementation);
24+
}
25+
}
26+
27+
// tslint:disable-next-line:max-classes-per-file
28+
class ServerImpl implements ITypedBookServer {
29+
public attr: string;
30+
31+
constructor(attr: string) {
32+
this.attr = attr;
33+
}
34+
35+
public getBook(call: grpc.ServerUnaryCall<GetBookRequest, Book>, callback: grpc.sendUnaryData<Book>): void {
36+
const book = new Book();
37+
38+
book.setTitle("DefaultBook");
39+
book.setAuthor("DefaultAuthor");
40+
41+
log(`[getBook] Done: ${JSON.stringify(book.toObject())}`);
42+
callback(null, book);
43+
}
44+
45+
public getBooks(call: grpc.ServerDuplexStream<GetBookRequest, Book>): void {
46+
call.on("data", (request: GetBookRequest) => {
47+
const reply = new Book();
48+
reply.setTitle(`Book${request.getIsbn()}`);
49+
reply.setAuthor(`Author${request.getIsbn()}`);
50+
reply.setIsbn(request.getIsbn());
51+
log(`[getBooks] Write: ${JSON.stringify(reply.toObject())}`);
52+
call.write(reply);
53+
});
54+
call.on("end", () => {
55+
log("[getBooks] Done.");
56+
call.end();
57+
});
58+
}
59+
60+
public getBooksViaAuthor(call: grpc.ServerWritableStream<GetBookViaAuthor, Book>): void {
61+
log(`[getBooksViaAuthor] Request: ${JSON.stringify(call.request.toObject())}`);
62+
for (let i = 1; i <= 10; i++) {
63+
const reply = new Book();
64+
reply.setTitle(`Book${i}`);
65+
reply.setAuthor(call.request.getAuthor());
66+
reply.setIsbn(i);
67+
log(`[getBooksViaAuthor] Write: ${JSON.stringify(reply.toObject())}`);
68+
call.write(reply);
69+
}
70+
log("[getBooksViaAuthor] Done.");
71+
call.end();
72+
}
73+
74+
public getGreatestBook(call: grpc.ServerReadableStream<GetBookRequest, Book>, callback: grpc.sendUnaryData<Book>): void {
75+
let lastOne: GetBookRequest;
76+
call.on("data", (request: GetBookRequest) => {
77+
log(`[getGreatestBook] Request: ${JSON.stringify(request.toObject())}`);
78+
lastOne = request;
79+
});
80+
call.on("end", () => {
81+
const reply = new Book();
82+
reply.setIsbn(lastOne.getIsbn());
83+
reply.setTitle("LastOne");
84+
reply.setAuthor("LastOne");
85+
log(`[getGreatestBook] Done: ${JSON.stringify(reply.toObject())}`);
86+
callback(null, reply);
87+
});
88+
}
89+
}
90+
91+
function startServer() {
92+
const server = new TypedServerOverride();
93+
94+
server.addTypedService<ITypedBookServer>(BookServiceService, new ServerImpl("hello world"));
95+
server.bindAsync("127.0.0.1:50051", grpc.ServerCredentials.createInsecure(), (err, port) => {
96+
if (err) {
97+
throw err;
98+
}
99+
log(`Server started, listening: 127.0.0.1:${port}`);
100+
server.start();
101+
});
102+
}
103+
104+
startServer();
105+
106+
process.on("uncaughtException", (err) => {
107+
log(`process on uncaughtException error: ${err}`);
108+
});
109+
110+
process.on("unhandledRejection", (err) => {
111+
log(`process on unhandledRejection error: ${err}`);
112+
});

0 commit comments

Comments
 (0)