@net-mesh/sdk 0.19.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/dist/mesh.d.ts ADDED
@@ -0,0 +1,369 @@
1
+ /**
2
+ * MeshNode — the multi-peer encrypted mesh handle.
3
+ *
4
+ * Wraps the NAPI `NetMesh` with ergonomic TypeScript APIs: typed
5
+ * `StreamConfig`, classed `BackpressureError` / `NotConnectedError`
6
+ * for `instanceof`-based pattern matching, and the `send_with_retry`
7
+ * / `send_blocking` helpers from the Rust core.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { MeshNode, BackpressureError, Reliability } from '@net-mesh/sdk';
12
+ *
13
+ * const node = await MeshNode.create({
14
+ * bindAddr: '127.0.0.1:9000',
15
+ * psk: '0'.repeat(64),
16
+ * });
17
+ *
18
+ * await node.connect('127.0.0.1:9001', peerPubkey, 0x2222n);
19
+ * node.start();
20
+ *
21
+ * const stream = node.openStream(0x2222n, {
22
+ * streamId: 7n,
23
+ * reliability: 'reliable',
24
+ * windowBytes: 256,
25
+ * });
26
+ *
27
+ * try {
28
+ * await node.sendOnStream(stream, [Buffer.from('hello')]);
29
+ * } catch (e) {
30
+ * if (e instanceof BackpressureError) {
31
+ * // daemon chose: drop, buffer, or retry
32
+ * } else {
33
+ * throw e;
34
+ * }
35
+ * }
36
+ * ```
37
+ */
38
+ import { type CapabilityFilter, type CapabilitySet, type ScopeFilter } from './capabilities';
39
+ import type { SubnetId, SubnetPolicy } from './subnets';
40
+ import type { Token } from './identity';
41
+ /** Reliability mode chosen at stream-open time. */
42
+ export type Reliability = 'fire_and_forget' | 'reliable';
43
+ /** Per-stream configuration for {@link MeshNode.openStream}. */
44
+ export interface StreamConfig {
45
+ /**
46
+ * Caller-chosen stream identifier. Opaque `bigint` at the transport
47
+ * layer; no value range has reserved meaning.
48
+ */
49
+ streamId: bigint;
50
+ /** Reliability mode. Default: `'fire_and_forget'`. */
51
+ reliability?: Reliability;
52
+ /**
53
+ * Initial send-credit window in bytes. Leave unset to inherit the
54
+ * core's `DEFAULT_STREAM_WINDOW_BYTES` (64 KB) — v2 backpressure
55
+ * is ON out of the box. Pass `0` to restore the v1 unbounded-queue
56
+ * behavior on this stream.
57
+ */
58
+ windowBytes?: number;
59
+ /**
60
+ * Fair-scheduler weight. `1` = equal share; higher = proportionally
61
+ * more packets per round. Default: `1`.
62
+ */
63
+ fairnessWeight?: number;
64
+ }
65
+ /** Per-stream stats snapshot. */
66
+ export interface StreamStats {
67
+ txSeq: bigint;
68
+ rxSeq: bigint;
69
+ inboundPending: bigint;
70
+ lastActivityNs: bigint;
71
+ active: boolean;
72
+ /** Cumulative Backpressure rejections since stream opened. */
73
+ backpressureEvents: bigint;
74
+ /**
75
+ * Bytes of send credit still available. `0` means the next send
76
+ * will be rejected as Backpressure. Receiver-driven `StreamWindow`
77
+ * grants replenish this counter.
78
+ */
79
+ txCreditRemaining: number;
80
+ /**
81
+ * Configured initial credit window in bytes. `0` disables
82
+ * backpressure entirely on this stream (escape hatch).
83
+ */
84
+ txWindow: number;
85
+ /** Cumulative StreamWindow grants received from the peer. */
86
+ creditGrantsReceived: bigint;
87
+ /** Cumulative StreamWindow grants emitted to the peer. */
88
+ creditGrantsSent: bigint;
89
+ }
90
+ /**
91
+ * Thrown by {@link MeshNode.sendOnStream} / `sendWithRetry` /
92
+ * `sendBlocking` when the stream's per-stream in-flight window is
93
+ * full. **The event was NOT sent.** Caller decides whether to drop,
94
+ * retry, or buffer at the app layer — see the "Back-pressure" section
95
+ * in `docs/TRANSPORT.md` for the three canonical patterns.
96
+ */
97
+ export declare class BackpressureError extends Error {
98
+ constructor(detail?: string);
99
+ }
100
+ /**
101
+ * Thrown when the stream's peer session is gone (peer never
102
+ * connected, disconnected, or the stream was closed). Distinct from
103
+ * {@link BackpressureError} because this is a "connection lost", not
104
+ * "too fast".
105
+ */
106
+ export declare class NotConnectedError extends Error {
107
+ constructor(detail?: string);
108
+ }
109
+ /**
110
+ * Options for {@link MeshNode.subscribeChannel}. Struct form so
111
+ * future knobs (timeout override, priority) don't break callers.
112
+ */
113
+ export interface SubscribeOptions {
114
+ /**
115
+ * Token to present to the publisher. The publisher verifies the
116
+ * ed25519 signature, checks the subject matches the subscribing
117
+ * peer's `EntityId`, and installs the token in its local cache
118
+ * before running `can_subscribe`. A matching token satisfies
119
+ * `requireToken` channels end-to-end.
120
+ */
121
+ token?: Token;
122
+ }
123
+ /** Options for {@link MeshNode.create}. */
124
+ export interface MeshNodeConfig {
125
+ /** Local bind address (e.g. `"127.0.0.1:9000"`). */
126
+ bindAddr: string;
127
+ /** Hex-encoded 32-byte pre-shared key (64 hex chars). */
128
+ psk: string;
129
+ /** Heartbeat interval in milliseconds. Default: 5000. */
130
+ heartbeatIntervalMs?: number;
131
+ /** Session timeout in milliseconds. Default: 30000. */
132
+ sessionTimeoutMs?: number;
133
+ /** Inbound shard count. Default: 4. */
134
+ numShards?: number;
135
+ /**
136
+ * Capability-index GC sweep interval in milliseconds. Default:
137
+ * 60_000. Shorter values make TTL-driven eviction more responsive
138
+ * at the cost of extra CPU; primarily useful in tests.
139
+ */
140
+ capabilityGcIntervalMs?: number;
141
+ /**
142
+ * Drop inbound `CapabilityAnnouncement` packets without a
143
+ * signature. Default: false. Signature *validity* is not yet
144
+ * enforced end-to-end — this is presence-only policy today.
145
+ */
146
+ requireSignedCapabilities?: boolean;
147
+ /**
148
+ * Pin this node to a specific subnet. Omitted = no restriction
149
+ * (`SubnetId::GLOBAL`). Visibility checks on the publish +
150
+ * subscribe paths compare against this value.
151
+ */
152
+ subnet?: SubnetId;
153
+ /**
154
+ * Policy that derives each peer's subnet from their capability
155
+ * announcements. Mesh-wide policy consistency is assumed —
156
+ * mismatched policies lead to asymmetric views of peer subnets.
157
+ */
158
+ subnetPolicy?: SubnetPolicy;
159
+ /**
160
+ * 32-byte ed25519 seed. When set, the mesh's keypair is
161
+ * derived from this seed — so its `entityId` and `nodeId`
162
+ * are reproducible across restarts, and a caller-side
163
+ * `Identity.fromSeed(seed)` can issue tokens that validate
164
+ * against this mesh. Treat as secret material.
165
+ */
166
+ identitySeed?: Buffer;
167
+ }
168
+ /**
169
+ * An opaque stream handle. Pass back to `sendOnStream` /
170
+ * `sendWithRetry` / `sendBlocking` / `closeStream`. You normally
171
+ * don't need to read the fields — they're exposed for diagnostics.
172
+ */
173
+ export interface MeshStream {
174
+ readonly peerNodeId: bigint;
175
+ readonly streamId: bigint;
176
+ }
177
+ /**
178
+ * A node on the Net mesh with full stream multiplexing + backpressure
179
+ * support.
180
+ */
181
+ export declare class MeshNode {
182
+ private native;
183
+ private constructor();
184
+ /** Create and configure a new mesh node. */
185
+ static create(config: MeshNodeConfig): Promise<MeshNode>;
186
+ /** 32-byte ed25519 entity id for this mesh. */
187
+ entityId(): Buffer;
188
+ /** Hex-encoded Noise static public key. */
189
+ publicKey(): string;
190
+ /** This node's id. */
191
+ nodeId(): bigint;
192
+ /** Connect to a peer as initiator. */
193
+ connect(peerAddr: string, peerPublicKey: string, peerNodeId: bigint): Promise<void>;
194
+ /** Accept an incoming connection as responder. Returns the peer's wire address. */
195
+ accept(peerNodeId: bigint): Promise<string>;
196
+ /** Start the receive loop / heartbeats / router. */
197
+ start(): Promise<void>;
198
+ /** Number of connected peers. */
199
+ peerCount(): number;
200
+ /**
201
+ * Open (or look up) a logical stream to a connected peer. Repeated
202
+ * calls for the same `(peer, streamId)` are idempotent; the first
203
+ * open wins and later differing configs are logged and ignored.
204
+ */
205
+ openStream(peerNodeId: bigint, config: StreamConfig): MeshStream;
206
+ /** Close a stream. Idempotent. */
207
+ closeStream(peerNodeId: bigint, streamId: bigint): void;
208
+ /**
209
+ * Send a batch of events on an explicit stream. Throws
210
+ * {@link BackpressureError} when the stream's in-flight window is
211
+ * full (no events sent — caller decides what to do),
212
+ * {@link NotConnectedError} when the peer session is gone, or a
213
+ * plain `Error` for underlying transport failures.
214
+ */
215
+ sendOnStream(stream: MeshStream, events: Buffer[]): Promise<void>;
216
+ /**
217
+ * Send events, retrying on {@link BackpressureError} with 5 ms → 200 ms
218
+ * exponential backoff up to `maxRetries` times. Transport errors and
219
+ * `NotConnectedError` are re-thrown immediately (they're not a
220
+ * pressure signal).
221
+ */
222
+ sendWithRetry(stream: MeshStream, events: Buffer[], maxRetries?: number): Promise<void>;
223
+ /**
224
+ * Block the calling task until the send succeeds or a transport
225
+ * error occurs. Retries {@link BackpressureError} with 5 ms → 200 ms
226
+ * exponential backoff up to 4096 times (~13 min worst case) —
227
+ * effectively "block until the network lets up" under practical
228
+ * workloads, but with a hard upper bound so runaway pressure can't
229
+ * hang the caller forever. Use {@link sendWithRetry} for a tighter
230
+ * bound.
231
+ */
232
+ sendBlocking(stream: MeshStream, events: Buffer[]): Promise<void>;
233
+ /** Snapshot per-stream stats. `null` if the peer or stream isn't open. */
234
+ streamStats(peerNodeId: bigint, streamId: bigint): StreamStats | null;
235
+ /**
236
+ * Register a channel on this node. Subscribers who ask to join are
237
+ * validated against `config` before being added to the roster.
238
+ *
239
+ * Mirrors the core `ChannelConfig` field-for-field. v1 omits
240
+ * `publishCaps` / `subscribeCaps` — those land with the security
241
+ * plan's identity surface.
242
+ */
243
+ registerChannel(config: ChannelConfig): void;
244
+ /**
245
+ * Ask `publisherNodeId` to add this node to `channel`'s subscriber
246
+ * set. Blocks until the publisher's `Ack` arrives or the
247
+ * membership-ack timeout elapses.
248
+ *
249
+ * Pass `opts.token` to present a
250
+ * {@link Token PermissionToken} issued by the publisher — required
251
+ * when the channel was registered with `requireToken: true` or
252
+ * when your caps alone don't satisfy `subscribeCaps`. The
253
+ * publisher verifies the signature, checks `subject ===
254
+ * thisNode.entityId`, installs it in its local cache, then runs
255
+ * the ACL check.
256
+ *
257
+ * Throws a {@link ChannelAuthError} or {@link ChannelError} on
258
+ * rejection; network-level failures propagate as plain `Error`.
259
+ */
260
+ subscribeChannel(publisherNodeId: bigint, channel: string, opts?: SubscribeOptions): Promise<void>;
261
+ /** Mirror of {@link subscribeChannel}. Idempotent on the publisher side. */
262
+ unsubscribeChannel(publisherNodeId: bigint, channel: string): Promise<void>;
263
+ /**
264
+ * Publish one payload to every subscriber of `channel`. Returns a
265
+ * {@link PublishReport} describing per-peer outcomes.
266
+ */
267
+ publish(channel: string, payload: Buffer, config?: PublishConfig): Promise<PublishReport>;
268
+ /**
269
+ * Announce this node's capabilities to every directly-connected
270
+ * peer. Self-indexes too, so `findNodes` on this same node matches
271
+ * on the announcement. Multi-hop propagation is deferred — peers
272
+ * more than one hop away will not see the announcement.
273
+ */
274
+ announceCapabilities(caps: CapabilitySet): Promise<void>;
275
+ /**
276
+ * Query the local capability index. Returns node ids (including
277
+ * our own `nodeId()` if self matches) whose latest announcement
278
+ * matches `filter`.
279
+ */
280
+ findNodes(filter: CapabilityFilter): bigint[];
281
+ /**
282
+ * Scoped variant of {@link findNodes}. Filters candidates through
283
+ * a {@link ScopeFilter} derived from each peer's `scope:*`
284
+ * reserved tags (e.g. `scope:tenant:oem-123`,
285
+ * `scope:region:eu-west`, `scope:subnet-local`).
286
+ *
287
+ * Untagged peers stay visible under most filters by design;
288
+ * peers tagged `scope:subnet-local` only show up under
289
+ * `{ kind: 'sameSubnet' }`. See `docs/SCOPED_CAPABILITIES_PLAN.md`
290
+ * for the full table.
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * // GPU pool for a specific tenant.
295
+ * const peers = node.findNodesScoped(
296
+ * { requireTags: ['model:llama3-70b'] },
297
+ * { kind: 'tenant', tenant: 'oem-123' },
298
+ * );
299
+ * ```
300
+ */
301
+ findNodesScoped(filter: CapabilityFilter, scope: ScopeFilter): bigint[];
302
+ /** Shutdown the mesh node. */
303
+ shutdown(): Promise<void>;
304
+ }
305
+ export type Visibility = 'subnet-local' | 'parent-visible' | 'exported' | 'global';
306
+ export type OnFailure = 'best_effort' | 'fail_fast' | 'collect';
307
+ /** Channel configuration — mirror of the core `ChannelConfig`. */
308
+ export interface ChannelConfig {
309
+ /** Canonical channel name. Crosses the boundary as a string. */
310
+ name: string;
311
+ /** Default: `'global'`. */
312
+ visibility?: Visibility;
313
+ /** Default reliability for streams on this channel. */
314
+ reliable?: boolean;
315
+ /**
316
+ * When true, subscribers must present a valid
317
+ * `PermissionToken` whose subject matches their entity id.
318
+ */
319
+ requireToken?: boolean;
320
+ /** Priority (0 = lowest). */
321
+ priority?: number;
322
+ /** Rate cap in packets per second. */
323
+ maxRatePps?: number;
324
+ /**
325
+ * Capability filter the publisher itself must satisfy before
326
+ * fan-out. `publish` rejects with a `channel:` error on
327
+ * mismatch.
328
+ */
329
+ publishCaps?: CapabilityFilter;
330
+ /**
331
+ * Capability filter each subscriber must satisfy.
332
+ * `subscribeChannel` throws a `ChannelAuthError` on mismatch.
333
+ */
334
+ subscribeCaps?: CapabilityFilter;
335
+ }
336
+ /** Publish-fanout config — mirror of the core `PublishConfig`. */
337
+ export interface PublishConfig {
338
+ /** Default: `'fire_and_forget'`. */
339
+ reliability?: Reliability;
340
+ /** Default: `'best_effort'`. */
341
+ onFailure?: OnFailure;
342
+ /** Max concurrent per-peer sends. Default 32. */
343
+ maxInflight?: number;
344
+ }
345
+ /** Per-peer report returned by {@link MeshNode.publish}. */
346
+ export interface PublishReport {
347
+ attempted: number;
348
+ delivered: number;
349
+ errors: Array<{
350
+ nodeId: bigint;
351
+ message: string;
352
+ }>;
353
+ }
354
+ /**
355
+ * Raised when a channel operation fails for a reason other than
356
+ * auth. The napi binding emits `"channel: ..."` prefixed errors that
357
+ * the SDK classifies into {@link ChannelAuthError} (unauthorized) or
358
+ * this class (everything else).
359
+ */
360
+ export declare class ChannelError extends Error {
361
+ constructor(detail?: string);
362
+ }
363
+ /**
364
+ * Raised when a Subscribe / Unsubscribe request is rejected because
365
+ * the subscriber isn't authorized on the publisher's channel config.
366
+ */
367
+ export declare class ChannelAuthError extends ChannelError {
368
+ constructor(detail?: string);
369
+ }