kkrpc 0.6.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 +682 -166
- package/dist/browser-mod.cjs +1 -1
- package/dist/browser-mod.d.cts +14 -6
- 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 -1
- 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-BYtPphRu.js → channel-LGw9tl8f.js} +3 -3
- package/dist/channel-LGw9tl8f.js.map +1 -0
- package/dist/{channel-D3yeLUXA.cjs → channel-OGNDLfsd.cjs} +2 -2
- package/dist/chrome-extension.cjs +1 -1
- 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 -1
- package/dist/chrome-extension.js.map +1 -1
- package/dist/deno-B319Kxci.js +2 -0
- package/dist/deno-B319Kxci.js.map +1 -0
- package/dist/{deno-noilmrf7.d.cts → 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-BzNLlX8S.d.ts → deno-DS3TcO_u.d.ts} +6 -2
- package/dist/deno-DS3TcO_u.d.ts.map +1 -0
- package/dist/deno-mod.cjs +1 -1
- package/dist/deno-mod.d.cts +3 -3
- package/dist/deno-mod.d.ts +4 -4
- package/dist/deno-mod.js +1 -1
- 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 -1
- package/dist/http.d.cts +3 -3
- package/dist/http.d.ts +3 -3
- package/dist/http.js +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 +2 -2
- package/dist/mod.d.cts +82 -15
- package/dist/mod.d.cts.map +1 -1
- package/dist/mod.d.ts +84 -17
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +2 -2
- 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 -1
- 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 -1
- 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-BAorUr9n.d.ts → 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-DwFX963h.d.cts → tauri-d0yDsUnV.d.cts} +16 -4
- package/dist/tauri-d0yDsUnV.d.cts.map +1 -0
- package/dist/{transfer-handlers-jwQTY0rF.d.ts → transfer-handlers-3wBZpi5F.d.ts} +1 -1
- package/dist/{transfer-handlers-jwQTY0rF.d.ts.map → transfer-handlers-3wBZpi5F.d.ts.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 +75 -5
- package/dist/channel-BYtPphRu.js.map +0 -1
- package/dist/channel-C92Q3vK-.d.ts.map +0 -1
- package/dist/channel-QTVxXCE5.d.cts.map +0 -1
- package/dist/deno-BzNLlX8S.d.ts.map +0 -1
- package/dist/deno-DAHG1vvV.js +0 -2
- package/dist/deno-DAHG1vvV.js.map +0 -1
- package/dist/deno-DjRflDaL.cjs +0 -1
- package/dist/deno-noilmrf7.d.cts.map +0 -1
- package/dist/http-5KkjLA6X.js +0 -2
- package/dist/http-5KkjLA6X.js.map +0 -1
- package/dist/http-Cvnz7K2R.d.cts.map +0 -1
- package/dist/http-DpCLmXI6.cjs +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 -2
- package/dist/tauri-C7MVgjRt.js.map +0 -1
- package/dist/tauri-DJegDPWN.cjs +0 -1
- package/dist/tauri-DwFX963h.d.cts.map +0 -1
- 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,42 +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
|
-
| **Hono WebSocket**
|
|
104
|
-
| **Socket.IO**
|
|
105
|
-
| **Elysia WebSocket** | Modern TypeScript framework WebSocket integration
|
|
106
|
-
| **Chrome Extension** | Extension component communication
|
|
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 |
|
|
107
115
|
|
|
108
116
|
The core of **kkrpc** design is in `RPCChannel` and `IoInterface`.
|
|
109
117
|
|
|
@@ -163,7 +171,7 @@ For backward compatibility, the receiving side will automatically detect the ser
|
|
|
163
171
|
|
|
164
172
|
### Installation
|
|
165
173
|
|
|
166
|
-
|
|
174
|
+
|
|
167
175
|
|
|
168
176
|
```bash
|
|
169
177
|
# npm
|
|
@@ -179,41 +187,35 @@ pnpm add kkrpc
|
|
|
179
187
|
import { RPCChannel } from "jsr:@kunkun/kkrpc"
|
|
180
188
|
```
|
|
181
189
|
|
|
182
|
-
</div>
|
|
183
|
-
|
|
184
190
|
### Basic Example
|
|
185
191
|
|
|
186
|
-
<div align="center">
|
|
187
|
-
|
|
188
192
|
```typescript
|
|
189
193
|
// server.ts
|
|
190
|
-
import {
|
|
194
|
+
import { NodeIo, RPCChannel } from "kkrpc"
|
|
191
195
|
|
|
192
196
|
const api = {
|
|
193
|
-
|
|
194
|
-
|
|
197
|
+
greet: (name: string) => `Hello, ${name}!`,
|
|
198
|
+
add: (a: number, b: number) => a + b
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
const rpc = new RPCChannel(new NodeIo(process.stdin, process.stdout), {
|
|
198
|
-
|
|
202
|
+
expose: api
|
|
199
203
|
})
|
|
200
204
|
```
|
|
201
205
|
|
|
202
206
|
```typescript
|
|
203
207
|
// client.ts
|
|
204
|
-
import { RPCChannel, NodeIo } from "kkrpc"
|
|
205
208
|
import { spawn } from "child_process"
|
|
209
|
+
import { NodeIo, RPCChannel } from "kkrpc"
|
|
206
210
|
|
|
207
211
|
const worker = spawn("deno", ["run", "server.ts"])
|
|
208
212
|
const rpc = new RPCChannel(new NodeIo(worker.stdout, worker.stdin))
|
|
209
213
|
const api = rpc.getAPI<typeof api>()
|
|
210
214
|
|
|
211
215
|
console.log(await api.greet("World")) // "Hello, World!"
|
|
212
|
-
console.log(await api.add(5, 3))
|
|
216
|
+
console.log(await api.add(5, 3)) // 8
|
|
213
217
|
```
|
|
214
218
|
|
|
215
|
-
</div>
|
|
216
|
-
|
|
217
219
|
## 📚 Examples
|
|
218
220
|
|
|
219
221
|
Below are simple examples to get you started quickly.
|
|
@@ -280,9 +282,13 @@ kkrpc preserves complete error information across RPC boundaries:
|
|
|
280
282
|
```ts
|
|
281
283
|
// Custom error class
|
|
282
284
|
class DatabaseError extends Error {
|
|
283
|
-
constructor(
|
|
285
|
+
constructor(
|
|
286
|
+
message: string,
|
|
287
|
+
public code: number,
|
|
288
|
+
public query: string
|
|
289
|
+
) {
|
|
284
290
|
super(message)
|
|
285
|
-
this.name =
|
|
291
|
+
this.name = "DatabaseError"
|
|
286
292
|
}
|
|
287
293
|
}
|
|
288
294
|
|
|
@@ -304,11 +310,11 @@ try {
|
|
|
304
310
|
await api.getUserById("")
|
|
305
311
|
} catch (error) {
|
|
306
312
|
// All error properties are preserved:
|
|
307
|
-
console.log(error.name)
|
|
308
|
-
console.log(error.message)
|
|
309
|
-
console.log(error.code)
|
|
310
|
-
console.log(error.query)
|
|
311
|
-
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
|
|
312
318
|
console.log(error.timestamp) // ISO timestamp
|
|
313
319
|
console.log(error.requestId) // Request ID
|
|
314
320
|
}
|
|
@@ -343,14 +349,14 @@ expect(sum).toBe(3)
|
|
|
343
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.
|
|
344
350
|
|
|
345
351
|
```ts
|
|
346
|
-
import { RPCChannel,
|
|
352
|
+
import { RPCChannel, transfer, WorkerParentIO } from "kkrpc/browser"
|
|
347
353
|
|
|
348
354
|
const worker = new Worker("worker.js")
|
|
349
355
|
const io = new WorkerParentIO(worker)
|
|
350
356
|
const rpc = new RPCChannel(io)
|
|
351
357
|
const api = rpc.getAPI<{
|
|
352
|
-
|
|
353
|
-
|
|
358
|
+
processBuffer(buffer: ArrayBuffer): Promise<number>
|
|
359
|
+
generateData(size: number): Promise<ArrayBuffer>
|
|
354
360
|
}>()
|
|
355
361
|
|
|
356
362
|
// Create a large buffer (10MB)
|
|
@@ -376,23 +382,26 @@ Hono WebSocket adapter provides seamless integration with the Hono framework's h
|
|
|
376
382
|
#### `server.ts`
|
|
377
383
|
|
|
378
384
|
```ts
|
|
379
|
-
import { Hono } from
|
|
380
|
-
import { upgradeWebSocket, websocket } from
|
|
381
|
-
import { createHonoWebSocketHandler } from
|
|
382
|
-
import { apiMethods, type API } from
|
|
385
|
+
import { Hono } from "hono"
|
|
386
|
+
import { upgradeWebSocket, websocket } from "hono/bun"
|
|
387
|
+
import { createHonoWebSocketHandler } from "kkrpc"
|
|
388
|
+
import { apiMethods, type API } from "./api"
|
|
383
389
|
|
|
384
390
|
const app = new Hono()
|
|
385
391
|
|
|
386
|
-
app.get(
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
392
|
+
app.get(
|
|
393
|
+
"/ws",
|
|
394
|
+
upgradeWebSocket(() => {
|
|
395
|
+
return createHonoWebSocketHandler<API>({
|
|
396
|
+
expose: apiMethods
|
|
397
|
+
})
|
|
398
|
+
})
|
|
399
|
+
)
|
|
391
400
|
|
|
392
401
|
const server = Bun.serve({
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
402
|
+
port: 3000,
|
|
403
|
+
fetch: app.fetch,
|
|
404
|
+
websocket
|
|
396
405
|
})
|
|
397
406
|
|
|
398
407
|
console.log(`Server running on port ${server.port}`)
|
|
@@ -401,15 +410,15 @@ console.log(`Server running on port ${server.port}`)
|
|
|
401
410
|
#### `client.ts`
|
|
402
411
|
|
|
403
412
|
```ts
|
|
404
|
-
import {
|
|
405
|
-
import { apiMethods, type API } from
|
|
413
|
+
import { RPCChannel, WebSocketClientIO } from "kkrpc"
|
|
414
|
+
import { apiMethods, type API } from "./api"
|
|
406
415
|
|
|
407
416
|
const clientIO = new WebSocketClientIO({
|
|
408
|
-
|
|
417
|
+
url: "ws://localhost:3000/ws"
|
|
409
418
|
})
|
|
410
419
|
|
|
411
420
|
const clientRPC = new RPCChannel<API, API>(clientIO, {
|
|
412
|
-
|
|
421
|
+
expose: apiMethods
|
|
413
422
|
})
|
|
414
423
|
|
|
415
424
|
const api = clientRPC.getAPI()
|
|
@@ -429,6 +438,7 @@ clientIO.destroy()
|
|
|
429
438
|
```
|
|
430
439
|
|
|
431
440
|
**Hono WebSocket Features:**
|
|
441
|
+
|
|
432
442
|
- **High Performance**: Built on Hono's ultra-fast WebSocket implementation
|
|
433
443
|
- **Cross-runtime**: Works across Bun, Deno, Node.js, and Cloudflare Workers
|
|
434
444
|
- **Type-safe**: Full TypeScript support with Hono integration
|
|
@@ -444,54 +454,54 @@ Elysia WebSocket adapter provides seamless integration with the modern TypeScrip
|
|
|
444
454
|
#### `server.ts`
|
|
445
455
|
|
|
446
456
|
```ts
|
|
447
|
-
import { Elysia } from
|
|
448
|
-
import { ElysiaWebSocketServerIO, RPCChannel } from
|
|
449
|
-
import { apiMethods, type API } from
|
|
457
|
+
import { Elysia } from "elysia"
|
|
458
|
+
import { ElysiaWebSocketServerIO, RPCChannel } from "kkrpc"
|
|
459
|
+
import { apiMethods, type API } from "./api"
|
|
450
460
|
|
|
451
461
|
// Extend API for Elysia-specific features
|
|
452
462
|
interface ElysiaAPI extends API {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
463
|
+
getConnectionInfo(): Promise<{
|
|
464
|
+
remoteAddress: string | undefined
|
|
465
|
+
query: Record<string, string>
|
|
466
|
+
headers: Record<string, string>
|
|
467
|
+
}>
|
|
458
468
|
}
|
|
459
469
|
|
|
460
470
|
const app = new Elysia()
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
console.log(
|
|
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")
|
|
484
494
|
```
|
|
485
495
|
|
|
486
496
|
#### `client.ts`
|
|
487
497
|
|
|
488
498
|
```ts
|
|
489
|
-
import { ElysiaWebSocketClientIO, RPCChannel } from
|
|
490
|
-
import { apiMethods, type API } from
|
|
499
|
+
import { ElysiaWebSocketClientIO, RPCChannel } from "kkrpc"
|
|
500
|
+
import { apiMethods, type API } from "./api"
|
|
491
501
|
|
|
492
|
-
const clientIO = new ElysiaWebSocketClientIO(
|
|
502
|
+
const clientIO = new ElysiaWebSocketClientIO("ws://localhost:3000/rpc")
|
|
493
503
|
const clientRPC = new RPCChannel<API, any>(clientIO, {
|
|
494
|
-
|
|
504
|
+
expose: apiMethods
|
|
495
505
|
})
|
|
496
506
|
|
|
497
507
|
const api = clientRPC.getAPI()
|
|
@@ -506,14 +516,15 @@ console.log(await api.math.grade3.divide(20, 4)) // 5
|
|
|
506
516
|
|
|
507
517
|
// Test Elysia-specific features
|
|
508
518
|
const connInfo = await api.getConnectionInfo()
|
|
509
|
-
console.log(
|
|
510
|
-
console.log(
|
|
511
|
-
console.log(
|
|
519
|
+
console.log("Connected from:", connInfo.remoteAddress)
|
|
520
|
+
console.log("Query params:", connInfo.query)
|
|
521
|
+
console.log("Headers:", connInfo.headers)
|
|
512
522
|
|
|
513
523
|
clientIO.destroy()
|
|
514
524
|
```
|
|
515
525
|
|
|
516
526
|
**Elysia WebSocket Features:**
|
|
527
|
+
|
|
517
528
|
- **Modern Framework**: Built on Elysia's TypeScript-first design
|
|
518
529
|
- **Ultra-fast**: Powered by uWebSocket for maximum performance
|
|
519
530
|
- **Rich Metadata**: Access to connection info, query params, and headers
|
|
@@ -524,12 +535,14 @@ clientIO.destroy()
|
|
|
524
535
|
**Learn more:** [Elysia WebSocket Documentation](https://elysiajs.com/patterns/websocket)
|
|
525
536
|
|
|
526
537
|
**Key Benefits:**
|
|
538
|
+
|
|
527
539
|
- **Zero-copy performance**: No serialization/deserialization overhead
|
|
528
540
|
- **Memory efficient**: Ownership transfers without copying
|
|
529
541
|
- **Automatic fallback**: Graceful degradation for non-transferable transports
|
|
530
542
|
- **Type-safe**: Full TypeScript support
|
|
531
543
|
|
|
532
544
|
**Supported Transferable Types:**
|
|
545
|
+
|
|
533
546
|
- `ArrayBuffer` - Binary data buffers
|
|
534
547
|
- `MessagePort` - Communication channels
|
|
535
548
|
- `ImageBitmap` - Decoded image data
|
|
@@ -615,55 +628,334 @@ For Chrome extensions, use the dedicated `ChromePortIO` adapter for reliable, po
|
|
|
615
628
|
#### `background.ts`
|
|
616
629
|
|
|
617
630
|
```ts
|
|
618
|
-
import { ChromePortIO, RPCChannel } from "kkrpc/chrome-extension"
|
|
619
|
-
import type { BackgroundAPI, ContentAPI } from "./types"
|
|
631
|
+
import { ChromePortIO, RPCChannel } from "kkrpc/chrome-extension"
|
|
632
|
+
import type { BackgroundAPI, ContentAPI } from "./types"
|
|
620
633
|
|
|
621
634
|
const backgroundAPI: BackgroundAPI = {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
}
|
|
635
|
+
async getExtensionVersion() {
|
|
636
|
+
return chrome.runtime.getManifest().version
|
|
637
|
+
}
|
|
638
|
+
}
|
|
626
639
|
|
|
627
640
|
chrome.runtime.onConnect.addListener((port) => {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
})
|
|
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
|
+
})
|
|
635
648
|
```
|
|
636
649
|
|
|
637
650
|
#### `content.ts`
|
|
638
651
|
|
|
639
652
|
```ts
|
|
640
|
-
import { ChromePortIO, RPCChannel } from "kkrpc/chrome-extension"
|
|
641
|
-
import type { BackgroundAPI, ContentAPI } from "./types"
|
|
653
|
+
import { ChromePortIO, RPCChannel } from "kkrpc/chrome-extension"
|
|
654
|
+
import type { BackgroundAPI, ContentAPI } from "./types"
|
|
642
655
|
|
|
643
656
|
const contentAPI: ContentAPI = {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
657
|
+
async getPageTitle() {
|
|
658
|
+
return document.title
|
|
659
|
+
}
|
|
660
|
+
}
|
|
648
661
|
|
|
649
|
-
const port = chrome.runtime.connect({ name: "content-to-background" })
|
|
650
|
-
const io = new ChromePortIO(port)
|
|
651
|
-
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 })
|
|
652
665
|
|
|
653
|
-
const backgroundAPI = rpc.getAPI()
|
|
666
|
+
const backgroundAPI = rpc.getAPI()
|
|
654
667
|
|
|
655
668
|
// Example call
|
|
656
|
-
backgroundAPI.getExtensionVersion().then(version => {
|
|
657
|
-
|
|
658
|
-
})
|
|
669
|
+
backgroundAPI.getExtensionVersion().then((version) => {
|
|
670
|
+
console.log("Extension version:", version)
|
|
671
|
+
})
|
|
659
672
|
```
|
|
660
673
|
|
|
661
674
|
**Chrome Extension Features:**
|
|
675
|
+
|
|
662
676
|
- **Port-based**: Uses `chrome.runtime.Port` for stable, long-lived connections.
|
|
663
677
|
- **Bidirectional**: Both sides can expose and call APIs.
|
|
664
678
|
- **Type-safe**: Full TypeScript support for your APIs.
|
|
665
679
|
- **Reliable**: Handles connection lifecycle and cleanup.
|
|
666
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
|
+
|
|
667
959
|
### Tauri Example
|
|
668
960
|
|
|
669
961
|
Call functions in bun/node/deno processes from Tauri app with JS/TS.
|
|
@@ -734,21 +1026,245 @@ I provided a sample tauri app in `examples/tauri-demo`.
|
|
|
734
1026
|
|
|
735
1027
|

|
|
736
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
|
+
|
|
737
1253
|
## 🆚 Comparison with Alternatives
|
|
738
1254
|
|
|
739
1255
|
<div align="center">
|
|
740
1256
|
|
|
741
|
-
| Feature
|
|
742
|
-
|
|
743
|
-
| **Cross-runtime**
|
|
744
|
-
| **Bidirectional**
|
|
745
|
-
| **Type Safety**
|
|
746
|
-
| **Transport Layers**
|
|
747
|
-
| **Error Preservation**
|
|
748
|
-
| **Property Access**
|
|
749
|
-
| **Zero Config**
|
|
750
|
-
| **Callbacks**
|
|
751
|
-
| **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 |
|
|
752
1268
|
|
|
753
1269
|
</div>
|
|
754
1270
|
|
|
@@ -787,13 +1303,13 @@ I provided a sample tauri app in `examples/tauri-demo`.
|
|
|
787
1303
|
|
|
788
1304
|
<div align="center">
|
|
789
1305
|
|
|
790
|
-
| Platform
|
|
791
|
-
|
|
792
|
-
| **NPM**
|
|
793
|
-
| **JSR**
|
|
794
|
-
| **GitHub**
|
|
795
|
-
| **Docs**
|
|
796
|
-
| **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) |
|
|
797
1313
|
|
|
798
1314
|
</div>
|
|
799
1315
|
|