@procwire/transport 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +844 -0
- package/dist/channel/builder.d.ts +68 -0
- package/dist/channel/builder.d.ts.map +1 -0
- package/dist/channel/builder.js +120 -0
- package/dist/channel/builder.js.map +1 -0
- package/dist/channel/index.d.ts +6 -0
- package/dist/channel/index.d.ts.map +1 -0
- package/dist/channel/index.js +6 -0
- package/dist/channel/index.js.map +1 -0
- package/dist/channel/quickstart.d.ts +94 -0
- package/dist/channel/quickstart.d.ts.map +1 -0
- package/dist/channel/quickstart.js +104 -0
- package/dist/channel/quickstart.js.map +1 -0
- package/dist/channel/request-channel.d.ts +119 -0
- package/dist/channel/request-channel.d.ts.map +1 -0
- package/dist/channel/request-channel.js +476 -0
- package/dist/channel/request-channel.js.map +1 -0
- package/dist/channel/types.d.ts +226 -0
- package/dist/channel/types.d.ts.map +1 -0
- package/dist/channel/types.js +2 -0
- package/dist/channel/types.js.map +1 -0
- package/dist/framing/index.d.ts +4 -0
- package/dist/framing/index.d.ts.map +1 -0
- package/dist/framing/index.js +4 -0
- package/dist/framing/index.js.map +1 -0
- package/dist/framing/length-prefixed.d.ts +55 -0
- package/dist/framing/length-prefixed.d.ts.map +1 -0
- package/dist/framing/length-prefixed.js +102 -0
- package/dist/framing/length-prefixed.js.map +1 -0
- package/dist/framing/line-delimited.d.ts +61 -0
- package/dist/framing/line-delimited.d.ts.map +1 -0
- package/dist/framing/line-delimited.js +94 -0
- package/dist/framing/line-delimited.js.map +1 -0
- package/dist/framing/types.d.ts +35 -0
- package/dist/framing/types.d.ts.map +1 -0
- package/dist/framing/types.js +2 -0
- package/dist/framing/types.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/process/handle.d.ts +64 -0
- package/dist/process/handle.d.ts.map +1 -0
- package/dist/process/handle.js +107 -0
- package/dist/process/handle.js.map +1 -0
- package/dist/process/index.d.ts +37 -0
- package/dist/process/index.d.ts.map +1 -0
- package/dist/process/index.js +37 -0
- package/dist/process/index.js.map +1 -0
- package/dist/process/manager.d.ts +58 -0
- package/dist/process/manager.d.ts.map +1 -0
- package/dist/process/manager.js +360 -0
- package/dist/process/manager.js.map +1 -0
- package/dist/process/types.d.ts +322 -0
- package/dist/process/types.d.ts.map +1 -0
- package/dist/process/types.js +2 -0
- package/dist/process/types.js.map +1 -0
- package/dist/protocol/index.d.ts +4 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +6 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/jsonrpc.d.ts +146 -0
- package/dist/protocol/jsonrpc.d.ts.map +1 -0
- package/dist/protocol/jsonrpc.js +288 -0
- package/dist/protocol/jsonrpc.js.map +1 -0
- package/dist/protocol/simple.d.ts +139 -0
- package/dist/protocol/simple.d.ts.map +1 -0
- package/dist/protocol/simple.js +297 -0
- package/dist/protocol/simple.js.map +1 -0
- package/dist/protocol/types.d.ts +117 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/types.js +2 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/serialization/index.d.ts +5 -0
- package/dist/serialization/index.d.ts.map +1 -0
- package/dist/serialization/index.js +5 -0
- package/dist/serialization/index.js.map +1 -0
- package/dist/serialization/json.d.ts +66 -0
- package/dist/serialization/json.d.ts.map +1 -0
- package/dist/serialization/json.js +66 -0
- package/dist/serialization/json.js.map +1 -0
- package/dist/serialization/raw.d.ts +38 -0
- package/dist/serialization/raw.d.ts.map +1 -0
- package/dist/serialization/raw.js +41 -0
- package/dist/serialization/raw.js.map +1 -0
- package/dist/serialization/registry.d.ts +91 -0
- package/dist/serialization/registry.d.ts.map +1 -0
- package/dist/serialization/registry.js +119 -0
- package/dist/serialization/registry.js.map +1 -0
- package/dist/serialization/types.d.ts +27 -0
- package/dist/serialization/types.d.ts.map +1 -0
- package/dist/serialization/types.js +2 -0
- package/dist/serialization/types.js.map +1 -0
- package/dist/transport/factory.d.ts +139 -0
- package/dist/transport/factory.d.ts.map +1 -0
- package/dist/transport/factory.js +162 -0
- package/dist/transport/factory.js.map +1 -0
- package/dist/transport/index.d.ts +6 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +9 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/socket-server.d.ts +48 -0
- package/dist/transport/socket-server.d.ts.map +1 -0
- package/dist/transport/socket-server.js +215 -0
- package/dist/transport/socket-server.js.map +1 -0
- package/dist/transport/socket-transport.d.ts +67 -0
- package/dist/transport/socket-transport.d.ts.map +1 -0
- package/dist/transport/socket-transport.js +193 -0
- package/dist/transport/socket-transport.js.map +1 -0
- package/dist/transport/stdio-transport.d.ts +94 -0
- package/dist/transport/stdio-transport.d.ts.map +1 -0
- package/dist/transport/stdio-transport.js +234 -0
- package/dist/transport/stdio-transport.js.map +1 -0
- package/dist/transport/types.d.ts +131 -0
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/transport/types.js +2 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/utils/assert.d.ts +16 -0
- package/dist/utils/assert.d.ts.map +1 -0
- package/dist/utils/assert.js +31 -0
- package/dist/utils/assert.js.map +1 -0
- package/dist/utils/disposables.d.ts +38 -0
- package/dist/utils/disposables.d.ts.map +1 -0
- package/dist/utils/disposables.js +59 -0
- package/dist/utils/disposables.js.map +1 -0
- package/dist/utils/errors.d.ts +43 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +69 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/events.d.ts +58 -0
- package/dist/utils/events.d.ts.map +1 -0
- package/dist/utils/events.js +95 -0
- package/dist/utils/events.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/pipe-path.d.ts +48 -0
- package/dist/utils/pipe-path.d.ts.map +1 -0
- package/dist/utils/pipe-path.js +89 -0
- package/dist/utils/pipe-path.js.map +1 -0
- package/dist/utils/platform.d.ts +16 -0
- package/dist/utils/platform.d.ts.map +1 -0
- package/dist/utils/platform.js +22 -0
- package/dist/utils/platform.js.map +1 -0
- package/dist/utils/time.d.ts +38 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +55 -0
- package/dist/utils/time.js.map +1 -0
- package/package.json +85 -0
package/README.md
ADDED
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
# @procwire/transport
|
|
2
|
+
|
|
3
|
+
Standalone, modular IPC (Inter-Process Communication) transport library for Node.js with **zero runtime dependencies**.
|
|
4
|
+
|
|
5
|
+
Build production-grade IPC channels with full control over every layer: transport, framing, serialization, and protocol.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Zero dependencies** - Core package has no runtime dependencies
|
|
10
|
+
- **Modular architecture** - Replace any layer independently
|
|
11
|
+
- **Type-safe** - Full TypeScript support with generics
|
|
12
|
+
- **Cross-platform** - Windows (Named Pipes), macOS/Linux (Unix Sockets)
|
|
13
|
+
- **Multiple transports** - stdio, named pipes, unix sockets
|
|
14
|
+
- **Flexible framing** - Line-delimited, length-prefixed, custom
|
|
15
|
+
- **Pluggable serialization** - JSON, MessagePack, Protobuf, Arrow, custom
|
|
16
|
+
- **Protocol agnostic** - JSON-RPC 2.0, custom protocols
|
|
17
|
+
- **ProcessManager** - Managed child processes with restart policies
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @procwire/transport
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or with optional codecs:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# With MessagePack support
|
|
29
|
+
npm install @procwire/transport @procwire/codec-msgpack @msgpack/msgpack
|
|
30
|
+
|
|
31
|
+
# With Protocol Buffers support
|
|
32
|
+
npm install @procwire/transport @procwire/codec-protobuf protobufjs
|
|
33
|
+
|
|
34
|
+
# With Apache Arrow support
|
|
35
|
+
npm install @procwire/transport @procwire/codec-arrow apache-arrow
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
### Basic stdio IPC
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { createStdioChannel } from "@procwire/transport";
|
|
44
|
+
|
|
45
|
+
// Parent process
|
|
46
|
+
const channel = await createStdioChannel("node", {
|
|
47
|
+
args: ["worker.js"],
|
|
48
|
+
timeout: 5000,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Send request
|
|
52
|
+
const result = await channel.request("calculate", { expr: "2+2" });
|
|
53
|
+
console.log(result); // 4
|
|
54
|
+
|
|
55
|
+
// Listen for notifications
|
|
56
|
+
channel.onNotification("log", (params) => {
|
|
57
|
+
console.log(params.message);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await channel.close();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### ProcessManager with dual channels
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { ProcessManager } from "@procwire/transport";
|
|
67
|
+
import { MessagePackCodec } from "@procwire/codec-msgpack";
|
|
68
|
+
|
|
69
|
+
const manager = new ProcessManager({
|
|
70
|
+
namespace: "my-app",
|
|
71
|
+
restartPolicy: { enabled: true, maxRestarts: 3 },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const handle = await manager.spawn("worker-1", {
|
|
75
|
+
executablePath: "node",
|
|
76
|
+
args: ["worker.js"],
|
|
77
|
+
|
|
78
|
+
// Control channel (stdio)
|
|
79
|
+
controlChannel: {},
|
|
80
|
+
|
|
81
|
+
// Data channel (pipe/socket with MessagePack)
|
|
82
|
+
dataChannel: {
|
|
83
|
+
enabled: true,
|
|
84
|
+
channel: {
|
|
85
|
+
framing: "length-prefixed",
|
|
86
|
+
serialization: new MessagePackCodec(),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Lightweight commands via control channel
|
|
92
|
+
const status = await handle.request("getStatus");
|
|
93
|
+
|
|
94
|
+
// Bulk data via data channel
|
|
95
|
+
const result = await handle.requestViaData("processItems", { items: [...] });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Custom channel with builder
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import {
|
|
102
|
+
ChannelBuilder,
|
|
103
|
+
TransportFactory,
|
|
104
|
+
LineDelimitedFraming,
|
|
105
|
+
JsonCodec,
|
|
106
|
+
JsonRpcProtocol,
|
|
107
|
+
} from "@procwire/transport";
|
|
108
|
+
|
|
109
|
+
const transport = TransportFactory.createStdio({
|
|
110
|
+
executablePath: "node",
|
|
111
|
+
args: ["worker.js"],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const channel = new ChannelBuilder()
|
|
115
|
+
.withTransport(transport)
|
|
116
|
+
.withFraming(new LineDelimitedFraming())
|
|
117
|
+
.withSerialization(new JsonCodec())
|
|
118
|
+
.withProtocol(new JsonRpcProtocol())
|
|
119
|
+
.withTimeout(10000)
|
|
120
|
+
.build();
|
|
121
|
+
|
|
122
|
+
await channel.start();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Architecture
|
|
126
|
+
|
|
127
|
+
The library uses a layered architecture where each layer is independent and replaceable:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
Application Layer (Your Code)
|
|
131
|
+
↓
|
|
132
|
+
Process Management (ProcessManager, ProcessHandle)
|
|
133
|
+
↓
|
|
134
|
+
Channel Layer (RequestChannel, ChannelBuilder)
|
|
135
|
+
↓
|
|
136
|
+
Protocol Layer (JSON-RPC 2.0, custom)
|
|
137
|
+
↓
|
|
138
|
+
Serialization Layer (JSON, MessagePack, Protobuf, Arrow)
|
|
139
|
+
↓
|
|
140
|
+
Framing Layer (line-delimited, length-prefixed)
|
|
141
|
+
↓
|
|
142
|
+
Transport Layer (stdio, named pipes, unix sockets)
|
|
143
|
+
↓
|
|
144
|
+
OS Layer (child_process, net.Server/Socket)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Core Abstractions
|
|
148
|
+
|
|
149
|
+
1. **Transport**: Raw byte transfer between endpoints
|
|
150
|
+
2. **Framing**: Message boundary detection in byte streams
|
|
151
|
+
3. **Serialization**: Object ↔ binary conversion
|
|
152
|
+
4. **Protocol**: Application-level message protocol
|
|
153
|
+
5. **Channel**: High-level request/response communication
|
|
154
|
+
6. **Process**: Managed child process lifecycle
|
|
155
|
+
|
|
156
|
+
## API Reference
|
|
157
|
+
|
|
158
|
+
### Transports
|
|
159
|
+
|
|
160
|
+
#### StdioTransport
|
|
161
|
+
|
|
162
|
+
Spawns child process and communicates via stdin/stdout.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { StdioTransport } from "@procwire/transport";
|
|
166
|
+
|
|
167
|
+
const transport = new StdioTransport({
|
|
168
|
+
executablePath: "node",
|
|
169
|
+
args: ["worker.js"],
|
|
170
|
+
cwd: process.cwd(),
|
|
171
|
+
env: { ...process.env, CUSTOM_VAR: "value" },
|
|
172
|
+
startupTimeout: 10000,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
await transport.connect();
|
|
176
|
+
transport.write(Buffer.from("data"));
|
|
177
|
+
transport.onData((data) => console.log(data));
|
|
178
|
+
await transport.close();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### SocketTransport (Client)
|
|
182
|
+
|
|
183
|
+
Connects to named pipe (Windows) or unix socket (Unix).
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { SocketTransport } from "@procwire/transport";
|
|
187
|
+
import { isWindows } from "@procwire/transport/utils";
|
|
188
|
+
|
|
189
|
+
const path = isWindows() ? "\\\\.\\pipe\\my-pipe" : "/tmp/my-socket.sock";
|
|
190
|
+
|
|
191
|
+
const transport = new SocketTransport({
|
|
192
|
+
path,
|
|
193
|
+
connectionTimeout: 5000,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await transport.connect();
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### SocketServer
|
|
200
|
+
|
|
201
|
+
Creates named pipe/unix socket server.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { SocketServer } from "@procwire/transport";
|
|
205
|
+
|
|
206
|
+
const server = new SocketServer({
|
|
207
|
+
unlinkOnListen: true, // Remove stale socket
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
server.onConnection((transport) => {
|
|
211
|
+
console.log("Client connected");
|
|
212
|
+
transport.onData((data) => {
|
|
213
|
+
transport.write(data); // Echo
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
await server.listen("/tmp/my-socket.sock");
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### TransportFactory
|
|
221
|
+
|
|
222
|
+
Convenience factory for creating transports.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { TransportFactory } from "@procwire/transport";
|
|
226
|
+
|
|
227
|
+
// Stdio
|
|
228
|
+
const stdio = TransportFactory.createStdio({
|
|
229
|
+
executablePath: "node",
|
|
230
|
+
args: ["worker.js"],
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Pipe client
|
|
234
|
+
const client = TransportFactory.createPipeClient({
|
|
235
|
+
path: "/tmp/socket.sock",
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Pipe server
|
|
239
|
+
const server = TransportFactory.createPipeServer();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Framing
|
|
243
|
+
|
|
244
|
+
#### LineDelimitedFraming
|
|
245
|
+
|
|
246
|
+
Splits messages by newline characters.
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { LineDelimitedFraming } from "@procwire/transport/framing";
|
|
250
|
+
|
|
251
|
+
const framing = new LineDelimitedFraming({
|
|
252
|
+
maxLineLength: 100000, // Default: 100KB
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Best for**:
|
|
257
|
+
- Text protocols (JSON-RPC over stdio)
|
|
258
|
+
- Human-readable debugging
|
|
259
|
+
- Simple implementations
|
|
260
|
+
|
|
261
|
+
**Not suitable for**:
|
|
262
|
+
- Binary data with embedded newlines
|
|
263
|
+
- Very large messages (>1MB)
|
|
264
|
+
|
|
265
|
+
#### LengthPrefixedFraming
|
|
266
|
+
|
|
267
|
+
Prefixes each message with 4-byte length (big-endian).
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { LengthPrefixedFraming } from "@procwire/transport/framing";
|
|
271
|
+
|
|
272
|
+
const framing = new LengthPrefixedFraming({
|
|
273
|
+
maxMessageSize: 10 * 1024 * 1024, // Default: 10MB
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Best for**:
|
|
278
|
+
- Binary protocols (MessagePack, Protobuf)
|
|
279
|
+
- Large messages
|
|
280
|
+
- High throughput
|
|
281
|
+
|
|
282
|
+
**Format**: `[4-byte length][message]`
|
|
283
|
+
|
|
284
|
+
### Serialization
|
|
285
|
+
|
|
286
|
+
#### JsonCodec
|
|
287
|
+
|
|
288
|
+
JSON serialization using native `JSON.parse`/`JSON.stringify`.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import { JsonCodec } from "@procwire/transport/serialization";
|
|
292
|
+
|
|
293
|
+
const codec = new JsonCodec();
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Pros**: Built-in, human-readable, widely supported
|
|
297
|
+
**Cons**: Larger payloads, slower for large data
|
|
298
|
+
|
|
299
|
+
#### RawCodec
|
|
300
|
+
|
|
301
|
+
Pass-through codec for binary data.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { RawCodec } from "@procwire/transport/serialization";
|
|
305
|
+
|
|
306
|
+
const codec = new RawCodec();
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Use case**: When you handle serialization yourself.
|
|
310
|
+
|
|
311
|
+
#### CodecRegistry
|
|
312
|
+
|
|
313
|
+
Dynamically select codec based on content type.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { CodecRegistry } from "@procwire/transport/serialization";
|
|
317
|
+
import { JsonCodec } from "@procwire/transport/serialization";
|
|
318
|
+
import { MessagePackCodec } from "@procwire/codec-msgpack";
|
|
319
|
+
|
|
320
|
+
const registry = new CodecRegistry();
|
|
321
|
+
registry.register(new JsonCodec());
|
|
322
|
+
registry.register(new MessagePackCodec());
|
|
323
|
+
|
|
324
|
+
// Use with protocol that supports content negotiation
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
#### Custom Codecs
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import type { SerializationCodec } from "@procwire/transport/serialization";
|
|
331
|
+
|
|
332
|
+
class MyCodec implements SerializationCodec<MyType> {
|
|
333
|
+
readonly name = "my-codec";
|
|
334
|
+
readonly contentType = "application/x-my-codec";
|
|
335
|
+
|
|
336
|
+
serialize(value: MyType): Buffer {
|
|
337
|
+
// ... encoding logic
|
|
338
|
+
return buffer;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
deserialize(buffer: Buffer): MyType {
|
|
342
|
+
// ... decoding logic
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Protocols
|
|
349
|
+
|
|
350
|
+
#### JsonRpcProtocol
|
|
351
|
+
|
|
352
|
+
JSON-RPC 2.0 implementation with request/response and notifications.
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { JsonRpcProtocol } from "@procwire/transport/protocol";
|
|
356
|
+
|
|
357
|
+
const protocol = new JsonRpcProtocol();
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Request**:
|
|
361
|
+
```json
|
|
362
|
+
{"jsonrpc":"2.0","id":1,"method":"add","params":{"a":2,"b":3}}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Response**:
|
|
366
|
+
```json
|
|
367
|
+
{"jsonrpc":"2.0","id":1,"result":5}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Notification** (no response):
|
|
371
|
+
```json
|
|
372
|
+
{"jsonrpc":"2.0","method":"log","params":{"message":"Hello"}}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
#### SimpleProtocol
|
|
376
|
+
|
|
377
|
+
Minimal protocol with method and params.
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
import { SimpleProtocol } from "@procwire/transport/protocol";
|
|
381
|
+
|
|
382
|
+
const protocol = new SimpleProtocol();
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Message**:
|
|
386
|
+
```json
|
|
387
|
+
{"method":"add","params":{"a":2,"b":3}}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### Custom Protocols
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import type { Protocol } from "@procwire/transport/protocol";
|
|
394
|
+
|
|
395
|
+
class MyProtocol implements Protocol {
|
|
396
|
+
encodeRequest(method: string, params: unknown, id: number): unknown {
|
|
397
|
+
return { m: method, p: params, i: id };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
encodeResponse(result: unknown, id: number): unknown {
|
|
401
|
+
return { r: result, i: id };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ... implement other methods
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Channels
|
|
409
|
+
|
|
410
|
+
#### RequestChannel
|
|
411
|
+
|
|
412
|
+
High-level channel for request/response communication.
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
import type { Channel } from "@procwire/transport/channel";
|
|
416
|
+
|
|
417
|
+
// Send request
|
|
418
|
+
const result = await channel.request<number>("add", { a: 2, b: 3 });
|
|
419
|
+
|
|
420
|
+
// Send notification (no response)
|
|
421
|
+
channel.notify("log", { message: "Hello" });
|
|
422
|
+
|
|
423
|
+
// Handle incoming requests
|
|
424
|
+
channel.onRequest("multiply", async (params) => {
|
|
425
|
+
return params.a * params.b;
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Handle incoming notifications
|
|
429
|
+
channel.onNotification("shutdown", () => {
|
|
430
|
+
process.exit(0);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Lifecycle
|
|
434
|
+
await channel.start();
|
|
435
|
+
await channel.close();
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
#### ChannelBuilder
|
|
439
|
+
|
|
440
|
+
Fluent API for building channels.
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
import { ChannelBuilder } from "@procwire/transport";
|
|
444
|
+
|
|
445
|
+
const channel = new ChannelBuilder()
|
|
446
|
+
.withTransport(transport)
|
|
447
|
+
.withFraming(framing)
|
|
448
|
+
.withSerialization(serialization)
|
|
449
|
+
.withProtocol(protocol)
|
|
450
|
+
.withTimeout(5000)
|
|
451
|
+
.build();
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Process Management
|
|
455
|
+
|
|
456
|
+
#### ProcessManager
|
|
457
|
+
|
|
458
|
+
Manages multiple child processes with restart policies.
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
import { ProcessManager } from "@procwire/transport";
|
|
462
|
+
|
|
463
|
+
const manager = new ProcessManager({
|
|
464
|
+
defaultTimeout: 30000,
|
|
465
|
+
namespace: "my-app",
|
|
466
|
+
restartPolicy: {
|
|
467
|
+
enabled: true,
|
|
468
|
+
maxRestarts: 3,
|
|
469
|
+
backoffMs: 1000,
|
|
470
|
+
maxBackoffMs: 30000,
|
|
471
|
+
},
|
|
472
|
+
gracefulShutdownMs: 5000,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Spawn process
|
|
476
|
+
const handle = await manager.spawn("worker-1", {
|
|
477
|
+
executablePath: "node",
|
|
478
|
+
args: ["worker.js"],
|
|
479
|
+
cwd: process.cwd(),
|
|
480
|
+
env: { ...process.env },
|
|
481
|
+
|
|
482
|
+
controlChannel: {
|
|
483
|
+
// Optional channel config
|
|
484
|
+
},
|
|
485
|
+
|
|
486
|
+
dataChannel: {
|
|
487
|
+
enabled: true,
|
|
488
|
+
path: "/tmp/custom-path.sock", // Optional, auto-generated if omitted
|
|
489
|
+
channel: {
|
|
490
|
+
framing: "length-prefixed",
|
|
491
|
+
serialization: new MessagePackCodec(),
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
|
|
495
|
+
restartPolicy: {
|
|
496
|
+
enabled: true,
|
|
497
|
+
maxRestarts: 5,
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Events
|
|
502
|
+
manager.on("spawn", ({ id }) => console.log(`Spawned: ${id}`));
|
|
503
|
+
manager.on("ready", ({ id }) => console.log(`Ready: ${id}`));
|
|
504
|
+
manager.on("exit", ({ id, code }) => console.log(`Exited: ${id} (${code})`));
|
|
505
|
+
manager.on("restart", ({ id, attempt }) => console.log(`Restart: ${id} (${attempt})`));
|
|
506
|
+
manager.on("error", ({ id, error }) => console.log(`Error: ${id}`, error));
|
|
507
|
+
|
|
508
|
+
// Stop process
|
|
509
|
+
await manager.stop("worker-1");
|
|
510
|
+
|
|
511
|
+
// Stop all
|
|
512
|
+
await manager.stopAll();
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
#### ProcessHandle
|
|
516
|
+
|
|
517
|
+
Handle to managed process with dual-channel support.
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
// Control channel (default)
|
|
521
|
+
const status = await handle.request("getStatus");
|
|
522
|
+
|
|
523
|
+
// Data channel
|
|
524
|
+
const result = await handle.requestViaData("processItems", { items: [...] });
|
|
525
|
+
|
|
526
|
+
// Notifications
|
|
527
|
+
handle.notify("config", { key: "value" });
|
|
528
|
+
|
|
529
|
+
// Events
|
|
530
|
+
handle.on("exit", ({ code }) => console.log(`Exited: ${code}`));
|
|
531
|
+
|
|
532
|
+
// Stop
|
|
533
|
+
await handle.stop();
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
## Utilities
|
|
537
|
+
|
|
538
|
+
### PipePath
|
|
539
|
+
|
|
540
|
+
Generate platform-specific pipe/socket paths.
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
import { PipePath } from "@procwire/transport/utils";
|
|
544
|
+
|
|
545
|
+
// Auto-generated path
|
|
546
|
+
const path = PipePath.forModule("my-app", "worker-1");
|
|
547
|
+
// Windows: \\.\pipe\my-app-worker-1
|
|
548
|
+
// Unix: /tmp/my-app-worker-1.sock
|
|
549
|
+
|
|
550
|
+
// Validate path
|
|
551
|
+
const isValid = PipePath.isValid(path);
|
|
552
|
+
|
|
553
|
+
// Cleanup (Unix only)
|
|
554
|
+
await PipePath.cleanup(path);
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Platform Detection
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
import { isWindows, getProcessId } from "@procwire/transport/utils";
|
|
561
|
+
|
|
562
|
+
if (isWindows()) {
|
|
563
|
+
// Windows-specific logic
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const pid = getProcessId();
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## Platform Notes
|
|
570
|
+
|
|
571
|
+
### Windows Named Pipes
|
|
572
|
+
|
|
573
|
+
- **Path format**: `\\\\.\\pipe\\<name>`
|
|
574
|
+
- **Namespace**: Global by default
|
|
575
|
+
- **Permissions**: Controlled by ACLs
|
|
576
|
+
- **Cleanup**: Automatic on server close
|
|
577
|
+
|
|
578
|
+
Example:
|
|
579
|
+
```typescript
|
|
580
|
+
const path = "\\\\.\\pipe\\my-app-worker-1";
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Unix Domain Sockets
|
|
584
|
+
|
|
585
|
+
- **Path format**: Absolute path (e.g., `/tmp/socket.sock`)
|
|
586
|
+
- **Max length**: 108 characters (Linux), 104 (macOS)
|
|
587
|
+
- **Permissions**: File system permissions (chmod)
|
|
588
|
+
- **Cleanup**: Manual (remove socket file)
|
|
589
|
+
|
|
590
|
+
Example:
|
|
591
|
+
```typescript
|
|
592
|
+
const path = "/tmp/my-app-worker-1.sock";
|
|
593
|
+
|
|
594
|
+
// Cleanup stale socket
|
|
595
|
+
const server = new SocketServer({ unlinkOnListen: true });
|
|
596
|
+
await server.listen(path);
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Path Recommendations
|
|
600
|
+
|
|
601
|
+
- **Development**: Use `/tmp/` on Unix, `\\\\.\\pipe\\` on Windows
|
|
602
|
+
- **Production**: Use app-specific directory with proper permissions
|
|
603
|
+
- **Multiple instances**: Include PID or unique ID in path
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
import { tmpdir } from "os";
|
|
607
|
+
import { join } from "path";
|
|
608
|
+
|
|
609
|
+
const path = isWindows()
|
|
610
|
+
? `\\\\.\\pipe\\my-app-${process.pid}`
|
|
611
|
+
: join(tmpdir(), `my-app-${process.pid}.sock`);
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
## Troubleshooting
|
|
615
|
+
|
|
616
|
+
### Hanging Requests
|
|
617
|
+
|
|
618
|
+
**Symptoms**: `channel.request()` never resolves.
|
|
619
|
+
|
|
620
|
+
**Common causes**:
|
|
621
|
+
1. **Framing mismatch**: Parent uses line-delimited, child uses length-prefixed
|
|
622
|
+
2. **Codec mismatch**: Parent uses JSON, child uses MessagePack
|
|
623
|
+
3. **Protocol mismatch**: Parent uses JSON-RPC, child uses custom protocol
|
|
624
|
+
4. **Child not responding**: Child crashed or deadlocked
|
|
625
|
+
|
|
626
|
+
**Solutions**:
|
|
627
|
+
- Verify both sides use identical framing/codec/protocol
|
|
628
|
+
- Check child process stderr for errors
|
|
629
|
+
- Add timeout to requests: `{ timeout: 5000 }`
|
|
630
|
+
- Enable debug logging (see below)
|
|
631
|
+
|
|
632
|
+
### Connection Refused (ECONNREFUSED)
|
|
633
|
+
|
|
634
|
+
**Symptoms**: `SocketTransport.connect()` fails with ECONNREFUSED.
|
|
635
|
+
|
|
636
|
+
**Common causes**:
|
|
637
|
+
1. Server not listening yet
|
|
638
|
+
2. Wrong path
|
|
639
|
+
3. Stale socket file (Unix)
|
|
640
|
+
|
|
641
|
+
**Solutions**:
|
|
642
|
+
```typescript
|
|
643
|
+
// Increase connection timeout
|
|
644
|
+
const transport = new SocketTransport({
|
|
645
|
+
path: "/tmp/socket.sock",
|
|
646
|
+
connectionTimeout: 10000, // Wait up to 10s
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// Or cleanup stale sockets
|
|
650
|
+
const server = new SocketServer({ unlinkOnListen: true });
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### Buffer Limits Exceeded
|
|
654
|
+
|
|
655
|
+
**Symptoms**: Errors about message size or buffer limits.
|
|
656
|
+
|
|
657
|
+
**Common causes**:
|
|
658
|
+
- Message exceeds `maxMessageSize` (length-prefixed)
|
|
659
|
+
- Line exceeds `maxLineLength` (line-delimited)
|
|
660
|
+
|
|
661
|
+
**Solutions**:
|
|
662
|
+
```typescript
|
|
663
|
+
// Increase limits
|
|
664
|
+
const framing = new LengthPrefixedFraming({
|
|
665
|
+
maxMessageSize: 100 * 1024 * 1024, // 100MB
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// Or split large messages
|
|
669
|
+
const chunks = splitIntoChunks(largeData, 1024 * 1024);
|
|
670
|
+
for (const chunk of chunks) {
|
|
671
|
+
await channel.request("processChunk", chunk);
|
|
672
|
+
}
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Worker Crashes on Startup
|
|
676
|
+
|
|
677
|
+
**Symptoms**: `ProcessManager.spawn()` fails or worker exits immediately.
|
|
678
|
+
|
|
679
|
+
**Common causes**:
|
|
680
|
+
1. Syntax error in worker code
|
|
681
|
+
2. Missing dependencies
|
|
682
|
+
3. Wrong executable path
|
|
683
|
+
|
|
684
|
+
**Solutions**:
|
|
685
|
+
- Test worker standalone: `node worker.js`
|
|
686
|
+
- Check stderr: `startupTimeout` should be long enough
|
|
687
|
+
- Verify executable path and args
|
|
688
|
+
|
|
689
|
+
### Memory Leaks
|
|
690
|
+
|
|
691
|
+
**Symptoms**: Memory usage grows over time.
|
|
692
|
+
|
|
693
|
+
**Common causes**:
|
|
694
|
+
- Unclosed channels
|
|
695
|
+
- Unremoved event listeners
|
|
696
|
+
- Buffered data not consumed
|
|
697
|
+
|
|
698
|
+
**Solutions**:
|
|
699
|
+
```typescript
|
|
700
|
+
// Always close channels
|
|
701
|
+
try {
|
|
702
|
+
await channel.request(...);
|
|
703
|
+
} finally {
|
|
704
|
+
await channel.close();
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Remove listeners
|
|
708
|
+
const unsub = channel.onNotification("log", handler);
|
|
709
|
+
// Later...
|
|
710
|
+
unsub();
|
|
711
|
+
|
|
712
|
+
// Set maxListeners if needed
|
|
713
|
+
channel.setMaxListeners(100);
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
## Debugging
|
|
717
|
+
|
|
718
|
+
### Enable Debug Logging
|
|
719
|
+
|
|
720
|
+
```typescript
|
|
721
|
+
// Set environment variable
|
|
722
|
+
process.env.DEBUG = "@procwire:*";
|
|
723
|
+
|
|
724
|
+
// Or programmatically (if debug package is used in future)
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### Inspect Raw Data
|
|
728
|
+
|
|
729
|
+
```typescript
|
|
730
|
+
import { inspect } from "util";
|
|
731
|
+
|
|
732
|
+
transport.onData((data) => {
|
|
733
|
+
console.error("Raw data:", inspect(data, { depth: null }));
|
|
734
|
+
});
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### Test Worker Standalone
|
|
738
|
+
|
|
739
|
+
Test child process independently:
|
|
740
|
+
|
|
741
|
+
```bash
|
|
742
|
+
# Send JSON-RPC request manually
|
|
743
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"add","params":{"a":2,"b":3}}' | node worker.js
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
## Examples
|
|
747
|
+
|
|
748
|
+
See the [examples/](../../examples/) directory for complete, runnable examples:
|
|
749
|
+
|
|
750
|
+
- [basic-stdio](../../examples/basic-stdio/) - Simple parent/child with stdio
|
|
751
|
+
- [dual-channel](../../examples/dual-channel/) - Control + data channels with MessagePack
|
|
752
|
+
- [rust-worker](../../examples/rust-worker/) - Cross-language IPC with Rust
|
|
753
|
+
|
|
754
|
+
## Performance Tips
|
|
755
|
+
|
|
756
|
+
1. **Choose the right framing**:
|
|
757
|
+
- Line-delimited: Best for text protocols (JSON-RPC over stdio)
|
|
758
|
+
- Length-prefixed: Best for binary protocols (MessagePack, Protobuf)
|
|
759
|
+
|
|
760
|
+
2. **Choose the right codec**:
|
|
761
|
+
- JSON: Human-readable, debugging
|
|
762
|
+
- MessagePack: 20-50% smaller, 2-5x faster than JSON
|
|
763
|
+
- Protobuf: Schema validation, cross-language
|
|
764
|
+
- Arrow: Columnar data, analytics workloads
|
|
765
|
+
|
|
766
|
+
3. **Use dual channels**:
|
|
767
|
+
- Control channel (stdio): Lightweight commands
|
|
768
|
+
- Data channel (pipe): Bulk data transfer
|
|
769
|
+
|
|
770
|
+
4. **Batch requests**:
|
|
771
|
+
- Send multiple items in one request instead of many small requests
|
|
772
|
+
- Reduces protocol overhead
|
|
773
|
+
|
|
774
|
+
5. **Reuse channels**:
|
|
775
|
+
- Keep channels open for multiple requests
|
|
776
|
+
- Avoid spawning processes repeatedly
|
|
777
|
+
|
|
778
|
+
6. **Monitor resource usage**:
|
|
779
|
+
- Track memory with `process.memoryUsage()`
|
|
780
|
+
- Profile CPU with `--prof` flag
|
|
781
|
+
- Use `clinic` for performance analysis
|
|
782
|
+
|
|
783
|
+
## TypeScript
|
|
784
|
+
|
|
785
|
+
Full TypeScript support with generics:
|
|
786
|
+
|
|
787
|
+
```typescript
|
|
788
|
+
interface User {
|
|
789
|
+
id: number;
|
|
790
|
+
name: string;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Typed request
|
|
794
|
+
const user = await channel.request<User>("getUser", { id: 123 });
|
|
795
|
+
console.log(user.name); // Type-safe
|
|
796
|
+
|
|
797
|
+
// Typed handler
|
|
798
|
+
channel.onRequest<{ id: number }, User>("getUser", async (params) => {
|
|
799
|
+
// params.id is number
|
|
800
|
+
return { id: params.id, name: "Alice" }; // Must return User
|
|
801
|
+
});
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
## Contributing
|
|
805
|
+
|
|
806
|
+
See [CONTRIBUTING.md](../CONTRIBUTING.md) for development setup and guidelines.
|
|
807
|
+
|
|
808
|
+
## License
|
|
809
|
+
|
|
810
|
+
MIT - See [LICENSE](../LICENSE) for details.
|
|
811
|
+
|
|
812
|
+
## Roadmap
|
|
813
|
+
|
|
814
|
+
### v0.1.0 (Current)
|
|
815
|
+
- Core transport, framing, serialization, protocol layers
|
|
816
|
+
- Stdio and pipe/socket transports
|
|
817
|
+
- JSON-RPC 2.0 and simple protocols
|
|
818
|
+
- ProcessManager with restart policies
|
|
819
|
+
- Optional codecs: MessagePack, Protobuf, Arrow
|
|
820
|
+
|
|
821
|
+
### v0.2.0 (Planned)
|
|
822
|
+
- HTTP/WebSocket transports
|
|
823
|
+
- Streaming support
|
|
824
|
+
- Compression layer (gzip, brotli)
|
|
825
|
+
- Authentication/authorization hooks
|
|
826
|
+
- Metrics and monitoring
|
|
827
|
+
|
|
828
|
+
### v1.0.0 (Future)
|
|
829
|
+
- Production-ready stable API
|
|
830
|
+
- Performance optimizations
|
|
831
|
+
- Comprehensive docs and examples
|
|
832
|
+
- Battle-tested in production
|
|
833
|
+
|
|
834
|
+
## Related Packages
|
|
835
|
+
|
|
836
|
+
- [@procwire/codec-msgpack](../codec-msgpack/) - MessagePack serialization
|
|
837
|
+
- [@procwire/codec-protobuf](../codec-protobuf/) - Protocol Buffers serialization
|
|
838
|
+
- [@procwire/codec-arrow](../codec-arrow/) - Apache Arrow serialization
|
|
839
|
+
|
|
840
|
+
## Support
|
|
841
|
+
|
|
842
|
+
- **Issues**: [GitHub Issues](https://github.com/your-org/ipc-bridge-core/issues)
|
|
843
|
+
- **Discussions**: [GitHub Discussions](https://github.com/your-org/ipc-bridge-core/discussions)
|
|
844
|
+
- **Documentation**: [Architecture Docs](../docs/procwire-transport-architecture.md)
|