better-grpc 0.0.1 → 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/LICENSE +21 -0
- package/README.md +237 -2
- package/dist/index.cjs +24722 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +86 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +24715 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -8
- package/index.js +0 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Photon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,13 +1,248 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
1
5
|
# better-grpc
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
> Simple, typed gRPC for TypeScript
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
[](https://www.typescriptlang.org/)
|
|
12
|
+
[](./LICENSE)
|
|
13
|
+
[](https://discord.gg/bZd4CMd2H5)
|
|
14
|
+
|
|
15
|
+
**`better-grpc`** is a library that provides a new way to define and use RPC services in TypeScript, focusing on developer experience and type safety. It eliminates the need for `.proto` files and code generation, allowing you to define your services entirely in TypeScript.
|
|
16
|
+
|
|
17
|
+
The core idea is to enable seamless communication between a client and a server, allowing you to call server-side functions from the client and client-side functions from the server, as if they were local.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Type-Safe:** Define your services in TypeScript and get full type safety and autocompletion for your clients and servers.
|
|
22
|
+
- **No `.proto` files:** No need to write `.proto` files or use `protoc` to generate code.
|
|
23
|
+
- **Simple API:** The API is designed to be simple and intuitive.
|
|
24
|
+
- **Symmetric Experience:** Call client-side functions from the server with the same syntax as calling server-side functions from the client.
|
|
4
25
|
|
|
5
26
|
## Installation
|
|
6
27
|
|
|
7
28
|
```bash
|
|
29
|
+
bun add better-grpc
|
|
30
|
+
# or
|
|
8
31
|
npm install better-grpc
|
|
32
|
+
# or
|
|
33
|
+
yarn add better-grpc
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### 1. Define a Service
|
|
39
|
+
|
|
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.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { Service, client, server, bidi } from 'better-grpc';
|
|
44
|
+
|
|
45
|
+
abstract class MyService extends Service('MyService') {
|
|
46
|
+
// This function is implemented and executed on the server.
|
|
47
|
+
sayHello = server<(name: string) => string>();
|
|
48
|
+
|
|
49
|
+
// This function is implemented and executed on the client.
|
|
50
|
+
log = client<(message: string) => void>();
|
|
51
|
+
|
|
52
|
+
// This function supports bidirectional streaming between client and server.
|
|
53
|
+
chat = bidi<(message: string) => void>();
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Implement the Service
|
|
58
|
+
|
|
59
|
+
Provide the implementations for the functions you defined for both the server and the client.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// Server-side implementation
|
|
63
|
+
const myServiceImpl = MyService.Server({
|
|
64
|
+
async sayHello(name: string) {
|
|
65
|
+
return `Hello, ${name}!`;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Client-side implementation
|
|
70
|
+
const myClientImpl = MyService.Client({
|
|
71
|
+
async log(message: string) {
|
|
72
|
+
console.log(`[Server]: ${message}`);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Create a Server
|
|
78
|
+
|
|
79
|
+
Create and start the server, passing in your service implementation.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { createGrpcServer } from 'better-grpc';
|
|
83
|
+
|
|
84
|
+
const server = await createGrpcServer(50051, myServiceImpl);
|
|
85
|
+
console.log('Server listening on port 50051');
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 4. Create a Client
|
|
89
|
+
|
|
90
|
+
Create a client for your service.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { createGrpcClient } from 'better-grpc';
|
|
94
|
+
|
|
95
|
+
const client = await createGrpcClient('localhost:50051', myClientImpl);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 5. Make remote calls
|
|
99
|
+
|
|
100
|
+
Now you can call remote functions from both the client and the server.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// On the client, call the server's `sayHello` function
|
|
104
|
+
const response = await client.MyService.sayHello('world');
|
|
105
|
+
console.log(response); // Outputs: 'Hello, world!'
|
|
106
|
+
|
|
107
|
+
// On the server, call client's `log` function
|
|
108
|
+
await server.MyService.log('Greeting from server');
|
|
109
|
+
// The client's console will show: '[Server]: Greeting from server'
|
|
110
|
+
```
|
|
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
|
+
|
|
184
|
+
## Why `better-grpc`?
|
|
185
|
+
|
|
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.
|
|
187
|
+
|
|
188
|
+
`better-grpc` solves this problem by allowing you to define your services entirely in TypeScript. This has several advantages:
|
|
189
|
+
|
|
190
|
+
- **Single Source of Truth:** Your service definition lives in your TypeScript code, right next to your implementation.
|
|
191
|
+
- **Improved Type Safety:** Leverage TypeScript's powerful type system for excellent autocompletion and type safety across your client and server.
|
|
192
|
+
- **Simplified Workflow:** No more `.proto` files, no more code generation. Just write TypeScript.
|
|
193
|
+
- **Symmetric Communication:** The server can invoke client functions with the same ease that the client invokes server functions, enabling powerful, bidirectional communication patterns.
|
|
194
|
+
|
|
195
|
+
## API
|
|
196
|
+
|
|
197
|
+
- `Service(name: string)`
|
|
198
|
+
|
|
199
|
+
A factory function that creates an abstract service class.
|
|
200
|
+
|
|
201
|
+
- `server<T>()`
|
|
202
|
+
|
|
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.
|
|
204
|
+
|
|
205
|
+
- `client<T>()`
|
|
206
|
+
|
|
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.
|
|
212
|
+
|
|
213
|
+
- `createGrpcServer(port: number, ...services: ServiceImpl[])`
|
|
214
|
+
|
|
215
|
+
Creates and starts a gRPC server.
|
|
216
|
+
|
|
217
|
+
- `createGrpcClient(address: string, ...services: ServiceImpl[])`
|
|
218
|
+
|
|
219
|
+
Creates and starts a gRPC client.
|
|
220
|
+
|
|
221
|
+
## Benchmarks
|
|
222
|
+
|
|
223
|
+
### Simple "Hello World"
|
|
224
|
+
|
|
225
|
+
> [!NOTE]
|
|
226
|
+
> This benchmark's server and client were run on same local machine.
|
|
227
|
+
|
|
228
|
+
#### tRPC
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
tRPC: 1543.021833ms
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### Elysia
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
Elysia: 128.935791ms
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### better-grpc
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
better-grpc: 126.681042ms
|
|
9
244
|
```
|
|
10
245
|
|
|
11
246
|
## License
|
|
12
247
|
|
|
13
|
-
MIT
|
|
248
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|