@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/README.md +1684 -0
- package/dist/_internal.d.ts +25 -0
- package/dist/_internal.js +60 -0
- package/dist/capabilities.d.ts +271 -0
- package/dist/capabilities.js +186 -0
- package/dist/capability-enhancements.d.ts +574 -0
- package/dist/capability-enhancements.js +1324 -0
- package/dist/capability-schema.d.ts +112 -0
- package/dist/capability-schema.js +317 -0
- package/dist/channel.d.ts +56 -0
- package/dist/channel.js +95 -0
- package/dist/compute.d.ts +546 -0
- package/dist/compute.js +741 -0
- package/dist/cortex.d.ts +236 -0
- package/dist/cortex.js +584 -0
- package/dist/deck.d.ts +342 -0
- package/dist/deck.js +717 -0
- package/dist/groups.d.ts +208 -0
- package/dist/groups.js +431 -0
- package/dist/identity.d.ts +149 -0
- package/dist/identity.js +264 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +147 -0
- package/dist/mesh.d.ts +369 -0
- package/dist/mesh.js +433 -0
- package/dist/meshdb.d.ts +87 -0
- package/dist/meshdb.js +111 -0
- package/dist/meshos.d.ts +277 -0
- package/dist/meshos.js +359 -0
- package/dist/node.d.ts +120 -0
- package/dist/node.js +246 -0
- package/dist/redis-dedup.d.ts +48 -0
- package/dist/redis-dedup.js +52 -0
- package/dist/stream.d.ts +47 -0
- package/dist/stream.js +118 -0
- package/dist/subnets.d.ts +75 -0
- package/dist/subnets.js +54 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.js +5 -0
- package/package.json +43 -0
package/dist/mesh.js
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MeshNode — the multi-peer encrypted mesh handle.
|
|
4
|
+
*
|
|
5
|
+
* Wraps the NAPI `NetMesh` with ergonomic TypeScript APIs: typed
|
|
6
|
+
* `StreamConfig`, classed `BackpressureError` / `NotConnectedError`
|
|
7
|
+
* for `instanceof`-based pattern matching, and the `send_with_retry`
|
|
8
|
+
* / `send_blocking` helpers from the Rust core.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { MeshNode, BackpressureError, Reliability } from '@net-mesh/sdk';
|
|
13
|
+
*
|
|
14
|
+
* const node = await MeshNode.create({
|
|
15
|
+
* bindAddr: '127.0.0.1:9000',
|
|
16
|
+
* psk: '0'.repeat(64),
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* await node.connect('127.0.0.1:9001', peerPubkey, 0x2222n);
|
|
20
|
+
* node.start();
|
|
21
|
+
*
|
|
22
|
+
* const stream = node.openStream(0x2222n, {
|
|
23
|
+
* streamId: 7n,
|
|
24
|
+
* reliability: 'reliable',
|
|
25
|
+
* windowBytes: 256,
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* try {
|
|
29
|
+
* await node.sendOnStream(stream, [Buffer.from('hello')]);
|
|
30
|
+
* } catch (e) {
|
|
31
|
+
* if (e instanceof BackpressureError) {
|
|
32
|
+
* // daemon chose: drop, buffer, or retry
|
|
33
|
+
* } else {
|
|
34
|
+
* throw e;
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.ChannelAuthError = exports.ChannelError = exports.MeshNode = exports.NotConnectedError = exports.BackpressureError = void 0;
|
|
41
|
+
const core_1 = require("@net-mesh/core");
|
|
42
|
+
const _internal_js_1 = require("./_internal.js");
|
|
43
|
+
const capabilities_1 = require("./capabilities");
|
|
44
|
+
/**
|
|
45
|
+
* Thrown by {@link MeshNode.sendOnStream} / `sendWithRetry` /
|
|
46
|
+
* `sendBlocking` when the stream's per-stream in-flight window is
|
|
47
|
+
* full. **The event was NOT sent.** Caller decides whether to drop,
|
|
48
|
+
* retry, or buffer at the app layer — see the "Back-pressure" section
|
|
49
|
+
* in `docs/TRANSPORT.md` for the three canonical patterns.
|
|
50
|
+
*/
|
|
51
|
+
class BackpressureError extends Error {
|
|
52
|
+
constructor(detail) {
|
|
53
|
+
super(detail ?? 'stream would block (queue full)');
|
|
54
|
+
this.name = 'BackpressureError';
|
|
55
|
+
Object.setPrototypeOf(this, BackpressureError.prototype);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.BackpressureError = BackpressureError;
|
|
59
|
+
/**
|
|
60
|
+
* Thrown when the stream's peer session is gone (peer never
|
|
61
|
+
* connected, disconnected, or the stream was closed). Distinct from
|
|
62
|
+
* {@link BackpressureError} because this is a "connection lost", not
|
|
63
|
+
* "too fast".
|
|
64
|
+
*/
|
|
65
|
+
class NotConnectedError extends Error {
|
|
66
|
+
constructor(detail) {
|
|
67
|
+
super(detail ?? 'stream not connected');
|
|
68
|
+
this.name = 'NotConnectedError';
|
|
69
|
+
Object.setPrototypeOf(this, NotConnectedError.prototype);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.NotConnectedError = NotConnectedError;
|
|
73
|
+
/**
|
|
74
|
+
* Translate a napi-thrown error into one of the typed stream error
|
|
75
|
+
* classes if it matches the stable prefix contract from the binding.
|
|
76
|
+
* Anything else is passed through unchanged.
|
|
77
|
+
*/
|
|
78
|
+
function toStreamError(e) {
|
|
79
|
+
const msg = e?.message ?? '';
|
|
80
|
+
// Prefixes are part of the binding's stable contract; see
|
|
81
|
+
// `bindings/node/src/lib.rs` (`ERR_BACKPRESSURE_PREFIX` /
|
|
82
|
+
// `ERR_NOT_CONNECTED_PREFIX`).
|
|
83
|
+
if (msg.startsWith('stream would block')) {
|
|
84
|
+
throw new BackpressureError(msg);
|
|
85
|
+
}
|
|
86
|
+
if (msg.startsWith('stream not connected')) {
|
|
87
|
+
throw new NotConnectedError(msg);
|
|
88
|
+
}
|
|
89
|
+
throw e;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A node on the Net mesh with full stream multiplexing + backpressure
|
|
93
|
+
* support.
|
|
94
|
+
*/
|
|
95
|
+
class MeshNode {
|
|
96
|
+
native;
|
|
97
|
+
constructor(native) {
|
|
98
|
+
this.native = native;
|
|
99
|
+
// Register on the WeakMap so sibling SDK modules can reach
|
|
100
|
+
// the native pointer without a public escape-hatch method on
|
|
101
|
+
// the class instance. See `./_internal.ts` for why.
|
|
102
|
+
(0, _internal_js_1.setNapiMesh)(this, native);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* **Test-only.** Inject a synthetic peer entry into the local
|
|
106
|
+
* capability index so vitest suites can stage multi-candidate
|
|
107
|
+
* placement for `ReplicaGroup` / `ForkGroup` / `StandbyGroup`
|
|
108
|
+
* tests without a full 3-node handshake.
|
|
109
|
+
*
|
|
110
|
+
* Not part of the stable API; do NOT use in production code —
|
|
111
|
+
* the real mesh surface is `announceCapabilities`. Gated at the
|
|
112
|
+
* NAPI layer behind the `test-helpers` cargo feature; release
|
|
113
|
+
* builds of `@net-mesh/core` do not export `testInjectSyntheticPeer`
|
|
114
|
+
* and this method will throw if called against such a build.
|
|
115
|
+
*
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
_testInjectSyntheticPeer(nodeId) {
|
|
119
|
+
const native = this.native;
|
|
120
|
+
if (typeof native.testInjectSyntheticPeer !== 'function') {
|
|
121
|
+
throw new Error('testInjectSyntheticPeer: NAPI build missing `test-helpers` feature');
|
|
122
|
+
}
|
|
123
|
+
native.testInjectSyntheticPeer(nodeId);
|
|
124
|
+
}
|
|
125
|
+
/** Create and configure a new mesh node. */
|
|
126
|
+
static async create(config) {
|
|
127
|
+
const native = await core_1.NetMesh.create({
|
|
128
|
+
bindAddr: config.bindAddr,
|
|
129
|
+
psk: config.psk,
|
|
130
|
+
heartbeatIntervalMs: config.heartbeatIntervalMs,
|
|
131
|
+
sessionTimeoutMs: config.sessionTimeoutMs,
|
|
132
|
+
numShards: config.numShards,
|
|
133
|
+
capabilityGcIntervalMs: config.capabilityGcIntervalMs,
|
|
134
|
+
requireSignedCapabilities: config.requireSignedCapabilities,
|
|
135
|
+
subnet: config.subnet,
|
|
136
|
+
subnetPolicy: config.subnetPolicy,
|
|
137
|
+
identitySeed: config.identitySeed,
|
|
138
|
+
});
|
|
139
|
+
return new MeshNode(native);
|
|
140
|
+
}
|
|
141
|
+
/** 32-byte ed25519 entity id for this mesh. */
|
|
142
|
+
entityId() {
|
|
143
|
+
return this.native.entityId();
|
|
144
|
+
}
|
|
145
|
+
/** Hex-encoded Noise static public key. */
|
|
146
|
+
publicKey() {
|
|
147
|
+
return this.native.publicKey();
|
|
148
|
+
}
|
|
149
|
+
/** This node's id. */
|
|
150
|
+
nodeId() {
|
|
151
|
+
return this.native.nodeId();
|
|
152
|
+
}
|
|
153
|
+
/** Connect to a peer as initiator. */
|
|
154
|
+
async connect(peerAddr, peerPublicKey, peerNodeId) {
|
|
155
|
+
await this.native.connect(peerAddr, peerPublicKey, peerNodeId);
|
|
156
|
+
}
|
|
157
|
+
/** Accept an incoming connection as responder. Returns the peer's wire address. */
|
|
158
|
+
async accept(peerNodeId) {
|
|
159
|
+
return await this.native.accept(peerNodeId);
|
|
160
|
+
}
|
|
161
|
+
/** Start the receive loop / heartbeats / router. */
|
|
162
|
+
async start() {
|
|
163
|
+
await this.native.start();
|
|
164
|
+
}
|
|
165
|
+
/** Number of connected peers. */
|
|
166
|
+
peerCount() {
|
|
167
|
+
return this.native.peerCount();
|
|
168
|
+
}
|
|
169
|
+
// ─── Stream API ──────────────────────────────────────────────────
|
|
170
|
+
/**
|
|
171
|
+
* Open (or look up) a logical stream to a connected peer. Repeated
|
|
172
|
+
* calls for the same `(peer, streamId)` are idempotent; the first
|
|
173
|
+
* open wins and later differing configs are logged and ignored.
|
|
174
|
+
*/
|
|
175
|
+
openStream(peerNodeId, config) {
|
|
176
|
+
const native = this.native.openStream(peerNodeId, {
|
|
177
|
+
streamId: config.streamId,
|
|
178
|
+
reliability: config.reliability,
|
|
179
|
+
windowBytes: config.windowBytes,
|
|
180
|
+
fairnessWeight: config.fairnessWeight,
|
|
181
|
+
});
|
|
182
|
+
return {
|
|
183
|
+
peerNodeId,
|
|
184
|
+
streamId: config.streamId,
|
|
185
|
+
_native: native,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/** Close a stream. Idempotent. */
|
|
189
|
+
closeStream(peerNodeId, streamId) {
|
|
190
|
+
this.native.closeStream(peerNodeId, streamId);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Send a batch of events on an explicit stream. Throws
|
|
194
|
+
* {@link BackpressureError} when the stream's in-flight window is
|
|
195
|
+
* full (no events sent — caller decides what to do),
|
|
196
|
+
* {@link NotConnectedError} when the peer session is gone, or a
|
|
197
|
+
* plain `Error` for underlying transport failures.
|
|
198
|
+
*/
|
|
199
|
+
async sendOnStream(stream, events) {
|
|
200
|
+
try {
|
|
201
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
202
|
+
await this.native.sendOnStream(stream._native, events);
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
toStreamError(e);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Send events, retrying on {@link BackpressureError} with 5 ms → 200 ms
|
|
210
|
+
* exponential backoff up to `maxRetries` times. Transport errors and
|
|
211
|
+
* `NotConnectedError` are re-thrown immediately (they're not a
|
|
212
|
+
* pressure signal).
|
|
213
|
+
*/
|
|
214
|
+
async sendWithRetry(stream, events, maxRetries = 8) {
|
|
215
|
+
try {
|
|
216
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
217
|
+
await this.native.sendWithRetry(stream._native, events, maxRetries);
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
toStreamError(e);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
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
|
+
async sendBlocking(stream, events) {
|
|
233
|
+
try {
|
|
234
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
235
|
+
await this.native.sendBlocking(stream._native, events);
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
toStreamError(e);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/** Snapshot per-stream stats. `null` if the peer or stream isn't open. */
|
|
242
|
+
streamStats(peerNodeId, streamId) {
|
|
243
|
+
const raw = this.native.streamStats(peerNodeId, streamId);
|
|
244
|
+
if (!raw)
|
|
245
|
+
return null;
|
|
246
|
+
// The napi binding marshals u64 fields as `BigInt` so values that
|
|
247
|
+
// exceed `Number.MAX_SAFE_INTEGER` — especially `lastActivityNs`,
|
|
248
|
+
// Unix-epoch nanoseconds always above 2^53 — survive the boundary
|
|
249
|
+
// without a precision trap. The u32 fields stay as regular numbers.
|
|
250
|
+
return {
|
|
251
|
+
txSeq: raw.txSeq,
|
|
252
|
+
rxSeq: raw.rxSeq,
|
|
253
|
+
inboundPending: raw.inboundPending,
|
|
254
|
+
lastActivityNs: raw.lastActivityNs,
|
|
255
|
+
active: raw.active,
|
|
256
|
+
backpressureEvents: raw.backpressureEvents,
|
|
257
|
+
txCreditRemaining: raw.txCreditRemaining,
|
|
258
|
+
txWindow: raw.txWindow,
|
|
259
|
+
creditGrantsReceived: raw.creditGrantsReceived,
|
|
260
|
+
creditGrantsSent: raw.creditGrantsSent,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// =========================================================
|
|
264
|
+
// Channels (distributed pub/sub)
|
|
265
|
+
// =========================================================
|
|
266
|
+
/**
|
|
267
|
+
* Register a channel on this node. Subscribers who ask to join are
|
|
268
|
+
* validated against `config` before being added to the roster.
|
|
269
|
+
*
|
|
270
|
+
* Mirrors the core `ChannelConfig` field-for-field. v1 omits
|
|
271
|
+
* `publishCaps` / `subscribeCaps` — those land with the security
|
|
272
|
+
* plan's identity surface.
|
|
273
|
+
*/
|
|
274
|
+
registerChannel(config) {
|
|
275
|
+
try {
|
|
276
|
+
this.native.registerChannel({
|
|
277
|
+
name: config.name,
|
|
278
|
+
visibility: config.visibility,
|
|
279
|
+
reliable: config.reliable,
|
|
280
|
+
requireToken: config.requireToken,
|
|
281
|
+
priority: config.priority,
|
|
282
|
+
maxRatePps: config.maxRatePps,
|
|
283
|
+
publishCaps: config.publishCaps
|
|
284
|
+
? (0, capabilities_1.capabilityFilterToNapi)(config.publishCaps)
|
|
285
|
+
: undefined,
|
|
286
|
+
subscribeCaps: config.subscribeCaps
|
|
287
|
+
? (0, capabilities_1.capabilityFilterToNapi)(config.subscribeCaps)
|
|
288
|
+
: undefined,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
catch (e) {
|
|
292
|
+
toChannelError(e);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Ask `publisherNodeId` to add this node to `channel`'s subscriber
|
|
297
|
+
* set. Blocks until the publisher's `Ack` arrives or the
|
|
298
|
+
* membership-ack timeout elapses.
|
|
299
|
+
*
|
|
300
|
+
* Pass `opts.token` to present a
|
|
301
|
+
* {@link Token PermissionToken} issued by the publisher — required
|
|
302
|
+
* when the channel was registered with `requireToken: true` or
|
|
303
|
+
* when your caps alone don't satisfy `subscribeCaps`. The
|
|
304
|
+
* publisher verifies the signature, checks `subject ===
|
|
305
|
+
* thisNode.entityId`, installs it in its local cache, then runs
|
|
306
|
+
* the ACL check.
|
|
307
|
+
*
|
|
308
|
+
* Throws a {@link ChannelAuthError} or {@link ChannelError} on
|
|
309
|
+
* rejection; network-level failures propagate as plain `Error`.
|
|
310
|
+
*/
|
|
311
|
+
async subscribeChannel(publisherNodeId, channel, opts) {
|
|
312
|
+
try {
|
|
313
|
+
await this.native.subscribeChannel(publisherNodeId, channel, opts?.token?.bytes);
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
toChannelError(e);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/** Mirror of {@link subscribeChannel}. Idempotent on the publisher side. */
|
|
320
|
+
async unsubscribeChannel(publisherNodeId, channel) {
|
|
321
|
+
try {
|
|
322
|
+
await this.native.unsubscribeChannel(publisherNodeId, channel);
|
|
323
|
+
}
|
|
324
|
+
catch (e) {
|
|
325
|
+
toChannelError(e);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Publish one payload to every subscriber of `channel`. Returns a
|
|
330
|
+
* {@link PublishReport} describing per-peer outcomes.
|
|
331
|
+
*/
|
|
332
|
+
async publish(channel, payload, config) {
|
|
333
|
+
try {
|
|
334
|
+
const raw = await this.native.publish(channel, payload, {
|
|
335
|
+
reliability: config?.reliability,
|
|
336
|
+
onFailure: config?.onFailure,
|
|
337
|
+
maxInflight: config?.maxInflight,
|
|
338
|
+
});
|
|
339
|
+
return {
|
|
340
|
+
attempted: raw.attempted,
|
|
341
|
+
delivered: raw.delivered,
|
|
342
|
+
errors: raw.errors.map((e) => ({
|
|
343
|
+
nodeId: e.nodeId,
|
|
344
|
+
message: e.message,
|
|
345
|
+
})),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
toChannelError(e);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Announce this node's capabilities to every directly-connected
|
|
354
|
+
* peer. Self-indexes too, so `findNodes` on this same node matches
|
|
355
|
+
* on the announcement. Multi-hop propagation is deferred — peers
|
|
356
|
+
* more than one hop away will not see the announcement.
|
|
357
|
+
*/
|
|
358
|
+
async announceCapabilities(caps) {
|
|
359
|
+
await this.native.announceCapabilities((0, capabilities_1.capabilitySetToNapi)(caps));
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Query the local capability index. Returns node ids (including
|
|
363
|
+
* our own `nodeId()` if self matches) whose latest announcement
|
|
364
|
+
* matches `filter`.
|
|
365
|
+
*/
|
|
366
|
+
findNodes(filter) {
|
|
367
|
+
return this.native.findNodes((0, capabilities_1.capabilityFilterToNapi)(filter));
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Scoped variant of {@link findNodes}. Filters candidates through
|
|
371
|
+
* a {@link ScopeFilter} derived from each peer's `scope:*`
|
|
372
|
+
* reserved tags (e.g. `scope:tenant:oem-123`,
|
|
373
|
+
* `scope:region:eu-west`, `scope:subnet-local`).
|
|
374
|
+
*
|
|
375
|
+
* Untagged peers stay visible under most filters by design;
|
|
376
|
+
* peers tagged `scope:subnet-local` only show up under
|
|
377
|
+
* `{ kind: 'sameSubnet' }`. See `docs/SCOPED_CAPABILITIES_PLAN.md`
|
|
378
|
+
* for the full table.
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* // GPU pool for a specific tenant.
|
|
383
|
+
* const peers = node.findNodesScoped(
|
|
384
|
+
* { requireTags: ['model:llama3-70b'] },
|
|
385
|
+
* { kind: 'tenant', tenant: 'oem-123' },
|
|
386
|
+
* );
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
389
|
+
findNodesScoped(filter, scope) {
|
|
390
|
+
return this.native.findNodesScoped((0, capabilities_1.capabilityFilterToNapi)(filter), (0, capabilities_1.scopeFilterToNapi)(scope));
|
|
391
|
+
}
|
|
392
|
+
/** Shutdown the mesh node. */
|
|
393
|
+
async shutdown() {
|
|
394
|
+
await this.native.shutdown();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
exports.MeshNode = MeshNode;
|
|
398
|
+
/**
|
|
399
|
+
* Raised when a channel operation fails for a reason other than
|
|
400
|
+
* auth. The napi binding emits `"channel: ..."` prefixed errors that
|
|
401
|
+
* the SDK classifies into {@link ChannelAuthError} (unauthorized) or
|
|
402
|
+
* this class (everything else).
|
|
403
|
+
*/
|
|
404
|
+
class ChannelError extends Error {
|
|
405
|
+
constructor(detail) {
|
|
406
|
+
super(detail ?? 'channel error');
|
|
407
|
+
this.name = 'ChannelError';
|
|
408
|
+
Object.setPrototypeOf(this, ChannelError.prototype);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
exports.ChannelError = ChannelError;
|
|
412
|
+
/**
|
|
413
|
+
* Raised when a Subscribe / Unsubscribe request is rejected because
|
|
414
|
+
* the subscriber isn't authorized on the publisher's channel config.
|
|
415
|
+
*/
|
|
416
|
+
class ChannelAuthError extends ChannelError {
|
|
417
|
+
constructor(detail) {
|
|
418
|
+
super(detail ?? 'channel: unauthorized');
|
|
419
|
+
this.name = 'ChannelAuthError';
|
|
420
|
+
Object.setPrototypeOf(this, ChannelAuthError.prototype);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
exports.ChannelAuthError = ChannelAuthError;
|
|
424
|
+
function toChannelError(e) {
|
|
425
|
+
const msg = e?.message ?? '';
|
|
426
|
+
if (msg.startsWith('channel: unauthorized')) {
|
|
427
|
+
throw new ChannelAuthError(msg);
|
|
428
|
+
}
|
|
429
|
+
if (msg.startsWith('channel:')) {
|
|
430
|
+
throw new ChannelError(msg);
|
|
431
|
+
}
|
|
432
|
+
throw e;
|
|
433
|
+
}
|
package/dist/meshdb.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MeshDB SDK wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the MeshDB query layer from `@net-mesh/core` (the napi
|
|
5
|
+
* binding) so `@net-mesh/sdk` consumers can stay on the
|
|
6
|
+
* ergonomic SDK module instead of dropping into the napi package.
|
|
7
|
+
* Also imports the AsyncIterable shim from `@net-mesh/core/meshdb`
|
|
8
|
+
* once, so `for await (const row of stream)` works without the
|
|
9
|
+
* consumer having to remember the side-effect import.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import {
|
|
14
|
+
* InMemoryChainReader,
|
|
15
|
+
* MeshQuery,
|
|
16
|
+
* MeshQueryRunner,
|
|
17
|
+
* QueryBuilder,
|
|
18
|
+
* } from '@net-mesh/sdk/meshdb';
|
|
19
|
+
*
|
|
20
|
+
* const reader = new InMemoryChainReader();
|
|
21
|
+
* // …populate reader…
|
|
22
|
+
* const runner = new MeshQueryRunner(reader);
|
|
23
|
+
*
|
|
24
|
+
* const query = new QueryBuilder().fromOrigin(0n).build();
|
|
25
|
+
* const stream = await runner.execute(query);
|
|
26
|
+
* for await (const row of stream) {
|
|
27
|
+
* console.log(row.seq, row.payload);
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Result of {@link parseMeshDbErrorKind} — the structured error kind
|
|
33
|
+
* extracted from a MeshDB error envelope, plus the human-readable
|
|
34
|
+
* message stripped of the `<<meshdb-kind:...>>` prefix.
|
|
35
|
+
*/
|
|
36
|
+
export interface ParsedMeshDbError {
|
|
37
|
+
kind: string;
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Pull the structured error kind out of a MeshDB error message.
|
|
42
|
+
* The substrate wraps every typed error as
|
|
43
|
+
* `<<meshdb-kind:KIND>>MESSAGE`; this helper splits the two halves so
|
|
44
|
+
* callers can dispatch programmatically. Returns `null` when the
|
|
45
|
+
* envelope is absent (forward-compatibility — unknown error shapes
|
|
46
|
+
* pass through unchanged).
|
|
47
|
+
*
|
|
48
|
+
* Mirrors the napi-side helper at `@net-mesh/core/meshdb` so consumers
|
|
49
|
+
* don't have to reach across packages for the parser.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseMeshDbErrorKind(raw: string): ParsedMeshDbError | null;
|
|
52
|
+
/**
|
|
53
|
+
* Note on AsyncIterable: the napi-side `MeshQueryStream` ships with a
|
|
54
|
+
* separate side-effect module (`@net-mesh/core/meshdb`) that augments
|
|
55
|
+
* the prototype with `[Symbol.asyncIterator]`. To use
|
|
56
|
+
* `for await (const row of stream)` syntax, import it once at
|
|
57
|
+
* startup:
|
|
58
|
+
*
|
|
59
|
+
* ```typescript
|
|
60
|
+
* import '@net-mesh/core/meshdb';
|
|
61
|
+
* import { MeshQueryRunner } from '@net-mesh/sdk/meshdb';
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* Without the side-effect import the stream still works via the
|
|
65
|
+
* imperative `next()` / `toArray()` methods.
|
|
66
|
+
*/
|
|
67
|
+
export { InMemoryChainReader, MeshQuery, MeshQueryRunner, MeshQueryStream, QueryBuilder, } from '@net-mesh/core';
|
|
68
|
+
export type { AggregateResult, CachePolicy, ExecuteOptions, GroupKey, JoinedRow, LineageEntry, Predicate as MeshDbPredicate, ResultRow, WindowBoundary, } from '@net-mesh/core';
|
|
69
|
+
/**
|
|
70
|
+
* Disposable-friendly wrapper around `MeshQueryRunner` for callers
|
|
71
|
+
* who want explicit lifetime management via the TC39
|
|
72
|
+
* `using`/`Symbol.dispose` protocol. The underlying runner has no
|
|
73
|
+
* native close; this wrapper exists so consumers can pair it with
|
|
74
|
+
* other Disposable resources in a single `using` chain.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* using runner = new DisposableMeshQueryRunner(reader);
|
|
79
|
+
* const stream = await runner.runner.execute(query);
|
|
80
|
+
* // runner is released when the enclosing scope exits.
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare class DisposableMeshQueryRunner {
|
|
84
|
+
readonly runner: import('@net-mesh/core').MeshQueryRunner;
|
|
85
|
+
constructor(reader: import('@net-mesh/core').InMemoryChainReader);
|
|
86
|
+
[Symbol.dispose](): void;
|
|
87
|
+
}
|
package/dist/meshdb.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MeshDB SDK wrapper.
|
|
4
|
+
*
|
|
5
|
+
* Re-exports the MeshDB query layer from `@net-mesh/core` (the napi
|
|
6
|
+
* binding) so `@net-mesh/sdk` consumers can stay on the
|
|
7
|
+
* ergonomic SDK module instead of dropping into the napi package.
|
|
8
|
+
* Also imports the AsyncIterable shim from `@net-mesh/core/meshdb`
|
|
9
|
+
* once, so `for await (const row of stream)` works without the
|
|
10
|
+
* consumer having to remember the side-effect import.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import {
|
|
15
|
+
* InMemoryChainReader,
|
|
16
|
+
* MeshQuery,
|
|
17
|
+
* MeshQueryRunner,
|
|
18
|
+
* QueryBuilder,
|
|
19
|
+
* } from '@net-mesh/sdk/meshdb';
|
|
20
|
+
*
|
|
21
|
+
* const reader = new InMemoryChainReader();
|
|
22
|
+
* // …populate reader…
|
|
23
|
+
* const runner = new MeshQueryRunner(reader);
|
|
24
|
+
*
|
|
25
|
+
* const query = new QueryBuilder().fromOrigin(0n).build();
|
|
26
|
+
* const stream = await runner.execute(query);
|
|
27
|
+
* for await (const row of stream) {
|
|
28
|
+
* console.log(row.seq, row.payload);
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.DisposableMeshQueryRunner = exports.QueryBuilder = exports.MeshQueryStream = exports.MeshQueryRunner = exports.MeshQuery = exports.InMemoryChainReader = void 0;
|
|
34
|
+
exports.parseMeshDbErrorKind = parseMeshDbErrorKind;
|
|
35
|
+
/**
|
|
36
|
+
* Pull the structured error kind out of a MeshDB error message.
|
|
37
|
+
* The substrate wraps every typed error as
|
|
38
|
+
* `<<meshdb-kind:KIND>>MESSAGE`; this helper splits the two halves so
|
|
39
|
+
* callers can dispatch programmatically. Returns `null` when the
|
|
40
|
+
* envelope is absent (forward-compatibility — unknown error shapes
|
|
41
|
+
* pass through unchanged).
|
|
42
|
+
*
|
|
43
|
+
* Mirrors the napi-side helper at `@net-mesh/core/meshdb` so consumers
|
|
44
|
+
* don't have to reach across packages for the parser.
|
|
45
|
+
*/
|
|
46
|
+
function parseMeshDbErrorKind(raw) {
|
|
47
|
+
const marker = '<<meshdb-kind:';
|
|
48
|
+
const start = raw.indexOf(marker);
|
|
49
|
+
if (start === -1)
|
|
50
|
+
return null;
|
|
51
|
+
const end = raw.indexOf('>>', start + marker.length);
|
|
52
|
+
if (end === -1)
|
|
53
|
+
return null;
|
|
54
|
+
const kind = raw.slice(start + marker.length, end);
|
|
55
|
+
const message = raw.slice(end + 2);
|
|
56
|
+
return { kind, message };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Note on AsyncIterable: the napi-side `MeshQueryStream` ships with a
|
|
60
|
+
* separate side-effect module (`@net-mesh/core/meshdb`) that augments
|
|
61
|
+
* the prototype with `[Symbol.asyncIterator]`. To use
|
|
62
|
+
* `for await (const row of stream)` syntax, import it once at
|
|
63
|
+
* startup:
|
|
64
|
+
*
|
|
65
|
+
* ```typescript
|
|
66
|
+
* import '@net-mesh/core/meshdb';
|
|
67
|
+
* import { MeshQueryRunner } from '@net-mesh/sdk/meshdb';
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* Without the side-effect import the stream still works via the
|
|
71
|
+
* imperative `next()` / `toArray()` methods.
|
|
72
|
+
*/
|
|
73
|
+
// Class re-exports — generated by napi-rs; identity-stable across
|
|
74
|
+
// imports of the same loaded module.
|
|
75
|
+
var core_1 = require("@net-mesh/core");
|
|
76
|
+
Object.defineProperty(exports, "InMemoryChainReader", { enumerable: true, get: function () { return core_1.InMemoryChainReader; } });
|
|
77
|
+
Object.defineProperty(exports, "MeshQuery", { enumerable: true, get: function () { return core_1.MeshQuery; } });
|
|
78
|
+
Object.defineProperty(exports, "MeshQueryRunner", { enumerable: true, get: function () { return core_1.MeshQueryRunner; } });
|
|
79
|
+
Object.defineProperty(exports, "MeshQueryStream", { enumerable: true, get: function () { return core_1.MeshQueryStream; } });
|
|
80
|
+
Object.defineProperty(exports, "QueryBuilder", { enumerable: true, get: function () { return core_1.QueryBuilder; } });
|
|
81
|
+
/**
|
|
82
|
+
* Disposable-friendly wrapper around `MeshQueryRunner` for callers
|
|
83
|
+
* who want explicit lifetime management via the TC39
|
|
84
|
+
* `using`/`Symbol.dispose` protocol. The underlying runner has no
|
|
85
|
+
* native close; this wrapper exists so consumers can pair it with
|
|
86
|
+
* other Disposable resources in a single `using` chain.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* using runner = new DisposableMeshQueryRunner(reader);
|
|
91
|
+
* const stream = await runner.runner.execute(query);
|
|
92
|
+
* // runner is released when the enclosing scope exits.
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
class DisposableMeshQueryRunner {
|
|
96
|
+
// Lazily-typed because the napi runner has no exported instance shape
|
|
97
|
+
// we can name without importing the value (and the value import is
|
|
98
|
+
// already covered above).
|
|
99
|
+
runner;
|
|
100
|
+
constructor(reader) {
|
|
101
|
+
const Runner = require('@net-mesh/core').MeshQueryRunner;
|
|
102
|
+
this.runner = new Runner(reader);
|
|
103
|
+
}
|
|
104
|
+
[Symbol.dispose]() {
|
|
105
|
+
// Runner has no native close — drop the reference so GC can
|
|
106
|
+
// collect it deterministically once the using-block exits.
|
|
107
|
+
// The reader retains its own Arc clone of the chain.
|
|
108
|
+
this.runner = undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.DisposableMeshQueryRunner = DisposableMeshQueryRunner;
|