@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.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
+ }
@@ -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;