kkrpc 0.5.0 → 0.6.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 +782 -110
- package/dist/browser-mod.cjs +1 -194
- package/dist/browser-mod.d.cts +15 -7
- package/dist/browser-mod.d.cts.map +1 -1
- package/dist/browser-mod.d.ts +16 -8
- package/dist/browser-mod.d.ts.map +1 -1
- package/dist/browser-mod.js +1 -174
- package/dist/browser-mod.js.map +1 -1
- package/dist/{channel-C92Q3vK-.d.ts → channel-ChGarSnI.d.cts} +6 -2
- package/dist/channel-ChGarSnI.d.cts.map +1 -0
- package/dist/{channel-QTVxXCE5.d.cts → channel-K9w_2vmv.d.ts} +6 -2
- package/dist/channel-K9w_2vmv.d.ts.map +1 -0
- package/dist/channel-LGw9tl8f.js +7 -0
- package/dist/channel-LGw9tl8f.js.map +1 -0
- package/dist/channel-OGNDLfsd.cjs +6 -0
- package/dist/chrome-extension.cjs +1 -83
- package/dist/chrome-extension.d.cts +8 -4
- package/dist/chrome-extension.d.cts.map +1 -1
- package/dist/chrome-extension.d.ts +9 -5
- package/dist/chrome-extension.d.ts.map +1 -1
- package/dist/chrome-extension.js +1 -72
- package/dist/chrome-extension.js.map +1 -1
- package/dist/chunk-DjWAcSYV.cjs +1 -0
- package/dist/deno-B319Kxci.js +2 -0
- package/dist/deno-B319Kxci.js.map +1 -0
- package/dist/{deno-BzNLlX8S.d.ts → deno-BNN8LLrd.d.cts} +6 -2
- package/dist/deno-BNN8LLrd.d.cts.map +1 -0
- package/dist/deno-Cl_O9sLW.cjs +1 -0
- package/dist/{deno-noilmrf7.d.cts → deno-DS3TcO_u.d.ts} +6 -2
- package/dist/deno-DS3TcO_u.d.ts.map +1 -0
- package/dist/deno-mod.cjs +1 -10
- package/dist/deno-mod.d.cts +4 -4
- package/dist/deno-mod.d.ts +4 -4
- package/dist/deno-mod.js +1 -4
- package/dist/electron-ipc.cjs +1 -0
- package/dist/electron-ipc.d.cts +142 -0
- package/dist/electron-ipc.d.cts.map +1 -0
- package/dist/electron-ipc.d.ts +142 -0
- package/dist/electron-ipc.d.ts.map +1 -0
- package/dist/electron-ipc.js +2 -0
- package/dist/electron-ipc.js.map +1 -0
- package/dist/electron.cjs +1 -0
- package/dist/electron.d.cts +69 -0
- package/dist/electron.d.cts.map +1 -0
- package/dist/electron.d.ts +69 -0
- package/dist/electron.d.ts.map +1 -0
- package/dist/electron.js +2 -0
- package/dist/electron.js.map +1 -0
- package/dist/http-BEp3rEW8.js +2 -0
- package/dist/http-BEp3rEW8.js.map +1 -0
- package/dist/{http-Cvnz7K2R.d.cts → http-CcLOuQmc.d.ts} +12 -2
- package/dist/http-CcLOuQmc.d.ts.map +1 -0
- package/dist/{http-xFcyKRFw.d.ts → http-DWq6Ez_h.d.cts} +12 -2
- package/dist/http-DWq6Ez_h.d.cts.map +1 -0
- package/dist/http-DoUu8nzZ.cjs +1 -0
- package/dist/http.cjs +1 -23
- package/dist/http.d.cts +3 -3
- package/dist/http.d.ts +3 -3
- package/dist/http.js +1 -19
- package/dist/http.js.map +1 -1
- package/dist/{interface-DIWzRMw1.d.cts → interface-DPtHJBBS.d.cts} +9 -10
- package/dist/interface-DPtHJBBS.d.cts.map +1 -0
- package/dist/{interface-Bt8kzlyu.d.ts → interface-DRqrAKo-.d.ts} +9 -10
- package/dist/interface-DRqrAKo-.d.ts.map +1 -0
- package/dist/kafka-BMOHF0lZ.d.ts +98 -0
- package/dist/kafka-BMOHF0lZ.d.ts.map +1 -0
- package/dist/kafka-DrhWN0Wx.js +2 -0
- package/dist/kafka-DrhWN0Wx.js.map +1 -0
- package/dist/kafka-_Fcc7erL.d.cts +98 -0
- package/dist/kafka-_Fcc7erL.d.cts.map +1 -0
- package/dist/kafka-x0zuIRyy.cjs +1 -0
- package/dist/kafka.cjs +1 -0
- package/dist/kafka.d.cts +3 -0
- package/dist/kafka.d.ts +3 -0
- package/dist/kafka.js +1 -0
- package/dist/mod.cjs +3 -222
- package/dist/mod.d.cts +292 -14
- package/dist/mod.d.cts.map +1 -1
- package/dist/mod.d.ts +293 -15
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +3 -197
- package/dist/mod.js.map +1 -1
- package/dist/nats-Ba4bXoY5.js +2 -0
- package/dist/nats-Ba4bXoY5.js.map +1 -0
- package/dist/nats-Bkr44tQB.d.cts +78 -0
- package/dist/nats-Bkr44tQB.d.cts.map +1 -0
- package/dist/nats-CMglzmZn.cjs +1 -0
- package/dist/nats-D3XOZAGl.d.ts +78 -0
- package/dist/nats-D3XOZAGl.d.ts.map +1 -0
- package/dist/nats.cjs +1 -0
- package/dist/nats.d.cts +3 -0
- package/dist/nats.d.ts +3 -0
- package/dist/nats.js +1 -0
- package/dist/rabbitmq-B5NVNJ3X.cjs +1 -0
- package/dist/rabbitmq-CpeO6XdQ.d.ts +61 -0
- package/dist/rabbitmq-CpeO6XdQ.d.ts.map +1 -0
- package/dist/rabbitmq-DAUXsuvL.d.cts +61 -0
- package/dist/rabbitmq-DAUXsuvL.d.cts.map +1 -0
- package/dist/rabbitmq-DbxfLKQj.js +2 -0
- package/dist/rabbitmq-DbxfLKQj.js.map +1 -0
- package/dist/rabbitmq.cjs +1 -0
- package/dist/rabbitmq.d.cts +5 -0
- package/dist/rabbitmq.d.ts +5 -0
- package/dist/rabbitmq.js +1 -0
- package/dist/redis-streams-BV472jY5.js +2 -0
- package/dist/redis-streams-BV472jY5.js.map +1 -0
- package/dist/redis-streams-CGnDQYMH.cjs +1 -0
- package/dist/redis-streams-DpbNc20y.d.cts +97 -0
- package/dist/redis-streams-DpbNc20y.d.cts.map +1 -0
- package/dist/redis-streams-avvO_U2r.d.ts +97 -0
- package/dist/redis-streams-avvO_U2r.d.ts.map +1 -0
- package/dist/redis-streams.cjs +1 -0
- package/dist/redis-streams.d.cts +3 -0
- package/dist/redis-streams.d.ts +3 -0
- package/dist/redis-streams.js +1 -0
- package/dist/socketio.cjs +1 -123
- package/dist/socketio.d.cts +12 -4
- package/dist/socketio.d.cts.map +1 -1
- package/dist/socketio.d.ts +12 -4
- package/dist/socketio.d.ts.map +1 -1
- package/dist/socketio.js +1 -120
- package/dist/socketio.js.map +1 -1
- package/dist/tauri-CSoj53la.js +2 -0
- package/dist/tauri-CSoj53la.js.map +1 -0
- package/dist/{tauri-DwFX963h.d.cts → tauri-DIJzjZwG.d.ts} +16 -4
- package/dist/tauri-DIJzjZwG.d.ts.map +1 -0
- package/dist/tauri-DvS2Czwp.cjs +1 -0
- package/dist/{tauri-BAorUr9n.d.ts → tauri-d0yDsUnV.d.cts} +16 -4
- package/dist/tauri-d0yDsUnV.d.cts.map +1 -0
- package/dist/{transfer-handlers-BbGAQoCs.d.ts → transfer-handlers-3wBZpi5F.d.ts} +2 -2
- package/dist/{transfer-handlers-BbGAQoCs.d.ts.map → transfer-handlers-3wBZpi5F.d.ts.map} +1 -1
- package/dist/{transfer-handlers-ZiPAV3RZ.d.cts → transfer-handlers-zy5mMRny.d.cts} +2 -2
- package/dist/{transfer-handlers-ZiPAV3RZ.d.cts.map → transfer-handlers-zy5mMRny.d.cts.map} +1 -1
- package/dist/{utils-BQ7rl7lY.d.ts → utils-BEgiHk89.d.ts} +1 -1
- package/dist/utils-BEgiHk89.d.ts.map +1 -0
- package/package.json +85 -5
- package/dist/channel-BYtPphRu.js +0 -743
- package/dist/channel-BYtPphRu.js.map +0 -1
- package/dist/channel-C92Q3vK-.d.ts.map +0 -1
- package/dist/channel-CgIjXyNX.cjs +0 -839
- package/dist/channel-QTVxXCE5.d.cts.map +0 -1
- package/dist/chunk-CUT6urMc.cjs +0 -30
- package/dist/deno-BzNLlX8S.d.ts.map +0 -1
- package/dist/deno-DAHG1vvV.js +0 -33
- package/dist/deno-DAHG1vvV.js.map +0 -1
- package/dist/deno-DyicMPIa.cjs +0 -38
- package/dist/deno-noilmrf7.d.cts.map +0 -1
- package/dist/http-5KkjLA6X.js +0 -97
- package/dist/http-5KkjLA6X.js.map +0 -1
- package/dist/http-Bq1OqmJP.cjs +0 -109
- package/dist/http-Cvnz7K2R.d.cts.map +0 -1
- package/dist/http-xFcyKRFw.d.ts.map +0 -1
- package/dist/interface-Bt8kzlyu.d.ts.map +0 -1
- package/dist/interface-DIWzRMw1.d.cts.map +0 -1
- package/dist/tauri-BAorUr9n.d.ts.map +0 -1
- package/dist/tauri-C7MVgjRt.js +0 -160
- package/dist/tauri-C7MVgjRt.js.map +0 -1
- package/dist/tauri-DwFX963h.d.cts.map +0 -1
- package/dist/tauri-JjaZEH0s.cjs +0 -177
- package/dist/utils-BQ7rl7lY.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
# 🚀 kkrpc
|
|
5
4
|
|
|
6
5
|
## TypeScript-First RPC Library
|
|
7
6
|
|
|
7
|
+
[](https://deepwiki.com/kunkunsh/kkrpc)
|
|
8
|
+
[](https://zread.ai/kunkunsh/kkrpc)
|
|
8
9
|
[](https://www.npmjs.com/package/kkrpc)
|
|
9
10
|
[](https://jsr.io/@kunkun/kkrpc)
|
|
10
11
|
[](https://github.com/kunkunsh/kkrpc/blob/main/LICENSE)
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
[](https://github.com/kunkunsh/kkrpc)
|
|
13
14
|
[](https://kunkunsh.github.io/kkrpc/)
|
|
14
15
|
[](https://excalidraw.com/#json=xp6GbAJVAx3nU-h3PhaxW,oYBNvYmCRsQ2XR3MQo73Ug)
|
|
16
|
+
[](https://docs.kkrpc.kunkun.sh/llms.txt)
|
|
15
17
|
|
|
16
18
|
</div>
|
|
17
19
|
|
|
@@ -25,10 +27,11 @@ Call remote functions as if they were local, with full TypeScript type safety an
|
|
|
25
27
|
|
|
26
28
|
**Similar to Comlink but with bidirectional communication** and support for multiple environments - both client and server can expose functions for the other to call across Node.js, Deno, Bun, and browser environments.
|
|
27
29
|
|
|
28
|
-
[**Quick Start**](#-quick-start) • [**Documentation**](https://kunkunsh.github.io/kkrpc/) • [**Examples**](#-examples) • [**API Reference**](https://jsr.io/@kunkun/kkrpc/doc)
|
|
30
|
+
[**Quick Start**](#-quick-start) • [**Documentation**](https://kunkunsh.github.io/kkrpc/) • [**Examples**](#-examples) • [**API Reference**](https://jsr.io/@kunkun/kkrpc/doc) • [**LLM Docs**](https://docs.kkrpc.kunkun.sh/llms.txt) • [**中文文档**](./README.zh.md)
|
|
29
31
|
|
|
30
32
|
<div align="center">
|
|
31
33
|
|
|
34
|
+
<img src="https://imgur.com/19XswxO.jpg" style="max-width: 800px; width: 100%; margin-bottom: 20px;"/>
|
|
32
35
|
<img src="https://imgur.com/vR3Lmv0.png" style="max-height: 200px; margin: 10px;"/>
|
|
33
36
|
<img src="https://i.imgur.com/zmOHNfu.png" style="max-height: 250px; margin: 10px;"/>
|
|
34
37
|
<img src="https://imgur.com/u728aVv.png" style="max-height: 400px; margin: 10px;"/>
|
|
@@ -46,19 +49,19 @@ kkrpc stands out in the crowded RPC landscape by offering **true cross-runtime c
|
|
|
46
49
|
|
|
47
50
|
<div align="center">
|
|
48
51
|
|
|
49
|
-
| Feature
|
|
50
|
-
|
|
51
|
-
| **🔄 Cross-runtime**
|
|
52
|
-
| **🛡️ Type-safe**
|
|
53
|
-
| **↔️ Bidirectional**
|
|
54
|
-
| **🏠 Property Access**
|
|
55
|
-
| **💥 Error Preservation**
|
|
56
|
-
| **🌐 Multiple Transports**
|
|
57
|
-
| **📞 Callback Support**
|
|
58
|
-
| **🔗 Nested Calls**
|
|
59
|
-
| **📦 Auto Serialization**
|
|
60
|
-
| **⚡ Zero Config**
|
|
61
|
-
| **🚀 Transferable Objects** | Zero-copy transfers for large data (40-100x faster)
|
|
52
|
+
| Feature | Description |
|
|
53
|
+
| --------------------------- | -------------------------------------------------------------- |
|
|
54
|
+
| **🔄 Cross-runtime** | Works seamlessly across Node.js, Deno, Bun, browsers, and more |
|
|
55
|
+
| **🛡️ Type-safe** | Full TypeScript inference and IDE autocompletion support |
|
|
56
|
+
| **↔️ Bidirectional** | Both endpoints can expose and call APIs simultaneously |
|
|
57
|
+
| **🏠 Property Access** | Remote getters/setters with dot notation (`await api.prop`) |
|
|
58
|
+
| **💥 Error Preservation** | Complete error objects across RPC boundaries |
|
|
59
|
+
| **🌐 Multiple Transports** | stdio, HTTP, WebSocket, postMessage, Chrome extensions |
|
|
60
|
+
| **📞 Callback Support** | Remote functions can accept callback functions |
|
|
61
|
+
| **🔗 Nested Calls** | Deep method chaining like `api.math.operations.calculate()` |
|
|
62
|
+
| **📦 Auto Serialization** | Intelligent JSON/superjson detection |
|
|
63
|
+
| **⚡ Zero Config** | No schema files or code generation required |
|
|
64
|
+
| **🚀 Transferable Objects** | Zero-copy transfers for large data (40-100x faster) |
|
|
62
65
|
|
|
63
66
|
</div>
|
|
64
67
|
|
|
@@ -68,40 +71,47 @@ kkrpc stands out in the crowded RPC landscape by offering **true cross-runtime c
|
|
|
68
71
|
|
|
69
72
|
```mermaid
|
|
70
73
|
graph LR
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
74
|
+
A[kkrpc] --> B[Node.js]
|
|
75
|
+
A --> C[Deno]
|
|
76
|
+
A --> D[Bun]
|
|
77
|
+
A --> E[Browser]
|
|
78
|
+
A --> F[Chrome Extension]
|
|
79
|
+
A --> G[Tauri]
|
|
80
|
+
|
|
81
|
+
B -.-> H[stdio]
|
|
82
|
+
C -.-> H
|
|
83
|
+
D -.-> H
|
|
84
|
+
|
|
85
|
+
E -.-> I[postMessage]
|
|
86
|
+
E -.-> J[Web Workers]
|
|
87
|
+
E -.-> K[iframes]
|
|
88
|
+
|
|
89
|
+
F -.-> L[Chrome Ports]
|
|
90
|
+
|
|
91
|
+
G -.-> M[Shell Plugin]
|
|
92
|
+
|
|
93
|
+
style A fill:#ff6b6b,stroke:#333,stroke-width:2px
|
|
91
94
|
```
|
|
92
95
|
|
|
93
96
|
</div>
|
|
94
97
|
|
|
95
98
|
### 📡 Transport Protocols
|
|
96
99
|
|
|
97
|
-
| Transport
|
|
98
|
-
|
|
99
|
-
| **stdio**
|
|
100
|
-
| **postMessage**
|
|
101
|
-
| **HTTP**
|
|
102
|
-
| **WebSocket**
|
|
103
|
-
| **
|
|
104
|
-
| **
|
|
100
|
+
| Transport | Use Case | Supported Runtimes |
|
|
101
|
+
| -------------------- | ----------------------------------------------------- | -------------------------------------- |
|
|
102
|
+
| **stdio** | Process-to-process communication | Node.js ↔ Deno ↔ Bun |
|
|
103
|
+
| **postMessage** | Browser context communication | Browser ↔ Web Workers ↔ iframes |
|
|
104
|
+
| **HTTP** | Web API communication | All runtimes |
|
|
105
|
+
| **WebSocket** | Real-time communication | All runtimes |
|
|
106
|
+
| **Hono WebSocket** | High-performance WebSocket with Hono framework | Node.js, Deno, Bun, Cloudflare Workers |
|
|
107
|
+
| **Socket.IO** | Enhanced real-time with rooms/namespaces | All runtimes |
|
|
108
|
+
| **Elysia WebSocket** | Modern TypeScript framework WebSocket integration | Bun, Node.js, Deno |
|
|
109
|
+
| **Chrome Extension** | Extension component communication | Chrome Extension contexts |
|
|
110
|
+
| **RabbitMQ** | Message queue communication | Node.js, Deno, Bun |
|
|
111
|
+
| **Redis Streams** | Stream-based messaging with persistence | Node.js, Deno, Bun |
|
|
112
|
+
| **Kafka** | Distributed streaming platform | Node.js, Deno, Bun |
|
|
113
|
+
| **NATS** | High-performance messaging system | Node.js, Deno, Bun |
|
|
114
|
+
| **Electron** | Desktop app IPC (Renderer ↔ Main ↔ Utility Process) | Electron |
|
|
105
115
|
|
|
106
116
|
The core of **kkrpc** design is in `RPCChannel` and `IoInterface`.
|
|
107
117
|
|
|
@@ -161,7 +171,7 @@ For backward compatibility, the receiving side will automatically detect the ser
|
|
|
161
171
|
|
|
162
172
|
### Installation
|
|
163
173
|
|
|
164
|
-
|
|
174
|
+
|
|
165
175
|
|
|
166
176
|
```bash
|
|
167
177
|
# npm
|
|
@@ -177,41 +187,35 @@ pnpm add kkrpc
|
|
|
177
187
|
import { RPCChannel } from "jsr:@kunkun/kkrpc"
|
|
178
188
|
```
|
|
179
189
|
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
190
|
### Basic Example
|
|
183
191
|
|
|
184
|
-
<div align="center">
|
|
185
|
-
|
|
186
192
|
```typescript
|
|
187
193
|
// server.ts
|
|
188
|
-
import {
|
|
194
|
+
import { NodeIo, RPCChannel } from "kkrpc"
|
|
189
195
|
|
|
190
196
|
const api = {
|
|
191
|
-
|
|
192
|
-
|
|
197
|
+
greet: (name: string) => `Hello, ${name}!`,
|
|
198
|
+
add: (a: number, b: number) => a + b
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
const rpc = new RPCChannel(new NodeIo(process.stdin, process.stdout), {
|
|
196
|
-
|
|
202
|
+
expose: api
|
|
197
203
|
})
|
|
198
204
|
```
|
|
199
205
|
|
|
200
206
|
```typescript
|
|
201
207
|
// client.ts
|
|
202
|
-
import { RPCChannel, NodeIo } from "kkrpc"
|
|
203
208
|
import { spawn } from "child_process"
|
|
209
|
+
import { NodeIo, RPCChannel } from "kkrpc"
|
|
204
210
|
|
|
205
211
|
const worker = spawn("deno", ["run", "server.ts"])
|
|
206
212
|
const rpc = new RPCChannel(new NodeIo(worker.stdout, worker.stdin))
|
|
207
213
|
const api = rpc.getAPI<typeof api>()
|
|
208
214
|
|
|
209
215
|
console.log(await api.greet("World")) // "Hello, World!"
|
|
210
|
-
console.log(await api.add(5, 3))
|
|
216
|
+
console.log(await api.add(5, 3)) // 8
|
|
211
217
|
```
|
|
212
218
|
|
|
213
|
-
</div>
|
|
214
|
-
|
|
215
219
|
## 📚 Examples
|
|
216
220
|
|
|
217
221
|
Below are simple examples to get you started quickly.
|
|
@@ -278,9 +282,13 @@ kkrpc preserves complete error information across RPC boundaries:
|
|
|
278
282
|
```ts
|
|
279
283
|
// Custom error class
|
|
280
284
|
class DatabaseError extends Error {
|
|
281
|
-
constructor(
|
|
285
|
+
constructor(
|
|
286
|
+
message: string,
|
|
287
|
+
public code: number,
|
|
288
|
+
public query: string
|
|
289
|
+
) {
|
|
282
290
|
super(message)
|
|
283
|
-
this.name =
|
|
291
|
+
this.name = "DatabaseError"
|
|
284
292
|
}
|
|
285
293
|
}
|
|
286
294
|
|
|
@@ -302,11 +310,11 @@ try {
|
|
|
302
310
|
await api.getUserById("")
|
|
303
311
|
} catch (error) {
|
|
304
312
|
// All error properties are preserved:
|
|
305
|
-
console.log(error.name)
|
|
306
|
-
console.log(error.message)
|
|
307
|
-
console.log(error.code)
|
|
308
|
-
console.log(error.query)
|
|
309
|
-
console.log(error.stack)
|
|
313
|
+
console.log(error.name) // "DatabaseError"
|
|
314
|
+
console.log(error.message) // "Invalid user ID"
|
|
315
|
+
console.log(error.code) // 400
|
|
316
|
+
console.log(error.query) // "SELECT * FROM users WHERE id = ?"
|
|
317
|
+
console.log(error.stack) // Full stack trace
|
|
310
318
|
console.log(error.timestamp) // ISO timestamp
|
|
311
319
|
console.log(error.requestId) // Request ID
|
|
312
320
|
}
|
|
@@ -341,14 +349,14 @@ expect(sum).toBe(3)
|
|
|
341
349
|
kkrpc supports zero-copy transfer of large data structures using browser's native transferable objects. This provides 40-100x performance improvement for large binary data transfers.
|
|
342
350
|
|
|
343
351
|
```ts
|
|
344
|
-
import { RPCChannel,
|
|
352
|
+
import { RPCChannel, transfer, WorkerParentIO } from "kkrpc/browser"
|
|
345
353
|
|
|
346
354
|
const worker = new Worker("worker.js")
|
|
347
355
|
const io = new WorkerParentIO(worker)
|
|
348
356
|
const rpc = new RPCChannel(io)
|
|
349
357
|
const api = rpc.getAPI<{
|
|
350
|
-
|
|
351
|
-
|
|
358
|
+
processBuffer(buffer: ArrayBuffer): Promise<number>
|
|
359
|
+
generateData(size: number): Promise<ArrayBuffer>
|
|
352
360
|
}>()
|
|
353
361
|
|
|
354
362
|
// Create a large buffer (10MB)
|
|
@@ -367,13 +375,174 @@ const newBuffer = await api.generateData(5 * 1024 * 1024)
|
|
|
367
375
|
console.log("Received from worker:", newBuffer.byteLength) // 5242880
|
|
368
376
|
```
|
|
369
377
|
|
|
378
|
+
### Hono WebSocket Example
|
|
379
|
+
|
|
380
|
+
Hono WebSocket adapter provides seamless integration with the Hono framework's high-performance WebSocket support.
|
|
381
|
+
|
|
382
|
+
#### `server.ts`
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
import { Hono } from "hono"
|
|
386
|
+
import { upgradeWebSocket, websocket } from "hono/bun"
|
|
387
|
+
import { createHonoWebSocketHandler } from "kkrpc"
|
|
388
|
+
import { apiMethods, type API } from "./api"
|
|
389
|
+
|
|
390
|
+
const app = new Hono()
|
|
391
|
+
|
|
392
|
+
app.get(
|
|
393
|
+
"/ws",
|
|
394
|
+
upgradeWebSocket(() => {
|
|
395
|
+
return createHonoWebSocketHandler<API>({
|
|
396
|
+
expose: apiMethods
|
|
397
|
+
})
|
|
398
|
+
})
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
const server = Bun.serve({
|
|
402
|
+
port: 3000,
|
|
403
|
+
fetch: app.fetch,
|
|
404
|
+
websocket
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
console.log(`Server running on port ${server.port}`)
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### `client.ts`
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
import { RPCChannel, WebSocketClientIO } from "kkrpc"
|
|
414
|
+
import { apiMethods, type API } from "./api"
|
|
415
|
+
|
|
416
|
+
const clientIO = new WebSocketClientIO({
|
|
417
|
+
url: "ws://localhost:3000/ws"
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
const clientRPC = new RPCChannel<API, API>(clientIO, {
|
|
421
|
+
expose: apiMethods
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
const api = clientRPC.getAPI()
|
|
425
|
+
|
|
426
|
+
// Test basic RPC calls
|
|
427
|
+
console.log(await api.add(5, 3)) // 8
|
|
428
|
+
console.log(await api.echo("Hello from Hono!")) // "Hello from Hono!"
|
|
429
|
+
|
|
430
|
+
// Test nested API calls
|
|
431
|
+
console.log(await api.math.grade2.multiply(4, 6)) // 24
|
|
432
|
+
|
|
433
|
+
// Test property access
|
|
434
|
+
console.log(await api.counter) // 42
|
|
435
|
+
console.log(await api.nested.value) // "hello world"
|
|
436
|
+
|
|
437
|
+
clientIO.destroy()
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**Hono WebSocket Features:**
|
|
441
|
+
|
|
442
|
+
- **High Performance**: Built on Hono's ultra-fast WebSocket implementation
|
|
443
|
+
- **Cross-runtime**: Works across Bun, Deno, Node.js, and Cloudflare Workers
|
|
444
|
+
- **Type-safe**: Full TypeScript support with Hono integration
|
|
445
|
+
- **Bidirectional**: Both client and server can expose APIs
|
|
446
|
+
- **Framework Integration**: Seamless integration with Hono's middleware ecosystem
|
|
447
|
+
|
|
448
|
+
**Learn more:** [Hono WebSocket Documentation](https://hono.dev/docs/helpers/websocket)
|
|
449
|
+
|
|
450
|
+
### Elysia WebSocket Example
|
|
451
|
+
|
|
452
|
+
Elysia WebSocket adapter provides seamless integration with the modern TypeScript-first Elysia framework and its uWebSocket-powered WebSocket support.
|
|
453
|
+
|
|
454
|
+
#### `server.ts`
|
|
455
|
+
|
|
456
|
+
```ts
|
|
457
|
+
import { Elysia } from "elysia"
|
|
458
|
+
import { ElysiaWebSocketServerIO, RPCChannel } from "kkrpc"
|
|
459
|
+
import { apiMethods, type API } from "./api"
|
|
460
|
+
|
|
461
|
+
// Extend API for Elysia-specific features
|
|
462
|
+
interface ElysiaAPI extends API {
|
|
463
|
+
getConnectionInfo(): Promise<{
|
|
464
|
+
remoteAddress: string | undefined
|
|
465
|
+
query: Record<string, string>
|
|
466
|
+
headers: Record<string, string>
|
|
467
|
+
}>
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const app = new Elysia()
|
|
471
|
+
.ws("/rpc", {
|
|
472
|
+
open(ws) {
|
|
473
|
+
const io = new ElysiaWebSocketServerIO(ws)
|
|
474
|
+
const elysiaApiMethods: ElysiaAPI = {
|
|
475
|
+
...apiMethods,
|
|
476
|
+
getConnectionInfo: async () => ({
|
|
477
|
+
remoteAddress: io.getRemoteAddress(),
|
|
478
|
+
query: io.getQuery(),
|
|
479
|
+
headers: io.getHeaders()
|
|
480
|
+
})
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const rpc = new RPCChannel<ElysiaAPI, ElysiaAPI>(io, {
|
|
484
|
+
expose: elysiaApiMethods
|
|
485
|
+
})
|
|
486
|
+
},
|
|
487
|
+
message(ws, message) {
|
|
488
|
+
ElysiaWebSocketServerIO.feedMessage(ws, message)
|
|
489
|
+
}
|
|
490
|
+
})
|
|
491
|
+
.listen(3000)
|
|
492
|
+
|
|
493
|
+
console.log("Elysia server running on port 3000")
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
#### `client.ts`
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
import { ElysiaWebSocketClientIO, RPCChannel } from "kkrpc"
|
|
500
|
+
import { apiMethods, type API } from "./api"
|
|
501
|
+
|
|
502
|
+
const clientIO = new ElysiaWebSocketClientIO("ws://localhost:3000/rpc")
|
|
503
|
+
const clientRPC = new RPCChannel<API, any>(clientIO, {
|
|
504
|
+
expose: apiMethods
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
const api = clientRPC.getAPI()
|
|
508
|
+
|
|
509
|
+
// Test basic RPC calls
|
|
510
|
+
console.log(await api.add(5, 3)) // 8
|
|
511
|
+
console.log(await api.echo("Hello from Elysia!")) // "Hello from Elysia!"
|
|
512
|
+
|
|
513
|
+
// Test nested API calls
|
|
514
|
+
console.log(await api.math.grade1.add(10, 20)) // 30
|
|
515
|
+
console.log(await api.math.grade3.divide(20, 4)) // 5
|
|
516
|
+
|
|
517
|
+
// Test Elysia-specific features
|
|
518
|
+
const connInfo = await api.getConnectionInfo()
|
|
519
|
+
console.log("Connected from:", connInfo.remoteAddress)
|
|
520
|
+
console.log("Query params:", connInfo.query)
|
|
521
|
+
console.log("Headers:", connInfo.headers)
|
|
522
|
+
|
|
523
|
+
clientIO.destroy()
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**Elysia WebSocket Features:**
|
|
527
|
+
|
|
528
|
+
- **Modern Framework**: Built on Elysia's TypeScript-first design
|
|
529
|
+
- **Ultra-fast**: Powered by uWebSocket for maximum performance
|
|
530
|
+
- **Rich Metadata**: Access to connection info, query params, and headers
|
|
531
|
+
- **Type-safe**: Full TypeScript inference and autocompletion
|
|
532
|
+
- **Runtime Flexible**: Works across Bun, Node.js, and Deno
|
|
533
|
+
- **Developer Experience**: Clean API with factory functions
|
|
534
|
+
|
|
535
|
+
**Learn more:** [Elysia WebSocket Documentation](https://elysiajs.com/patterns/websocket)
|
|
536
|
+
|
|
370
537
|
**Key Benefits:**
|
|
538
|
+
|
|
371
539
|
- **Zero-copy performance**: No serialization/deserialization overhead
|
|
372
540
|
- **Memory efficient**: Ownership transfers without copying
|
|
373
541
|
- **Automatic fallback**: Graceful degradation for non-transferable transports
|
|
374
542
|
- **Type-safe**: Full TypeScript support
|
|
375
543
|
|
|
376
544
|
**Supported Transferable Types:**
|
|
545
|
+
|
|
377
546
|
- `ArrayBuffer` - Binary data buffers
|
|
378
547
|
- `MessagePort` - Communication channels
|
|
379
548
|
- `ImageBitmap` - Decoded image data
|
|
@@ -459,55 +628,334 @@ For Chrome extensions, use the dedicated `ChromePortIO` adapter for reliable, po
|
|
|
459
628
|
#### `background.ts`
|
|
460
629
|
|
|
461
630
|
```ts
|
|
462
|
-
import { ChromePortIO, RPCChannel } from "kkrpc/chrome-extension"
|
|
463
|
-
import type { BackgroundAPI, ContentAPI } from "./types"
|
|
631
|
+
import { ChromePortIO, RPCChannel } from "kkrpc/chrome-extension"
|
|
632
|
+
import type { BackgroundAPI, ContentAPI } from "./types"
|
|
464
633
|
|
|
465
634
|
const backgroundAPI: BackgroundAPI = {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
635
|
+
async getExtensionVersion() {
|
|
636
|
+
return chrome.runtime.getManifest().version
|
|
637
|
+
}
|
|
638
|
+
}
|
|
470
639
|
|
|
471
640
|
chrome.runtime.onConnect.addListener((port) => {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
})
|
|
641
|
+
if (port.name === "content-to-background") {
|
|
642
|
+
const io = new ChromePortIO(port)
|
|
643
|
+
const rpc = new RPCChannel(io, { expose: backgroundAPI })
|
|
644
|
+
// Handle disconnect
|
|
645
|
+
port.onDisconnect.addListener(() => io.destroy())
|
|
646
|
+
}
|
|
647
|
+
})
|
|
479
648
|
```
|
|
480
649
|
|
|
481
650
|
#### `content.ts`
|
|
482
651
|
|
|
483
652
|
```ts
|
|
484
|
-
import { ChromePortIO, RPCChannel } from "kkrpc/chrome-extension"
|
|
485
|
-
import type { BackgroundAPI, ContentAPI } from "./types"
|
|
653
|
+
import { ChromePortIO, RPCChannel } from "kkrpc/chrome-extension"
|
|
654
|
+
import type { BackgroundAPI, ContentAPI } from "./types"
|
|
486
655
|
|
|
487
656
|
const contentAPI: ContentAPI = {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
657
|
+
async getPageTitle() {
|
|
658
|
+
return document.title
|
|
659
|
+
}
|
|
660
|
+
}
|
|
492
661
|
|
|
493
|
-
const port = chrome.runtime.connect({ name: "content-to-background" })
|
|
494
|
-
const io = new ChromePortIO(port)
|
|
495
|
-
const rpc = new RPCChannel<ContentAPI, BackgroundAPI>(io, { expose: contentAPI })
|
|
662
|
+
const port = chrome.runtime.connect({ name: "content-to-background" })
|
|
663
|
+
const io = new ChromePortIO(port)
|
|
664
|
+
const rpc = new RPCChannel<ContentAPI, BackgroundAPI>(io, { expose: contentAPI })
|
|
496
665
|
|
|
497
|
-
const backgroundAPI = rpc.getAPI()
|
|
666
|
+
const backgroundAPI = rpc.getAPI()
|
|
498
667
|
|
|
499
668
|
// Example call
|
|
500
|
-
backgroundAPI.getExtensionVersion().then(version => {
|
|
501
|
-
|
|
502
|
-
})
|
|
669
|
+
backgroundAPI.getExtensionVersion().then((version) => {
|
|
670
|
+
console.log("Extension version:", version)
|
|
671
|
+
})
|
|
503
672
|
```
|
|
504
673
|
|
|
505
674
|
**Chrome Extension Features:**
|
|
675
|
+
|
|
506
676
|
- **Port-based**: Uses `chrome.runtime.Port` for stable, long-lived connections.
|
|
507
677
|
- **Bidirectional**: Both sides can expose and call APIs.
|
|
508
678
|
- **Type-safe**: Full TypeScript support for your APIs.
|
|
509
679
|
- **Reliable**: Handles connection lifecycle and cleanup.
|
|
510
680
|
|
|
681
|
+
### RabbitMQ Example
|
|
682
|
+
|
|
683
|
+
RabbitMQ adapter provides reliable message queue communication with support for topic exchanges and durable messaging.
|
|
684
|
+
|
|
685
|
+
#### `producer.ts`
|
|
686
|
+
|
|
687
|
+
```ts
|
|
688
|
+
import { RabbitMQIO, RPCChannel } from "kkrpc"
|
|
689
|
+
import { apiMethods, type API } from "./api"
|
|
690
|
+
|
|
691
|
+
const rabbitmqIO = new RabbitMQIO({
|
|
692
|
+
url: "amqp://localhost",
|
|
693
|
+
exchange: "kkrpc-exchange",
|
|
694
|
+
exchangeType: "topic",
|
|
695
|
+
durable: true
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
const producerRPC = new RPCChannel<API, API>(rabbitmqIO, {
|
|
699
|
+
expose: apiMethods
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
const api = producerRPC.getAPI()
|
|
703
|
+
|
|
704
|
+
// Test basic RPC calls
|
|
705
|
+
console.log(await api.add(5, 3)) // 8
|
|
706
|
+
console.log(await api.echo("Hello from RabbitMQ!")) // "Hello from RabbitMQ!"
|
|
707
|
+
|
|
708
|
+
rabbitmqIO.destroy()
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
#### `consumer.ts`
|
|
712
|
+
|
|
713
|
+
```ts
|
|
714
|
+
import { RabbitMQIO, RPCChannel } from "kkrpc"
|
|
715
|
+
import { apiMethods, type API } from "./api"
|
|
716
|
+
|
|
717
|
+
const rabbitmqIO = new RabbitMQIO({
|
|
718
|
+
url: "amqp://localhost",
|
|
719
|
+
exchange: "kkrpc-exchange",
|
|
720
|
+
exchangeType: "topic",
|
|
721
|
+
durable: true,
|
|
722
|
+
sessionId: "consumer-session"
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
const consumerRPC = new RPCChannel<API, API>(rabbitmqIO, {
|
|
726
|
+
expose: apiMethods
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
const api = consumerRPC.getAPI()
|
|
730
|
+
|
|
731
|
+
// Process messages from producer
|
|
732
|
+
console.log(await api.add(10, 20)) // 30
|
|
733
|
+
console.log(await api.echo("Hello from consumer!")) // "Hello from consumer!"
|
|
734
|
+
|
|
735
|
+
rabbitmqIO.destroy()
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
**RabbitMQ Features:**
|
|
739
|
+
|
|
740
|
+
- **Topic Exchange**: Flexible routing with wildcard patterns
|
|
741
|
+
- **Durable Messaging**: Messages survive broker restarts
|
|
742
|
+
- **Load Balancing**: Multiple consumers can share workload
|
|
743
|
+
- **Reliable Delivery**: Acknowledgments and redelivery support
|
|
744
|
+
- **Session Management**: Unique sessions prevent message conflicts
|
|
745
|
+
|
|
746
|
+
### Redis Streams Example
|
|
747
|
+
|
|
748
|
+
Redis Streams adapter provides high-performance stream-based messaging with persistence and consumer group support.
|
|
749
|
+
|
|
750
|
+
#### `publisher.ts`
|
|
751
|
+
|
|
752
|
+
```ts
|
|
753
|
+
import { RedisStreamsIO, RPCChannel } from "kkrpc"
|
|
754
|
+
import { apiMethods, type API } from "./api"
|
|
755
|
+
|
|
756
|
+
const redisIO = new RedisStreamsIO({
|
|
757
|
+
url: "redis://localhost:6379",
|
|
758
|
+
stream: "kkrpc-stream",
|
|
759
|
+
maxLen: 10000, // Keep only last 10k messages
|
|
760
|
+
maxQueueSize: 1000
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
const publisherRPC = new RPCChannel<API, API>(redisIO, {
|
|
764
|
+
expose: apiMethods
|
|
765
|
+
})
|
|
766
|
+
|
|
767
|
+
const api = publisherRPC.getAPI()
|
|
768
|
+
|
|
769
|
+
// Test basic RPC calls
|
|
770
|
+
console.log(await api.add(7, 8)) // 15
|
|
771
|
+
console.log(await api.echo("Hello from Redis Streams!")) // "Hello from Redis Streams!"
|
|
772
|
+
|
|
773
|
+
// Get stream information
|
|
774
|
+
const streamInfo = await redisIO.getStreamInfo()
|
|
775
|
+
console.log("Stream length:", streamInfo.length)
|
|
776
|
+
|
|
777
|
+
redisIO.destroy()
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
#### `subscriber.ts`
|
|
781
|
+
|
|
782
|
+
```ts
|
|
783
|
+
import { RedisStreamsIO, RPCChannel } from "kkrpc"
|
|
784
|
+
import { apiMethods, type API } from "./api"
|
|
785
|
+
|
|
786
|
+
// Using consumer group for load balancing
|
|
787
|
+
const redisIO = new RedisStreamsIO({
|
|
788
|
+
url: "redis://localhost:6379",
|
|
789
|
+
stream: "kkrpc-stream",
|
|
790
|
+
consumerGroup: "kkrpc-group",
|
|
791
|
+
consumerName: "worker-1",
|
|
792
|
+
useConsumerGroup: true, // Enable load balancing
|
|
793
|
+
maxQueueSize: 1000
|
|
794
|
+
})
|
|
795
|
+
|
|
796
|
+
const subscriberRPC = new RPCChannel<API, API>(redisIO, {
|
|
797
|
+
expose: apiMethods
|
|
798
|
+
})
|
|
799
|
+
|
|
800
|
+
const api = subscriberRPC.getAPI()
|
|
801
|
+
|
|
802
|
+
// Process messages with load balancing
|
|
803
|
+
console.log(await api.multiply(4, 6)) // 24
|
|
804
|
+
console.log(await api.echo("Hello from subscriber!")) // "Hello from subscriber!"
|
|
805
|
+
|
|
806
|
+
redisIO.destroy()
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
**Redis Streams Features:**
|
|
810
|
+
|
|
811
|
+
- **Two Modes**: Pub/Sub (all consumers) or Consumer Groups (load balancing)
|
|
812
|
+
- **Persistence**: Messages stored in Redis with configurable retention
|
|
813
|
+
- **Memory Protection**: Queue size limits prevent memory issues
|
|
814
|
+
- **Consumer Groups**: Load balancing with message acknowledgment
|
|
815
|
+
- **Stream Management**: Built-in tools for monitoring and trimming streams
|
|
816
|
+
|
|
817
|
+
### Kafka Example
|
|
818
|
+
|
|
819
|
+
Kafka adapter provides distributed streaming with high throughput and fault tolerance for large-scale systems.
|
|
820
|
+
|
|
821
|
+
#### `producer.ts`
|
|
822
|
+
|
|
823
|
+
```ts
|
|
824
|
+
import { KafkaIO, RPCChannel } from "kkrpc"
|
|
825
|
+
import { apiMethods, type API } from "./api"
|
|
826
|
+
|
|
827
|
+
const kafkaIO = new KafkaIO({
|
|
828
|
+
brokers: ["localhost:9092"],
|
|
829
|
+
topic: "kkrpc-topic",
|
|
830
|
+
clientId: "kkrpc-producer",
|
|
831
|
+
numPartitions: 3,
|
|
832
|
+
replicationFactor: 1,
|
|
833
|
+
maxQueueSize: 1000
|
|
834
|
+
})
|
|
835
|
+
|
|
836
|
+
const producerRPC = new RPCChannel<API, API>(kafkaIO, {
|
|
837
|
+
expose: apiMethods
|
|
838
|
+
})
|
|
839
|
+
|
|
840
|
+
const api = producerRPC.getAPI()
|
|
841
|
+
|
|
842
|
+
// Test basic RPC calls
|
|
843
|
+
console.log(await api.add(12, 18)) // 30
|
|
844
|
+
console.log(await api.echo("Hello from Kafka!")) // "Hello from Kafka!"
|
|
845
|
+
|
|
846
|
+
console.log("Topic:", kafkaIO.getTopic())
|
|
847
|
+
console.log("Session ID:", kafkaIO.getSessionId())
|
|
848
|
+
|
|
849
|
+
kafkaIO.destroy()
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
#### `consumer.ts`
|
|
853
|
+
|
|
854
|
+
```ts
|
|
855
|
+
import { KafkaIO, RPCChannel } from "kkrpc"
|
|
856
|
+
import { apiMethods, type API } from "./api"
|
|
857
|
+
|
|
858
|
+
const kafkaIO = new KafkaIO({
|
|
859
|
+
brokers: ["localhost:9092"],
|
|
860
|
+
topic: "kkrpc-topic",
|
|
861
|
+
clientId: "kkrpc-consumer",
|
|
862
|
+
groupId: "kkrpc-consumer-group",
|
|
863
|
+
fromBeginning: false, // Only read new messages
|
|
864
|
+
maxQueueSize: 1000
|
|
865
|
+
})
|
|
866
|
+
|
|
867
|
+
const consumerRPC = new RPCChannel<API, API>(kafkaIO, {
|
|
868
|
+
expose: apiMethods
|
|
869
|
+
})
|
|
870
|
+
|
|
871
|
+
const api = consumerRPC.getAPI()
|
|
872
|
+
|
|
873
|
+
// Process messages from Kafka
|
|
874
|
+
console.log(await api.divide(100, 4)) // 25
|
|
875
|
+
console.log(await api.echo("Hello from Kafka consumer!")) // "Hello from Kafka consumer!"
|
|
876
|
+
|
|
877
|
+
console.log("Topic:", kafkaIO.getTopic())
|
|
878
|
+
console.log("Group ID:", kafkaIO.getGroupId())
|
|
879
|
+
|
|
880
|
+
kafkaIO.destroy()
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
**Kafka Features:**
|
|
884
|
+
|
|
885
|
+
- **Distributed**: Built-in replication and partitioning
|
|
886
|
+
- **High Throughput**: Optimized for high-volume message streaming
|
|
887
|
+
- **Fault Tolerant**: Replication and automatic failover
|
|
888
|
+
- **Scalable**: Horizontal scaling with partitions
|
|
889
|
+
- **Persistent**: Durable message storage with configurable retention
|
|
890
|
+
- **Consumer Groups**: Load balancing across consumer instances
|
|
891
|
+
|
|
892
|
+
### NATS Example
|
|
893
|
+
|
|
894
|
+
NATS adapter provides high-performance messaging with publish/subscribe patterns and optional queue groups for load balancing.
|
|
895
|
+
|
|
896
|
+
#### `publisher.ts`
|
|
897
|
+
|
|
898
|
+
```ts
|
|
899
|
+
import { NatsIO, RPCChannel } from "kkrpc"
|
|
900
|
+
import { apiMethods, type API } from "./api"
|
|
901
|
+
|
|
902
|
+
const natsIO = new NatsIO({
|
|
903
|
+
servers: "nats://localhost:4222",
|
|
904
|
+
subject: "kkrpc-messages",
|
|
905
|
+
queueGroup: "kkrpc-group" // Optional: enables load balancing
|
|
906
|
+
})
|
|
907
|
+
|
|
908
|
+
const publisherRPC = new RPCChannel<API, API>(natsIO, {
|
|
909
|
+
expose: apiMethods
|
|
910
|
+
})
|
|
911
|
+
|
|
912
|
+
const api = publisherRPC.getAPI()
|
|
913
|
+
|
|
914
|
+
// Test basic RPC calls
|
|
915
|
+
console.log(await api.add(5, 3)) // 8
|
|
916
|
+
console.log(await api.echo("Hello from NATS!")) // "Hello from NATS!"
|
|
917
|
+
|
|
918
|
+
console.log("Subject:", natsIO.getSubject())
|
|
919
|
+
console.log("Session ID:", natsIO.getSessionId())
|
|
920
|
+
|
|
921
|
+
natsIO.destroy()
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
#### `subscriber.ts`
|
|
925
|
+
|
|
926
|
+
```ts
|
|
927
|
+
import { NatsIO, RPCChannel } from "kkrpc"
|
|
928
|
+
import { apiMethods, type API } from "./api"
|
|
929
|
+
|
|
930
|
+
const natsIO = new NatsIO({
|
|
931
|
+
servers: "nats://localhost:4222",
|
|
932
|
+
subject: "kkrpc-messages",
|
|
933
|
+
queueGroup: "kkrpc-group", // Optional: enables load balancing
|
|
934
|
+
sessionId: "subscriber-session"
|
|
935
|
+
})
|
|
936
|
+
|
|
937
|
+
const subscriberRPC = new RPCChannel<API, API>(natsIO, {
|
|
938
|
+
expose: apiMethods
|
|
939
|
+
})
|
|
940
|
+
|
|
941
|
+
const api = subscriberRPC.getAPI()
|
|
942
|
+
|
|
943
|
+
// Process messages from publisher
|
|
944
|
+
console.log(await api.add(10, 20)) // 30
|
|
945
|
+
console.log(await api.echo("Hello from subscriber!")) // "Hello from subscriber!"
|
|
946
|
+
|
|
947
|
+
natsIO.destroy()
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
**NATS Features:**
|
|
951
|
+
|
|
952
|
+
- **High Performance**: Ultra-low latency messaging system
|
|
953
|
+
- **Subject-Based**: Flexible subject hierarchy for routing
|
|
954
|
+
- **Queue Groups**: Optional load balancing across subscribers
|
|
955
|
+
- **Simple Model**: Pub/Sub with request/reply support
|
|
956
|
+
- **Cross-Platform**: Works across Node.js, Deno, and Bun
|
|
957
|
+
- **No Schema Required**: Dynamic message routing without upfront configuration
|
|
958
|
+
|
|
511
959
|
### Tauri Example
|
|
512
960
|
|
|
513
961
|
Call functions in bun/node/deno processes from Tauri app with JS/TS.
|
|
@@ -578,21 +1026,245 @@ I provided a sample tauri app in `examples/tauri-demo`.
|
|
|
578
1026
|
|
|
579
1027
|

|
|
580
1028
|
|
|
1029
|
+
### Electron Example
|
|
1030
|
+
|
|
1031
|
+
Electron adapter provides type-safe bidirectional RPC communication between Renderer process, Main process, and Utility Process.
|
|
1032
|
+
|
|
1033
|
+
There are two sets of adapters for Electron:
|
|
1034
|
+
|
|
1035
|
+
1. **Renderer ↔ Main IPC**: `ElectronIpcMainIO` (Main side) + `ElectronIpcRendererIO` (Renderer side)
|
|
1036
|
+
2. **Main ↔ Utility Process**: `ElectronUtilityProcessIO` (Main side) + `ElectronUtilityProcessChildIO` (Utility Process side)
|
|
1037
|
+
|
|
1038
|
+
#### Preload Script Setup
|
|
1039
|
+
|
|
1040
|
+
Use `createSecureIpcBridge` to create a secured `ipcRenderer` with channel whitelisting:
|
|
1041
|
+
|
|
1042
|
+
```ts title="preload.ts"
|
|
1043
|
+
import { contextBridge, ipcRenderer } from "electron"
|
|
1044
|
+
import { createSecureIpcBridge } from "kkrpc/electron-ipc"
|
|
1045
|
+
|
|
1046
|
+
const securedIpcRenderer = createSecureIpcBridge({
|
|
1047
|
+
ipcRenderer,
|
|
1048
|
+
channelPrefix: "kkrpc-"
|
|
1049
|
+
})
|
|
1050
|
+
|
|
1051
|
+
contextBridge.exposeInMainWorld("electron", {
|
|
1052
|
+
ipcRenderer: securedIpcRenderer
|
|
1053
|
+
})
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
This automatically whitelists only channels starting with `"kkrpc-"`. You can also whitelist specific channels:
|
|
1057
|
+
|
|
1058
|
+
```ts title="preload.ts"
|
|
1059
|
+
import { contextBridge, ipcRenderer } from "electron"
|
|
1060
|
+
import { createSecureIpcBridge } from "kkrpc/electron-ipc"
|
|
1061
|
+
|
|
1062
|
+
const securedIpcRenderer = createSecureIpcBridge({
|
|
1063
|
+
ipcRenderer,
|
|
1064
|
+
allowedChannels: ["kkrpc-ipc", "kkrpc-worker-relay"]
|
|
1065
|
+
})
|
|
1066
|
+
|
|
1067
|
+
contextBridge.exposeInMainWorld("electron", {
|
|
1068
|
+
ipcRenderer: securedIpcRenderer
|
|
1069
|
+
})
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
This approach avoids direct Electron dependencies in kkrpc, making it compatible with any Electron version.
|
|
1073
|
+
|
|
1074
|
+
#### Main Process
|
|
1075
|
+
|
|
1076
|
+
```ts title="main.ts"
|
|
1077
|
+
import { app, BrowserWindow, ipcMain, utilityProcess } from "electron"
|
|
1078
|
+
import { ElectronUtilityProcessIO, RPCChannel } from "kkrpc/electron"
|
|
1079
|
+
import { ElectronIpcMainIO } from "kkrpc/electron-ipc"
|
|
1080
|
+
|
|
1081
|
+
interface MainAPI {
|
|
1082
|
+
showNotification(message: string): Promise<void>
|
|
1083
|
+
getAppVersion(): Promise<string>
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
interface WorkerAPI {
|
|
1087
|
+
add(a: number, b: number): Promise<number>
|
|
1088
|
+
multiply(a: number, b: number): Promise<number>
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
const mainAPI: MainAPI = {
|
|
1092
|
+
showNotification: async (message: string) => {
|
|
1093
|
+
console.log(`[Main] Notification: ${message}`)
|
|
1094
|
+
},
|
|
1095
|
+
getAppVersion: async () => app.getVersion()
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// 1. Setup Renderer ↔ Main IPC
|
|
1099
|
+
const win = new BrowserWindow({
|
|
1100
|
+
webPreferences: {
|
|
1101
|
+
preload: path.join(__dirname, "preload.js"),
|
|
1102
|
+
contextIsolation: true,
|
|
1103
|
+
nodeIntegration: false
|
|
1104
|
+
}
|
|
1105
|
+
})
|
|
1106
|
+
|
|
1107
|
+
const ipcIO = new ElectronIpcMainIO(ipcMain, win.webContents)
|
|
1108
|
+
const ipcRPC = new RPCChannel<MainAPI, object>(ipcIO, { expose: mainAPI })
|
|
1109
|
+
|
|
1110
|
+
// 2. Setup Main ↔ Utility Process
|
|
1111
|
+
const workerPath = path.join(__dirname, "./worker.js")
|
|
1112
|
+
const workerProcess = utilityProcess.fork(workerPath)
|
|
1113
|
+
const workerIO = new ElectronUtilityProcessIO(workerProcess)
|
|
1114
|
+
const workerRPC = new RPCChannel<MainAPI, WorkerAPI>(workerIO, { expose: mainAPI })
|
|
1115
|
+
const workerAPI = workerRPC.getAPI()
|
|
1116
|
+
|
|
1117
|
+
// Now you can call worker methods from main
|
|
1118
|
+
const result = await workerAPI.add(2, 3) // 5
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
#### Renderer Process
|
|
1122
|
+
|
|
1123
|
+
```ts title="renderer.ts"
|
|
1124
|
+
import { ElectronIpcRendererIO, RPCChannel } from "kkrpc/electron-ipc"
|
|
1125
|
+
|
|
1126
|
+
interface MainAPI {
|
|
1127
|
+
showNotification(message: string): Promise<void>
|
|
1128
|
+
getAppVersion(): Promise<string>
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
const ipcIO = new ElectronIpcRendererIO()
|
|
1132
|
+
const ipcRPC = new RPCChannel<object, MainAPI>(ipcIO, { expose: {} })
|
|
1133
|
+
const mainAPI = ipcRPC.getAPI()
|
|
1134
|
+
|
|
1135
|
+
// Call main process methods from renderer
|
|
1136
|
+
await mainAPI.showNotification("Hello from renderer!")
|
|
1137
|
+
const version = await mainAPI.getAppVersion()
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
#### Utility Process (Worker)
|
|
1141
|
+
|
|
1142
|
+
```ts title="worker.ts"
|
|
1143
|
+
import { ElectronUtilityProcessChildIO, RPCChannel } from "kkrpc/electron"
|
|
1144
|
+
|
|
1145
|
+
interface MainAPI {
|
|
1146
|
+
showNotification(message: string): Promise<void>
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const io = new ElectronUtilityProcessChildIO()
|
|
1150
|
+
|
|
1151
|
+
const workerMethods = {
|
|
1152
|
+
add: async (a: number, b: number) => a + b,
|
|
1153
|
+
multiply: async (a: number, b: number) => a * b
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
const rpc = new RPCChannel<typeof workerMethods, MainAPI>(io, {
|
|
1157
|
+
expose: workerMethods
|
|
1158
|
+
})
|
|
1159
|
+
|
|
1160
|
+
const mainAPI = rpc.getAPI()
|
|
1161
|
+
|
|
1162
|
+
// Call back to main process
|
|
1163
|
+
await mainAPI.showNotification("Hello from worker!")
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
**Electron Features:**
|
|
1167
|
+
|
|
1168
|
+
- **Type-safe IPC**: Full TypeScript support across Renderer ↔ Main ↔ Utility Process
|
|
1169
|
+
- **Bidirectional**: All processes can expose and call APIs
|
|
1170
|
+
- **Secure**: Works with `contextIsolation: true` (recommended)
|
|
1171
|
+
- **Multiple Patterns**: Supports both IPC and Utility Process communication
|
|
1172
|
+
- **Nested API Support**: Full support for nested method calls like `api.math.add()`
|
|
1173
|
+
|
|
1174
|
+
**Learn more:** [Electron Documentation](https://www.electronjs.org/docs/latest/)
|
|
1175
|
+
|
|
1176
|
+
### Relay Example
|
|
1177
|
+
|
|
1178
|
+
The `createRelay` function creates a transparent bidirectional relay between two IoInterfaces. This is useful when you want to connect two different transport layers without the intermediary process knowing the API details.
|
|
1179
|
+
|
|
1180
|
+
A common use case is connecting a Renderer process to an external Node.js process through Electron's Main process:
|
|
1181
|
+
|
|
1182
|
+
```
|
|
1183
|
+
Renderer (IPC) → Main (relay) → External Node Process (stdio)
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
With relay, Main acts as a transparent byte pipe - it forwards messages without parsing them.
|
|
1187
|
+
|
|
1188
|
+
#### Main Process (with relay)
|
|
1189
|
+
|
|
1190
|
+
```ts title="main.ts"
|
|
1191
|
+
import { spawn } from "child_process"
|
|
1192
|
+
import { createRelay, NodeIo } from "kkrpc"
|
|
1193
|
+
import { ElectronIpcMainIO } from "kkrpc/electron-ipc"
|
|
1194
|
+
|
|
1195
|
+
// Spawn external Node.js process
|
|
1196
|
+
const worker = spawn("node", ["./worker.js"])
|
|
1197
|
+
|
|
1198
|
+
// Create relay: IPC channel "worker-relay" <-> stdio
|
|
1199
|
+
const relay = createRelay(
|
|
1200
|
+
new ElectronIpcMainIO(ipcMain, webContents, "worker-relay"),
|
|
1201
|
+
new NodeIo(worker.stdout, worker.stdin)
|
|
1202
|
+
)
|
|
1203
|
+
|
|
1204
|
+
// Cleanup when done
|
|
1205
|
+
app.on("window-all-closed", () => {
|
|
1206
|
+
relay.destroy()
|
|
1207
|
+
worker.kill()
|
|
1208
|
+
})
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
#### Renderer Process
|
|
1212
|
+
|
|
1213
|
+
```ts title="renderer.ts"
|
|
1214
|
+
import { ElectronIpcRendererIO, RPCChannel } from "kkrpc/electron-ipc"
|
|
1215
|
+
|
|
1216
|
+
// Connect via the relay channel (not the default "kkrpc-ipc" channel)
|
|
1217
|
+
const io = new ElectronIpcRendererIO("worker-relay")
|
|
1218
|
+
const rpc = new RPCChannel<{}, WorkerAPI>(io)
|
|
1219
|
+
const workerAPI = rpc.getAPI()
|
|
1220
|
+
|
|
1221
|
+
// Calls go directly to the external worker process
|
|
1222
|
+
const result = await workerAPI.calculate(42)
|
|
1223
|
+
```
|
|
1224
|
+
|
|
1225
|
+
#### External Worker Process
|
|
1226
|
+
|
|
1227
|
+
```ts title="worker.ts"
|
|
1228
|
+
import { NodeIo, RPCChannel } from "kkrpc"
|
|
1229
|
+
|
|
1230
|
+
const io = new NodeIo(process.stdin, process.stdout)
|
|
1231
|
+
const rpc = new RPCChannel<WorkerAPI, {}>(io, {
|
|
1232
|
+
expose: {
|
|
1233
|
+
calculate: async (n: number) => n * 2
|
|
1234
|
+
}
|
|
1235
|
+
})
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1238
|
+
**Relay Scenarios:**
|
|
1239
|
+
|
|
1240
|
+
| Scenario | From | Through | To | Use Case |
|
|
1241
|
+
| ------------------------------- | ----------------------- | ---------------------- | -------- | -------------------------------------- |
|
|
1242
|
+
| **Renderer → External Process** | `ElectronIpcRendererIO` | Main (`createRelay`) | `NodeIo` | Call external Node.js/Bun/Deno scripts |
|
|
1243
|
+
| **Browser → Server Process** | `WebSocketClientIO` | Server (`createRelay`) | `NodeIo` | Browser to shell process via WebSocket |
|
|
1244
|
+
| **Worker → External Process** | `WorkerChildIO` | Main (`createRelay`) | `NodeIo` | Web Worker to external script |
|
|
1245
|
+
|
|
1246
|
+
**Benefits:**
|
|
1247
|
+
|
|
1248
|
+
- **Transparent**: Intermediary doesn't need to know the API
|
|
1249
|
+
- **Clean separation**: Main doesn't expose worker methods
|
|
1250
|
+
- **Multiple channels**: Can create multiple relays on different IPC channels
|
|
1251
|
+
- **Composable**: Can chain relays through multiple processes
|
|
1252
|
+
|
|
581
1253
|
## 🆚 Comparison with Alternatives
|
|
582
1254
|
|
|
583
1255
|
<div align="center">
|
|
584
1256
|
|
|
585
|
-
| Feature
|
|
586
|
-
|
|
587
|
-
| **Cross-runtime**
|
|
588
|
-
| **Bidirectional**
|
|
589
|
-
| **Type Safety**
|
|
590
|
-
| **Transport Layers**
|
|
591
|
-
| **Error Preservation**
|
|
592
|
-
| **Property Access**
|
|
593
|
-
| **Zero Config**
|
|
594
|
-
| **Callbacks**
|
|
595
|
-
| **Transferable Objects** | ✅ Zero-copy transfers (40-100x faster)
|
|
1257
|
+
| Feature | kkrpc | tRPC | Comlink |
|
|
1258
|
+
| ------------------------ | ------------------------------------------------------------------ | ------------------------------ | ------------------------------ |
|
|
1259
|
+
| **Cross-runtime** | ✅ Node.js, Deno, Bun, Browser | ❌ Node.js/Browser only | ❌ Browser only |
|
|
1260
|
+
| **Bidirectional** | ✅ Both sides can call APIs | ❌ Client calls server only | ✅ Both sides can call APIs |
|
|
1261
|
+
| **Type Safety** | ✅ Full TypeScript support | ✅ Full TypeScript support | ✅ TypeScript support |
|
|
1262
|
+
| **Transport Layers** | ✅ stdio, HTTP, WebSocket, postMessage, Chrome Extension, Electron | ❌ HTTP only | ❌ postMessage only |
|
|
1263
|
+
| **Error Preservation** | ✅ Complete error objects | ⚠️ Limited error serialization | ⚠️ Limited error serialization |
|
|
1264
|
+
| **Property Access** | ✅ Remote getters/setters | ❌ Methods only | ❌ Methods only |
|
|
1265
|
+
| **Zero Config** | ✅ No code generation | ✅ No code generation | ✅ No code generation |
|
|
1266
|
+
| **Callbacks** | ✅ Function parameters | ❌ No callbacks | ✅ Function parameters |
|
|
1267
|
+
| **Transferable Objects** | ✅ Zero-copy transfers (40-100x faster) | ❌ Not supported | ✅ Basic support |
|
|
596
1268
|
|
|
597
1269
|
</div>
|
|
598
1270
|
|
|
@@ -631,13 +1303,13 @@ I provided a sample tauri app in `examples/tauri-demo`.
|
|
|
631
1303
|
|
|
632
1304
|
<div align="center">
|
|
633
1305
|
|
|
634
|
-
| Platform
|
|
635
|
-
|
|
636
|
-
| **NPM**
|
|
637
|
-
| **JSR**
|
|
638
|
-
| **GitHub**
|
|
639
|
-
| **Docs**
|
|
640
|
-
| **Examples** | Code Samples
|
|
1306
|
+
| Platform | Package | Link |
|
|
1307
|
+
| ------------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1308
|
+
| **NPM** | `kkrpc` | [](https://www.npmjs.com/package/kkrpc) |
|
|
1309
|
+
| **JSR** | `@kunkun/kkrpc` | [](https://jsr.io/@kunkun/kkrpc) |
|
|
1310
|
+
| **GitHub** | Repository | [](https://github.com/kunkunsh/kkrpc) |
|
|
1311
|
+
| **Docs** | Typedoc | [](https://kunkunsh.github.io/kkrpc/) |
|
|
1312
|
+
| **Examples** | Code Samples | [](https://github.com/kunkunsh/kkrpc/tree/main/examples) |
|
|
641
1313
|
|
|
642
1314
|
</div>
|
|
643
1315
|
|