better-grpc 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  <div align="center">
2
2
 
3
+ ![Banner](./.github/assets/banner.png)
4
+
3
5
  # better-grpc
4
6
 
5
7
  > Simple, typed gRPC for TypeScript
@@ -38,7 +40,7 @@ yarn add better-grpc
38
40
  Create an abstract class that extends `Service` to define your service. Use the `server` and `client` helpers to define where your function is implemented and executed.
39
41
 
40
42
  ```typescript
41
- import { Service, client, server } from 'better-grpc';
43
+ import { Service, client, server, bidi } from 'better-grpc';
42
44
 
43
45
  abstract class MyService extends Service('MyService') {
44
46
  // This function is implemented and executed on the server.
@@ -46,6 +48,9 @@ abstract class MyService extends Service('MyService') {
46
48
 
47
49
  // This function is implemented and executed on the client.
48
50
  log = client<(message: string) => void>();
51
+
52
+ // This function supports bidirectional streaming between client and server.
53
+ chat = bidi<(message: string) => void>();
49
54
  }
50
55
  ```
51
56
 
@@ -104,6 +109,78 @@ await server.MyService.log('Greeting from server');
104
109
  // The client's console will show: '[Server]: Greeting from server'
105
110
  ```
106
111
 
112
+ ### 6. Use bidirectional streams
113
+
114
+ Bidirectional gRPCs expose a function that both emits values (when you invoke it) and acts as an async iterator so you can consume the opposite side's messages.
115
+
116
+ ```typescript
117
+ // Client usage
118
+ await client.MyService.chat('hello from client'); // emit to the server
119
+
120
+ for await (const [message] of client.MyService.chat) {
121
+ console.log('Server replied:', message);
122
+ break;
123
+ }
124
+
125
+ // Server usage mirrors the client
126
+ await server.MyService.chat('hello from server'); // emit to the client
127
+
128
+ for await (const [message] of server.MyService.chat) {
129
+ console.log('Client replied:', message);
130
+ break;
131
+ }
132
+ ```
133
+
134
+ ### 7. Attach typed metadata
135
+
136
+ Define metadata requirements with [Zod](https://github.com/colinhacks/zod) schemas, and `better-grpc` will automatically type the context on both sides and marshal the payload into gRPC metadata.
137
+
138
+ ```typescript
139
+ import { Service, server, bidi } from 'better-grpc';
140
+ import { z } from 'zod';
141
+
142
+ abstract class GreeterService extends Service('GreeterService') {
143
+ greet = server<(name: string) => string>()({
144
+ metadata: z.object({ requestId: z.string() }),
145
+ });
146
+
147
+ chat = bidi<(message: string) => void>()({
148
+ metadata: z.object({ room: z.string() }),
149
+ });
150
+ }
151
+ ```
152
+
153
+ Server implementations receive the typed metadata as the first argument:
154
+
155
+ ```typescript
156
+ const GreeterServerImpl = GreeterService.Server({
157
+ async greet(context, name) {
158
+ console.log('Request', context.metadata.requestId);
159
+ return `Hello, ${name}!`;
160
+ },
161
+ });
162
+ ```
163
+
164
+ On the client, unary calls that require metadata expose a `.withMeta()` helper, and bidi streams provide a `.context()` helper that must be awaited and called before sending messages (the bidi stream will be established after calling `.context()`):
165
+
166
+ ```typescript
167
+ await client.GreeterService.greet('Ada').withMeta({ requestId: crypto.randomUUID() });
168
+
169
+ await client.GreeterService.chat.context({
170
+ metadata: { room: 'general' },
171
+ });
172
+ // you must provide the context before calling the bidi function;
173
+ // otherwise, it will continue to wait.
174
+ await client.GreeterService.chat('hello from client');
175
+ ```
176
+
177
+ On the server side, the bidi function expose a `.context` value that can be used to access metadata:
178
+
179
+ ```typescript
180
+ const chatContext = await server.GreeterService.chat.context;
181
+ console.log(chatContext.metadata.room); // 'general'
182
+ ````
183
+
107
184
  ## Why `better-grpc`?
108
185
 
109
186
  The traditional workflow for creating gRPC services with TypeScript involves writing `.proto` files, using `protoc` to generate TypeScript code, and then using that generated code. This process can be cumbersome and result in a disconnect between your service definition and your code.
@@ -123,11 +200,15 @@ A factory function that creates an abstract service class.
123
200
 
124
201
  - `server<T>()`
125
202
 
126
- A helper function to define a server-side function signature. `T` should be a function type.
203
+ Defines a server-side unary function signature. `T` should be a function type. Call the returned descriptor with `({ metadata: z.object({...}) })` to require typed metadata for that RPC. Client code then calls `client.MyService.fn(...args).withMeta({...})`, and server handlers receive the context object as the first argument.
127
204
 
128
205
  - `client<T>()`
129
206
 
130
- A helper function to define a client-side function signature. `T` should be a function type.
207
+ Defines a client-side unary function signature. `T` should be a function type.
208
+
209
+ - `bidi<T>()`
210
+
211
+ Defines a bidirectional stream signature. `T` should be a function type that returns `void`. Like `server()`, you can pass `({ metadata: schema })` to type the attached metadata; client stubs expose `bidiFn.context({ metadata })` and server stubs expose `await bidiFn.context` to read it.
131
212
 
132
213
  - `createGrpcServer(port: number, ...services: ServiceImpl[])`
133
214
 
@@ -164,4 +245,4 @@ better-grpc: 126.681042ms
164
245
 
165
246
  ## License
166
247
 
167
- This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
248
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.