@noy-db/by-peer 0.1.0-pre.3
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 +66 -0
- package/dist/index.cjs +444 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +236 -0
- package/dist/index.d.ts +236 -0
- package/dist/index.js +410 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { NoydbStore } from '@noy-db/hub';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `PeerChannel` — the minimal duplex message primitive used by the p2p
|
|
5
|
+
* NoydbStore wrapper. Any transport that can deliver UTF-8 strings
|
|
6
|
+
* reliably and in-order qualifies: WebRTC DataChannel, BroadcastChannel,
|
|
7
|
+
* MessagePort, WebSocket, even postMessage pairs.
|
|
8
|
+
*
|
|
9
|
+
* Keeping the transport abstract has three payoffs:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Tests run without a WebRTC polyfill.** `pairInMemory()` returns
|
|
12
|
+
* two wired channels for conformance tests against `to-memory`.
|
|
13
|
+
* 2. **Consumers pick their signaling story.** Matrix rooms, QR codes,
|
|
14
|
+
* pastebin, Firebase Realtime DB — the handshake is out of scope.
|
|
15
|
+
* 3. **Future transports slot in cheaply.** WebTransport (HTTP/3),
|
|
16
|
+
* libp2p, Iroh, or a plain relay WebSocket become additional
|
|
17
|
+
* bindings without touching the RPC layer.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Minimal duplex message primitive.
|
|
23
|
+
*
|
|
24
|
+
* Implementations MUST deliver every `send` payload in order exactly
|
|
25
|
+
* once to every live `on('message')` subscriber. `close()` is best-effort
|
|
26
|
+
* — once called, further `send()` calls MAY throw and `on('close')` MUST
|
|
27
|
+
* fire once.
|
|
28
|
+
*/
|
|
29
|
+
interface PeerChannel {
|
|
30
|
+
/** Enqueue a payload for delivery to the remote end. */
|
|
31
|
+
send(payload: string): void;
|
|
32
|
+
/** Subscribe to incoming payloads or lifecycle events. Returns unsubscribe. */
|
|
33
|
+
on(event: 'message', listener: (payload: string) => void): () => void;
|
|
34
|
+
on(event: 'close', listener: () => void): () => void;
|
|
35
|
+
/** Close the channel. Idempotent. */
|
|
36
|
+
close(): void;
|
|
37
|
+
/** True once the channel is ready for `send`. */
|
|
38
|
+
readonly isOpen: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a pair of in-memory `PeerChannel`s wired to each other.
|
|
42
|
+
* Intended for tests and multi-tab simulations inside a single process.
|
|
43
|
+
*/
|
|
44
|
+
declare function pairInMemory(): [PeerChannel, PeerChannel];
|
|
45
|
+
/**
|
|
46
|
+
* Wrap a WebRTC `RTCDataChannel` as a `PeerChannel`.
|
|
47
|
+
*
|
|
48
|
+
* Browser-only — the caller is responsible for establishing the
|
|
49
|
+
* `RTCPeerConnection`, exchanging SDP offers/answers out of band, and
|
|
50
|
+
* passing the opened DataChannel here. When the remote peer is only
|
|
51
|
+
* reachable via TURN, the relay sees DTLS-wrapped ciphertext (noy-db
|
|
52
|
+
* already encrypts at rest, so even a TURN compromise leaks nothing).
|
|
53
|
+
*/
|
|
54
|
+
declare function fromDataChannel(dc: RTCDataChannel): PeerChannel;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* JSON-RPC protocol over a `PeerChannel`.
|
|
58
|
+
*
|
|
59
|
+
* Request shape:
|
|
60
|
+
* `{ t: 'req', id, method, args }`
|
|
61
|
+
* Response shape (success):
|
|
62
|
+
* `{ t: 'res', id, ok: true, result }`
|
|
63
|
+
* Response shape (error):
|
|
64
|
+
* `{ t: 'res', id, ok: false, error: { name, message, version? } }`
|
|
65
|
+
*
|
|
66
|
+
* Why not reuse msgpack/protobuf? The payloads are already base64-encoded
|
|
67
|
+
* ciphertext — further binary packing saves ~8-12% at a large dependency
|
|
68
|
+
* cost. JSON over UTF-8 is inspectable, fits the zero-dependency ethos,
|
|
69
|
+
* and WebRTC DataChannel string mode already frames for us.
|
|
70
|
+
*
|
|
71
|
+
* @module
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/** Wire format discriminator for RPC messages. */
|
|
75
|
+
type RpcMessage = RpcRequest | RpcResponse;
|
|
76
|
+
interface RpcRequest {
|
|
77
|
+
readonly t: 'req';
|
|
78
|
+
readonly id: string;
|
|
79
|
+
readonly method: string;
|
|
80
|
+
readonly args: readonly unknown[];
|
|
81
|
+
}
|
|
82
|
+
interface RpcResponse {
|
|
83
|
+
readonly t: 'res';
|
|
84
|
+
readonly id: string;
|
|
85
|
+
readonly ok: boolean;
|
|
86
|
+
readonly result?: unknown;
|
|
87
|
+
readonly error?: {
|
|
88
|
+
name: string;
|
|
89
|
+
message: string;
|
|
90
|
+
version?: number;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Handler invoked when an RPC request arrives. Return value is serialized as `result`. */
|
|
94
|
+
type RpcHandler = (method: string, args: readonly unknown[]) => Promise<unknown>;
|
|
95
|
+
/** Options for a client-side RPC caller. */
|
|
96
|
+
interface RpcClientOptions {
|
|
97
|
+
/** Max milliseconds to wait for a response before rejecting. */
|
|
98
|
+
timeoutMs?: number;
|
|
99
|
+
}
|
|
100
|
+
/** Client: wrap a `PeerChannel` in a `call(method, args)` helper. */
|
|
101
|
+
declare function createRpcClient(channel: PeerChannel, opts?: RpcClientOptions): {
|
|
102
|
+
call<T = unknown>(method: string, args: readonly unknown[]): Promise<T>;
|
|
103
|
+
dispose(): void;
|
|
104
|
+
};
|
|
105
|
+
/** Server: dispatch incoming RPC requests through a handler. Returns a dispose fn. */
|
|
106
|
+
declare function serveRpc(channel: PeerChannel, handler: RpcHandler): () => void;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* `peerStore()` — a `NoydbStore` backed by RPC calls over a `PeerChannel`.
|
|
110
|
+
*
|
|
111
|
+
* The local peer calls `get`/`put`/`delete`/… against this store as if
|
|
112
|
+
* it were any other backend; every call is serialized as an RPC request
|
|
113
|
+
* to the remote peer, which runs `servePeerStore()` to funnel the RPCs
|
|
114
|
+
* into its own local `NoydbStore`.
|
|
115
|
+
*
|
|
116
|
+
* Error re-hydration: the remote handler re-throws `ConflictError` with
|
|
117
|
+
* a `.version` field when a CAS check fails. The RPC layer carries
|
|
118
|
+
* `version` in the error envelope so the local caller can catch
|
|
119
|
+
* `ConflictError` with the same semantics as a direct store call.
|
|
120
|
+
*
|
|
121
|
+
* @module
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
interface PeerStoreOptions {
|
|
125
|
+
/** The duplex channel to the remote peer. */
|
|
126
|
+
readonly channel: PeerChannel;
|
|
127
|
+
/** Max ms to wait for any single RPC response. Default 30s. */
|
|
128
|
+
readonly timeoutMs?: number;
|
|
129
|
+
/** Optional display name used in diagnostics. Default `'by-peer'`. */
|
|
130
|
+
readonly name?: string;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create a `NoydbStore` that forwards every operation to a remote peer
|
|
134
|
+
* over the supplied `PeerChannel`. The remote peer must be running
|
|
135
|
+
* `servePeerStore()` against its own local store.
|
|
136
|
+
*/
|
|
137
|
+
declare function peerStore(opts: PeerStoreOptions): NoydbStore & {
|
|
138
|
+
dispose: () => void;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* `servePeerStore()` — runs on the peer that owns the data. Listens on
|
|
143
|
+
* a `PeerChannel` for RPC requests from a remote `peerStore()` client
|
|
144
|
+
* and executes each one against the local `NoydbStore`.
|
|
145
|
+
*
|
|
146
|
+
* The 6 core methods plus the optional `ping` / `listSince` / `listPage`
|
|
147
|
+
* extensions are exposed. Unknown methods surface as a remote Error.
|
|
148
|
+
*
|
|
149
|
+
* @module
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
interface ServePeerStoreOptions {
|
|
153
|
+
/** The duplex channel from the remote peer. */
|
|
154
|
+
readonly channel: PeerChannel;
|
|
155
|
+
/** The local store to serve. */
|
|
156
|
+
readonly store: NoydbStore;
|
|
157
|
+
/**
|
|
158
|
+
* Optional method whitelist. When provided, any method not in the set
|
|
159
|
+
* is rejected with "method not allowed". Useful for read-only peers.
|
|
160
|
+
*/
|
|
161
|
+
readonly allow?: ReadonlySet<string>;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Start serving the local store on the channel. Returns a dispose
|
|
165
|
+
* function that stops the RPC listener. The underlying channel is NOT
|
|
166
|
+
* closed by dispose — ownership stays with the caller.
|
|
167
|
+
*/
|
|
168
|
+
declare function servePeerStore(opts: ServePeerStoreOptions): () => void;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* WebRTC handshake helper — opinionated wrapper around
|
|
172
|
+
* `RTCPeerConnection` that produces a ready-to-use `PeerChannel`.
|
|
173
|
+
*
|
|
174
|
+
* The handshake is split into two halves so the caller can ferry the
|
|
175
|
+
* SDP blobs over whatever signaling channel they prefer (QR code,
|
|
176
|
+
* Matrix room, pastebin, Firebase, signed URL…). Signaling is
|
|
177
|
+
* intentionally out of scope — noy-db has no opinion on how peers
|
|
178
|
+
* discover each other, only on what flows once they do.
|
|
179
|
+
*
|
|
180
|
+
* ```ts
|
|
181
|
+
* // Peer A (initiator)
|
|
182
|
+
* const a = await createOffer({ iceServers })
|
|
183
|
+
* send(a.offer) // → signaling channel
|
|
184
|
+
* const answer = await receive() // ← signaling channel
|
|
185
|
+
* await a.accept(answer)
|
|
186
|
+
* const channel = await a.channel // ready PeerChannel
|
|
187
|
+
*
|
|
188
|
+
* // Peer B (responder)
|
|
189
|
+
* const offer = await receive() // ← signaling channel
|
|
190
|
+
* const b = await acceptOffer(offer, { iceServers })
|
|
191
|
+
* send(b.answer) // → signaling channel
|
|
192
|
+
* const channel = await b.channel // ready PeerChannel
|
|
193
|
+
* ```
|
|
194
|
+
*
|
|
195
|
+
* Browser-only. Node consumers who want to interconnect with browsers
|
|
196
|
+
* can plug `@roamhq/wrtc` into the global `RTCPeerConnection` slot; this
|
|
197
|
+
* module does not pull it in so the package has zero runtime deps.
|
|
198
|
+
*
|
|
199
|
+
* @module
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
type PeerConnection = RTCPeerConnection;
|
|
203
|
+
type SessionDescription = RTCSessionDescriptionInit;
|
|
204
|
+
interface WebRTCOptions {
|
|
205
|
+
/** Optional ICE servers (STUN / TURN). */
|
|
206
|
+
readonly iceServers?: RTCIceServer[];
|
|
207
|
+
/** Label for the `RTCDataChannel`. Default `'noydb'`. */
|
|
208
|
+
readonly label?: string;
|
|
209
|
+
}
|
|
210
|
+
interface Initiator {
|
|
211
|
+
readonly offer: SessionDescription;
|
|
212
|
+
/** Feed in the remote peer's SDP answer to complete the handshake. */
|
|
213
|
+
accept(answer: SessionDescription): Promise<void>;
|
|
214
|
+
/** Resolves with the opened `PeerChannel` once the DataChannel is live. */
|
|
215
|
+
readonly channel: Promise<PeerChannel>;
|
|
216
|
+
readonly connection: PeerConnection;
|
|
217
|
+
}
|
|
218
|
+
interface Responder {
|
|
219
|
+
readonly answer: SessionDescription;
|
|
220
|
+
/** Resolves with the opened `PeerChannel` once the DataChannel is live. */
|
|
221
|
+
readonly channel: Promise<PeerChannel>;
|
|
222
|
+
readonly connection: PeerConnection;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Build an offer as the initiating peer. Returns the SDP offer to send
|
|
226
|
+
* to the remote peer and a promise for the opened `PeerChannel`.
|
|
227
|
+
*/
|
|
228
|
+
declare function createOffer(opts?: WebRTCOptions): Promise<Initiator>;
|
|
229
|
+
/**
|
|
230
|
+
* Accept an incoming offer as the responding peer. Returns the SDP
|
|
231
|
+
* answer to send back to the initiator and a promise for the opened
|
|
232
|
+
* `PeerChannel`.
|
|
233
|
+
*/
|
|
234
|
+
declare function acceptOffer(offer: SessionDescription, opts?: WebRTCOptions): Promise<Responder>;
|
|
235
|
+
|
|
236
|
+
export { type Initiator, type PeerChannel, type PeerStoreOptions, type Responder, type RpcClientOptions, type RpcHandler, type RpcMessage, type RpcRequest, type RpcResponse, type ServePeerStoreOptions, type WebRTCOptions, acceptOffer, createOffer, createRpcClient, fromDataChannel, pairInMemory, peerStore, servePeerStore, serveRpc };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { NoydbStore } from '@noy-db/hub';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `PeerChannel` — the minimal duplex message primitive used by the p2p
|
|
5
|
+
* NoydbStore wrapper. Any transport that can deliver UTF-8 strings
|
|
6
|
+
* reliably and in-order qualifies: WebRTC DataChannel, BroadcastChannel,
|
|
7
|
+
* MessagePort, WebSocket, even postMessage pairs.
|
|
8
|
+
*
|
|
9
|
+
* Keeping the transport abstract has three payoffs:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Tests run without a WebRTC polyfill.** `pairInMemory()` returns
|
|
12
|
+
* two wired channels for conformance tests against `to-memory`.
|
|
13
|
+
* 2. **Consumers pick their signaling story.** Matrix rooms, QR codes,
|
|
14
|
+
* pastebin, Firebase Realtime DB — the handshake is out of scope.
|
|
15
|
+
* 3. **Future transports slot in cheaply.** WebTransport (HTTP/3),
|
|
16
|
+
* libp2p, Iroh, or a plain relay WebSocket become additional
|
|
17
|
+
* bindings without touching the RPC layer.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Minimal duplex message primitive.
|
|
23
|
+
*
|
|
24
|
+
* Implementations MUST deliver every `send` payload in order exactly
|
|
25
|
+
* once to every live `on('message')` subscriber. `close()` is best-effort
|
|
26
|
+
* — once called, further `send()` calls MAY throw and `on('close')` MUST
|
|
27
|
+
* fire once.
|
|
28
|
+
*/
|
|
29
|
+
interface PeerChannel {
|
|
30
|
+
/** Enqueue a payload for delivery to the remote end. */
|
|
31
|
+
send(payload: string): void;
|
|
32
|
+
/** Subscribe to incoming payloads or lifecycle events. Returns unsubscribe. */
|
|
33
|
+
on(event: 'message', listener: (payload: string) => void): () => void;
|
|
34
|
+
on(event: 'close', listener: () => void): () => void;
|
|
35
|
+
/** Close the channel. Idempotent. */
|
|
36
|
+
close(): void;
|
|
37
|
+
/** True once the channel is ready for `send`. */
|
|
38
|
+
readonly isOpen: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a pair of in-memory `PeerChannel`s wired to each other.
|
|
42
|
+
* Intended for tests and multi-tab simulations inside a single process.
|
|
43
|
+
*/
|
|
44
|
+
declare function pairInMemory(): [PeerChannel, PeerChannel];
|
|
45
|
+
/**
|
|
46
|
+
* Wrap a WebRTC `RTCDataChannel` as a `PeerChannel`.
|
|
47
|
+
*
|
|
48
|
+
* Browser-only — the caller is responsible for establishing the
|
|
49
|
+
* `RTCPeerConnection`, exchanging SDP offers/answers out of band, and
|
|
50
|
+
* passing the opened DataChannel here. When the remote peer is only
|
|
51
|
+
* reachable via TURN, the relay sees DTLS-wrapped ciphertext (noy-db
|
|
52
|
+
* already encrypts at rest, so even a TURN compromise leaks nothing).
|
|
53
|
+
*/
|
|
54
|
+
declare function fromDataChannel(dc: RTCDataChannel): PeerChannel;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* JSON-RPC protocol over a `PeerChannel`.
|
|
58
|
+
*
|
|
59
|
+
* Request shape:
|
|
60
|
+
* `{ t: 'req', id, method, args }`
|
|
61
|
+
* Response shape (success):
|
|
62
|
+
* `{ t: 'res', id, ok: true, result }`
|
|
63
|
+
* Response shape (error):
|
|
64
|
+
* `{ t: 'res', id, ok: false, error: { name, message, version? } }`
|
|
65
|
+
*
|
|
66
|
+
* Why not reuse msgpack/protobuf? The payloads are already base64-encoded
|
|
67
|
+
* ciphertext — further binary packing saves ~8-12% at a large dependency
|
|
68
|
+
* cost. JSON over UTF-8 is inspectable, fits the zero-dependency ethos,
|
|
69
|
+
* and WebRTC DataChannel string mode already frames for us.
|
|
70
|
+
*
|
|
71
|
+
* @module
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/** Wire format discriminator for RPC messages. */
|
|
75
|
+
type RpcMessage = RpcRequest | RpcResponse;
|
|
76
|
+
interface RpcRequest {
|
|
77
|
+
readonly t: 'req';
|
|
78
|
+
readonly id: string;
|
|
79
|
+
readonly method: string;
|
|
80
|
+
readonly args: readonly unknown[];
|
|
81
|
+
}
|
|
82
|
+
interface RpcResponse {
|
|
83
|
+
readonly t: 'res';
|
|
84
|
+
readonly id: string;
|
|
85
|
+
readonly ok: boolean;
|
|
86
|
+
readonly result?: unknown;
|
|
87
|
+
readonly error?: {
|
|
88
|
+
name: string;
|
|
89
|
+
message: string;
|
|
90
|
+
version?: number;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Handler invoked when an RPC request arrives. Return value is serialized as `result`. */
|
|
94
|
+
type RpcHandler = (method: string, args: readonly unknown[]) => Promise<unknown>;
|
|
95
|
+
/** Options for a client-side RPC caller. */
|
|
96
|
+
interface RpcClientOptions {
|
|
97
|
+
/** Max milliseconds to wait for a response before rejecting. */
|
|
98
|
+
timeoutMs?: number;
|
|
99
|
+
}
|
|
100
|
+
/** Client: wrap a `PeerChannel` in a `call(method, args)` helper. */
|
|
101
|
+
declare function createRpcClient(channel: PeerChannel, opts?: RpcClientOptions): {
|
|
102
|
+
call<T = unknown>(method: string, args: readonly unknown[]): Promise<T>;
|
|
103
|
+
dispose(): void;
|
|
104
|
+
};
|
|
105
|
+
/** Server: dispatch incoming RPC requests through a handler. Returns a dispose fn. */
|
|
106
|
+
declare function serveRpc(channel: PeerChannel, handler: RpcHandler): () => void;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* `peerStore()` — a `NoydbStore` backed by RPC calls over a `PeerChannel`.
|
|
110
|
+
*
|
|
111
|
+
* The local peer calls `get`/`put`/`delete`/… against this store as if
|
|
112
|
+
* it were any other backend; every call is serialized as an RPC request
|
|
113
|
+
* to the remote peer, which runs `servePeerStore()` to funnel the RPCs
|
|
114
|
+
* into its own local `NoydbStore`.
|
|
115
|
+
*
|
|
116
|
+
* Error re-hydration: the remote handler re-throws `ConflictError` with
|
|
117
|
+
* a `.version` field when a CAS check fails. The RPC layer carries
|
|
118
|
+
* `version` in the error envelope so the local caller can catch
|
|
119
|
+
* `ConflictError` with the same semantics as a direct store call.
|
|
120
|
+
*
|
|
121
|
+
* @module
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
interface PeerStoreOptions {
|
|
125
|
+
/** The duplex channel to the remote peer. */
|
|
126
|
+
readonly channel: PeerChannel;
|
|
127
|
+
/** Max ms to wait for any single RPC response. Default 30s. */
|
|
128
|
+
readonly timeoutMs?: number;
|
|
129
|
+
/** Optional display name used in diagnostics. Default `'by-peer'`. */
|
|
130
|
+
readonly name?: string;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create a `NoydbStore` that forwards every operation to a remote peer
|
|
134
|
+
* over the supplied `PeerChannel`. The remote peer must be running
|
|
135
|
+
* `servePeerStore()` against its own local store.
|
|
136
|
+
*/
|
|
137
|
+
declare function peerStore(opts: PeerStoreOptions): NoydbStore & {
|
|
138
|
+
dispose: () => void;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* `servePeerStore()` — runs on the peer that owns the data. Listens on
|
|
143
|
+
* a `PeerChannel` for RPC requests from a remote `peerStore()` client
|
|
144
|
+
* and executes each one against the local `NoydbStore`.
|
|
145
|
+
*
|
|
146
|
+
* The 6 core methods plus the optional `ping` / `listSince` / `listPage`
|
|
147
|
+
* extensions are exposed. Unknown methods surface as a remote Error.
|
|
148
|
+
*
|
|
149
|
+
* @module
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
interface ServePeerStoreOptions {
|
|
153
|
+
/** The duplex channel from the remote peer. */
|
|
154
|
+
readonly channel: PeerChannel;
|
|
155
|
+
/** The local store to serve. */
|
|
156
|
+
readonly store: NoydbStore;
|
|
157
|
+
/**
|
|
158
|
+
* Optional method whitelist. When provided, any method not in the set
|
|
159
|
+
* is rejected with "method not allowed". Useful for read-only peers.
|
|
160
|
+
*/
|
|
161
|
+
readonly allow?: ReadonlySet<string>;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Start serving the local store on the channel. Returns a dispose
|
|
165
|
+
* function that stops the RPC listener. The underlying channel is NOT
|
|
166
|
+
* closed by dispose — ownership stays with the caller.
|
|
167
|
+
*/
|
|
168
|
+
declare function servePeerStore(opts: ServePeerStoreOptions): () => void;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* WebRTC handshake helper — opinionated wrapper around
|
|
172
|
+
* `RTCPeerConnection` that produces a ready-to-use `PeerChannel`.
|
|
173
|
+
*
|
|
174
|
+
* The handshake is split into two halves so the caller can ferry the
|
|
175
|
+
* SDP blobs over whatever signaling channel they prefer (QR code,
|
|
176
|
+
* Matrix room, pastebin, Firebase, signed URL…). Signaling is
|
|
177
|
+
* intentionally out of scope — noy-db has no opinion on how peers
|
|
178
|
+
* discover each other, only on what flows once they do.
|
|
179
|
+
*
|
|
180
|
+
* ```ts
|
|
181
|
+
* // Peer A (initiator)
|
|
182
|
+
* const a = await createOffer({ iceServers })
|
|
183
|
+
* send(a.offer) // → signaling channel
|
|
184
|
+
* const answer = await receive() // ← signaling channel
|
|
185
|
+
* await a.accept(answer)
|
|
186
|
+
* const channel = await a.channel // ready PeerChannel
|
|
187
|
+
*
|
|
188
|
+
* // Peer B (responder)
|
|
189
|
+
* const offer = await receive() // ← signaling channel
|
|
190
|
+
* const b = await acceptOffer(offer, { iceServers })
|
|
191
|
+
* send(b.answer) // → signaling channel
|
|
192
|
+
* const channel = await b.channel // ready PeerChannel
|
|
193
|
+
* ```
|
|
194
|
+
*
|
|
195
|
+
* Browser-only. Node consumers who want to interconnect with browsers
|
|
196
|
+
* can plug `@roamhq/wrtc` into the global `RTCPeerConnection` slot; this
|
|
197
|
+
* module does not pull it in so the package has zero runtime deps.
|
|
198
|
+
*
|
|
199
|
+
* @module
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
type PeerConnection = RTCPeerConnection;
|
|
203
|
+
type SessionDescription = RTCSessionDescriptionInit;
|
|
204
|
+
interface WebRTCOptions {
|
|
205
|
+
/** Optional ICE servers (STUN / TURN). */
|
|
206
|
+
readonly iceServers?: RTCIceServer[];
|
|
207
|
+
/** Label for the `RTCDataChannel`. Default `'noydb'`. */
|
|
208
|
+
readonly label?: string;
|
|
209
|
+
}
|
|
210
|
+
interface Initiator {
|
|
211
|
+
readonly offer: SessionDescription;
|
|
212
|
+
/** Feed in the remote peer's SDP answer to complete the handshake. */
|
|
213
|
+
accept(answer: SessionDescription): Promise<void>;
|
|
214
|
+
/** Resolves with the opened `PeerChannel` once the DataChannel is live. */
|
|
215
|
+
readonly channel: Promise<PeerChannel>;
|
|
216
|
+
readonly connection: PeerConnection;
|
|
217
|
+
}
|
|
218
|
+
interface Responder {
|
|
219
|
+
readonly answer: SessionDescription;
|
|
220
|
+
/** Resolves with the opened `PeerChannel` once the DataChannel is live. */
|
|
221
|
+
readonly channel: Promise<PeerChannel>;
|
|
222
|
+
readonly connection: PeerConnection;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Build an offer as the initiating peer. Returns the SDP offer to send
|
|
226
|
+
* to the remote peer and a promise for the opened `PeerChannel`.
|
|
227
|
+
*/
|
|
228
|
+
declare function createOffer(opts?: WebRTCOptions): Promise<Initiator>;
|
|
229
|
+
/**
|
|
230
|
+
* Accept an incoming offer as the responding peer. Returns the SDP
|
|
231
|
+
* answer to send back to the initiator and a promise for the opened
|
|
232
|
+
* `PeerChannel`.
|
|
233
|
+
*/
|
|
234
|
+
declare function acceptOffer(offer: SessionDescription, opts?: WebRTCOptions): Promise<Responder>;
|
|
235
|
+
|
|
236
|
+
export { type Initiator, type PeerChannel, type PeerStoreOptions, type Responder, type RpcClientOptions, type RpcHandler, type RpcMessage, type RpcRequest, type RpcResponse, type ServePeerStoreOptions, type WebRTCOptions, acceptOffer, createOffer, createRpcClient, fromDataChannel, pairInMemory, peerStore, servePeerStore, serveRpc };
|