Expand category docs and module APIs across the library.
Manual pass adds helpers (listChannels, taskCounts, openProposals), richer getStats with protocol fields, category README hubs, and tightened api.md for core, messaging, network, routing, supercomputer, and consensus modules. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -1,219 +1,19 @@
|
||||
# API: hyper-p2p-rpc
|
||||
|
||||
**Protocol:** `hyper-p2p-rpc/v2` (request/reply and streaming on one Protomux channel)
|
||||
**Protocols:** `hyper-p2p-rpc/v2` (server/client)
|
||||
|
||||
**Export:** `{ RPCServer, RPCClient, RPC_PROTOCOL, generateId }`
|
||||
## HyperP2PRPCServer
|
||||
|
||||
Note: `STREAM_PROTOCOL` (`hyper-p2p-rpc-stream/v2`) is exported as a constant in source but streaming is implemented on the primary `RPC_PROTOCOL` channel via `chunk` / `done` reply fields.
|
||||
### `register(name, handler)` / `unregister(name)`
|
||||
|
||||
## Overview
|
||||
### `listServices() → string[]` / `connectionCount() → number`
|
||||
|
||||
`hyper-p2p-rpc` provides typed RPC over an existing duplex socket (typically `@hyperswarm/secret-stream` after Hyperswarm connects). `RPCServer` registers named async handlers on a connection; `RPCClient` issues calls and consumes results. Handlers may return a plain value or an **async iterable**; the server then streams chunks on the same channel before a terminal `done` or `error` frame.
|
||||
### `getStats() → { requests, streams, services, connections, protocol }`
|
||||
|
||||
This module does not join Hyperswarm itself—wire it after you have a socket from presence, session-bridge, or your own swarm setup.
|
||||
## HyperP2PRPCClient
|
||||
|
||||
## RPCServer
|
||||
### `call(service, method, args?, opts?)`
|
||||
|
||||
### Constructor
|
||||
### `getStats() → { calls, errors, protocol }`
|
||||
|
||||
```js
|
||||
const server = new RPCServer(opts)
|
||||
```
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `signingKeyPair` | `KeyPair` \| `null` | `null` | Reserved for future signed RPC; not used in v0.3.1 handlers |
|
||||
| `timeout` | `number` | `30000` | Default RPC timeout in ms for `server.call()` |
|
||||
|
||||
Internal state: `services` (`Map`), `connections` (`Set`), `_channels` (`WeakMap` socket → `{ channel, rpcMsg }`).
|
||||
|
||||
### `register(name, handler, schema = null)`
|
||||
|
||||
Registers a service method.
|
||||
|
||||
- **Parameters:**
|
||||
- `name` — `string` method name
|
||||
- `handler` — `async function (params, ctx) => result | AsyncIterable`
|
||||
- `schema` — stored on the entry but **not validated** in current implementation
|
||||
- **Returns:** `void`
|
||||
- **Throws:**
|
||||
- `TypeError: handler must be function`
|
||||
|
||||
`ctx` object passed to handlers:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `socket` | duplex stream | Connection that received the call |
|
||||
| `peerKey` | `Buffer` \| `null` | `socket.remotePublicKey` if set |
|
||||
|
||||
### `handleConnection(socket)`
|
||||
|
||||
Attaches Protomux `hyper-p2p-rpc/v2` to `socket`. Idempotent per socket (second call is a no-op).
|
||||
|
||||
- **Returns:** `void`
|
||||
- **Throws:** —
|
||||
|
||||
Emits `connection` with `socket` when the channel opens.
|
||||
|
||||
### `call(socket, method, params = {}, timeoutMs = this.defaultTimeout)`
|
||||
|
||||
Server-initiated RPC to a peer on an already-handled socket (adds a temporary reply listener on the same channel).
|
||||
|
||||
- **Returns:** `Promise<result>` — resolved with `reply.result`
|
||||
- **Throws:**
|
||||
- `Error: Socket not connected to RPC server`
|
||||
- `Error: RPC_TIMEOUT`
|
||||
- `Error: <reply.error>` — remote error string (e.g. `METHOD_NOT_FOUND: <name>`)
|
||||
|
||||
### `close()`
|
||||
|
||||
Destroys all tracked sockets, clears services, emits `close`.
|
||||
|
||||
- **Returns:** `void`
|
||||
- **Throws:** —
|
||||
|
||||
### `getStats()`
|
||||
|
||||
- **Returns:** `{ ops: number, errors: number }` — shallow copy (counters default `0`)
|
||||
- **Throws:** —
|
||||
|
||||
## RPCClient
|
||||
|
||||
### Constructor
|
||||
|
||||
```js
|
||||
const client = new RPCClient(socket, opts)
|
||||
```
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `timeout` | `number` | `30000` | Default timeout for `call()` and `callStream()` |
|
||||
|
||||
Requires a duplex `socket` compatible with `Protomux.from(socket)`. Opens `hyper-p2p-rpc/v2` immediately in `_setupControlChannel`.
|
||||
|
||||
### `call(method, params = {}, timeoutMs = this.defaultTimeout)`
|
||||
|
||||
- **Returns:** `Promise<result>` — `reply.result` from server
|
||||
- **Throws:**
|
||||
- `Error: RPC_TIMEOUT`
|
||||
- `Error: <reply.error>` — includes `METHOD_NOT_FOUND: <method>` and handler exception messages
|
||||
- `Error: Connection closed` — if socket closes while pending
|
||||
|
||||
### `callStream(method, params = {}, timeoutMs = this.defaultTimeout)`
|
||||
|
||||
Invokes a handler that returns an async iterable; resolves to an async iterable of chunks.
|
||||
|
||||
- **Returns:** `Promise<AsyncIterable>` — object with `[Symbol.asyncIterator]`
|
||||
- **Throws:**
|
||||
- `Error: STREAM_TIMEOUT` — no `{ stream: true }` ack before timeout
|
||||
- `Error: <reply.error>` — server or stream failure
|
||||
- `Error: stream read timeout` — no chunk/done within `timeoutMs` while reading
|
||||
- `Error: Connection closed`
|
||||
|
||||
Iterator behavior:
|
||||
|
||||
- Yields each `reply.chunk` value
|
||||
- Stops when `reply.done === true`
|
||||
- Throws `new Error(data.error)` if a stream frame carries `error`
|
||||
|
||||
### `close()`
|
||||
|
||||
Calls `socket.destroy()`.
|
||||
|
||||
- **Returns:** `void`
|
||||
- **Throws:** —
|
||||
|
||||
## Wire reply shapes (client view)
|
||||
|
||||
| Frame | Fields | Meaning |
|
||||
|-------|--------|---------|
|
||||
| Success | `{ id, result }` | Unary RPC complete |
|
||||
| Error | `{ id, error: string }` | Failed RPC or stream |
|
||||
| Stream ack | `{ id, stream: true }` | Server will send chunks |
|
||||
| Chunk | `{ id, chunk: any }` | One streamed value |
|
||||
| Done | `{ id, done: true }` | Stream finished |
|
||||
|
||||
Request frame: `{ id, method, params }` where `id` is 32 hex chars from `generateId()` (`crypto.randomBytes(16)`).
|
||||
|
||||
## Events
|
||||
|
||||
### RPCServer
|
||||
|
||||
| Event | Payload | When |
|
||||
|-------|---------|------|
|
||||
| `connection` | `socket` | RPC Protomux channel `onopen` |
|
||||
| `close` | — | `close()` |
|
||||
|
||||
### RPCClient
|
||||
|
||||
| Event | Payload | When |
|
||||
|-------|---------|------|
|
||||
| `close` | — | Socket `close`; all pending calls rejected |
|
||||
|
||||
## getStats()
|
||||
|
||||
| Field | Type | Meaning |
|
||||
|-------|------|---------|
|
||||
| `ops` | `number` | Reserved counter (default `0`) |
|
||||
| `errors` | `number` | Reserved counter (default `0`) |
|
||||
|
||||
## Errors
|
||||
|
||||
Stable message substrings: see [`../_shared/ERROR_CODES.md`](../../_shared/ERROR_CODES.md).
|
||||
|
||||
| Message | Where |
|
||||
|---------|--------|
|
||||
| `handler must be function` | `register()` — `TypeError` |
|
||||
| `Socket not connected to RPC server` | `RPCServer.call()` |
|
||||
| `RPC_TIMEOUT` | `RPCServer.call()`, `RPCClient.call()` |
|
||||
| `STREAM_TIMEOUT` | `RPCClient.callStream()` initial ack |
|
||||
| `stream read timeout` | `RPCClient.callStream()` iterator `next()` |
|
||||
| `Connection closed` | `RPCClient` on socket close |
|
||||
| `METHOD_NOT_FOUND: <method>` | Sent in `reply.error`, surfaced as `Error` on client |
|
||||
|
||||
Handler exceptions are sent as `{ id, error: err.message || String(err) }` (not rethrown on server).
|
||||
|
||||
## P2P
|
||||
|
||||
Typical integration:
|
||||
|
||||
1. Establish a Hyperswarm (or paired `SecretStream`) connection between two peers.
|
||||
2. `server.handleConnection(socketA)` and `new RPCClient(socketB)` on the paired ends.
|
||||
3. `client.call('method', params)` or `client.callStream('method', params)`.
|
||||
|
||||
`RPC_PROTOCOL` must match on both sides (`core-infrastructure/hyper-p2p-rpc/v2`). Encoding is `compact-encoding` `c.json` for all RPC frames.
|
||||
|
||||
Unary server flow: handler return value → `{ id, result }`.
|
||||
|
||||
Streaming server flow: async iterable from handler → `{ id, stream: true }`, then `{ id, chunk }` per yield, then `{ id, done: true }` or `{ id, error }`.
|
||||
|
||||
Pairing helper for tests: `lib/pair.js` (`pairSecretStreams`, `waitSecretStreamsConnected`).
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
cd modules/core-infrastructure/hyper-p2p-rpc
|
||||
npm install && npm test
|
||||
```
|
||||
|
||||
`test/test.js` — registration API and close. For full wire coverage, run streaming and integration tests manually:
|
||||
|
||||
```bash
|
||||
bare test/streaming-test.js
|
||||
bare test/integration-two-node.js
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
bare examples/basic.js
|
||||
bare examples/streaming.js
|
||||
```
|
||||
|
||||
## Module exports
|
||||
|
||||
```js
|
||||
const { RPCServer, RPCClient, RPC_PROTOCOL, generateId } = require('hyper-p2p-rpc')
|
||||
```
|
||||
|
||||
`generateId()` returns a 32-character hex string (16 random bytes).
|
||||
Pair streams via `lib/pair.js`. Events: `request`, `stream`, `error`.
|
||||
|
||||
@@ -118,8 +118,17 @@ class RPCServer extends EventEmitter {
|
||||
}
|
||||
|
||||
|
||||
listServices () { return [...this.services.keys()] }
|
||||
|
||||
connectionCount () { return this.connections.size }
|
||||
|
||||
getStats () {
|
||||
return { ...this._stats }
|
||||
return {
|
||||
...this._stats,
|
||||
services: this.services.size,
|
||||
connections: this.connections.size,
|
||||
protocol: RPC_PROTOCOL
|
||||
}
|
||||
}
|
||||
|
||||
close () {
|
||||
|
||||
Reference in New Issue
Block a user