@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/meshos.d.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MeshOS daemon-author SDK — TypeScript wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Sits on top of the napi-rs binding at `@net-mesh/core`. Adds:
|
|
5
|
+
*
|
|
6
|
+
* - {@link MeshOsDaemon} interface for daemon implementors.
|
|
7
|
+
* - Typed {@link DaemonControl} / {@link MaintenanceState} unions
|
|
8
|
+
* the binding emits as plain POJOs.
|
|
9
|
+
* - {@link MeshOsSdkError} typed Error subclass that parses the
|
|
10
|
+
* substrate `<<meshos-sdk-kind:KIND>>MSG` envelope into a
|
|
11
|
+
* structured `.kind` field.
|
|
12
|
+
* - `AsyncIterable<DaemonControl>` over the raw `nextControl()`
|
|
13
|
+
* napi method.
|
|
14
|
+
*
|
|
15
|
+
* Slice 1 (Phase 3) ships: `start` / `registerDaemon` /
|
|
16
|
+
* `nextControl` / `tryNextControl` / `publishLog` /
|
|
17
|
+
* `gracefulShutdown` / `metadata` / `refreshMetadata`. Capability
|
|
18
|
+
* publishing is a substrate-side stub (`publishCapabilities`
|
|
19
|
+
* returns without committing). Snapshot / restore work via the
|
|
20
|
+
* daemon object's optional methods; the supervisor invokes them
|
|
21
|
+
* on migration.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { Identity } from '@net-mesh/core';
|
|
26
|
+
* import { MeshOsDaemonSdk, type MeshOsDaemon } from '@net-mesh/sdk/meshos';
|
|
27
|
+
*
|
|
28
|
+
* const daemon: MeshOsDaemon = {
|
|
29
|
+
* name: 'echo',
|
|
30
|
+
* process: (event) => [event.payload],
|
|
31
|
+
* };
|
|
32
|
+
*
|
|
33
|
+
* const sdk = MeshOsDaemonSdk.start();
|
|
34
|
+
* const handle = sdk.registerDaemon(daemon, Identity.generate());
|
|
35
|
+
* handle.publishLog('info', 'started');
|
|
36
|
+
*
|
|
37
|
+
* for await (const ev of handle.controlEvents()) {
|
|
38
|
+
* if (ev.kind === 'Shutdown') break;
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* await handle.gracefulShutdown(5_000n);
|
|
42
|
+
* await sdk.shutdown();
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
import { MeshOsDaemonHandle as NapiHandle, type CausalEventJs, type Identity, type CapabilitySetJs } from '@net-mesh/core';
|
|
46
|
+
/**
|
|
47
|
+
* Typed error raised by the MeshOS SDK. The Rust side embeds a
|
|
48
|
+
* `<<meshos-sdk-kind:KIND>>MSG` envelope in the message; this
|
|
49
|
+
* class parses the kind out so callers can branch programmatically
|
|
50
|
+
* instead of regexing the message.
|
|
51
|
+
*/
|
|
52
|
+
export declare class MeshOsSdkError extends Error {
|
|
53
|
+
readonly kind: string;
|
|
54
|
+
constructor(kind: string, message: string);
|
|
55
|
+
/**
|
|
56
|
+
* Parse the envelope on a caught napi error. Returns the typed
|
|
57
|
+
* subclass when the envelope is present; falls through to the
|
|
58
|
+
* original error otherwise.
|
|
59
|
+
*/
|
|
60
|
+
static fromCaught(err: unknown): MeshOsSdkError | Error;
|
|
61
|
+
}
|
|
62
|
+
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
|
63
|
+
export type DaemonControlShutdown = {
|
|
64
|
+
kind: 'Shutdown';
|
|
65
|
+
gracePeriodMs: bigint;
|
|
66
|
+
};
|
|
67
|
+
export type DaemonControlDrainStart = {
|
|
68
|
+
kind: 'DrainStart';
|
|
69
|
+
gracePeriodMs: bigint;
|
|
70
|
+
};
|
|
71
|
+
export type DaemonControlDrainFinish = {
|
|
72
|
+
kind: 'DrainFinish';
|
|
73
|
+
};
|
|
74
|
+
export type DaemonControlBackpressureOn = {
|
|
75
|
+
kind: 'BackpressureOn';
|
|
76
|
+
level: number;
|
|
77
|
+
};
|
|
78
|
+
export type DaemonControlBackpressureOff = {
|
|
79
|
+
kind: 'BackpressureOff';
|
|
80
|
+
};
|
|
81
|
+
export type DaemonControlUnknown = {
|
|
82
|
+
kind: 'Unknown';
|
|
83
|
+
};
|
|
84
|
+
export type DaemonControl = DaemonControlShutdown | DaemonControlDrainStart | DaemonControlDrainFinish | DaemonControlBackpressureOn | DaemonControlBackpressureOff | DaemonControlUnknown;
|
|
85
|
+
export type MaintenanceStateActive = {
|
|
86
|
+
kind: 'Active';
|
|
87
|
+
};
|
|
88
|
+
export type MaintenanceStateEntering = {
|
|
89
|
+
kind: 'EnteringMaintenance';
|
|
90
|
+
sinceMs: bigint;
|
|
91
|
+
deadlineRemainingMs: bigint | null;
|
|
92
|
+
};
|
|
93
|
+
export type MaintenanceStateSteady = {
|
|
94
|
+
kind: 'Maintenance';
|
|
95
|
+
sinceMs: bigint;
|
|
96
|
+
};
|
|
97
|
+
export type MaintenanceStateExiting = {
|
|
98
|
+
kind: 'ExitingMaintenance';
|
|
99
|
+
sinceMs: bigint;
|
|
100
|
+
};
|
|
101
|
+
export type MaintenanceStateDrainFailed = {
|
|
102
|
+
kind: 'DrainFailed';
|
|
103
|
+
sinceMs: bigint;
|
|
104
|
+
reason: string;
|
|
105
|
+
};
|
|
106
|
+
export type MaintenanceStateRecovery = {
|
|
107
|
+
kind: 'Recovery';
|
|
108
|
+
sinceMs: bigint;
|
|
109
|
+
};
|
|
110
|
+
export type MaintenanceStateUnknown = {
|
|
111
|
+
kind: 'Unknown';
|
|
112
|
+
};
|
|
113
|
+
export type MaintenanceState = MaintenanceStateActive | MaintenanceStateEntering | MaintenanceStateSteady | MaintenanceStateExiting | MaintenanceStateDrainFailed | MaintenanceStateRecovery | MaintenanceStateUnknown;
|
|
114
|
+
export type PeerHealth = 'Healthy' | 'Degraded' | 'Unreachable' | 'Unknown';
|
|
115
|
+
export type PeerMaintenance = 'Active' | 'EnteringMaintenance' | 'Maintenance' | 'ExitingMaintenance' | 'DrainFailed' | 'Recovery' | 'Unknown';
|
|
116
|
+
export interface PeerSnapshot {
|
|
117
|
+
rttMs: bigint | null;
|
|
118
|
+
health: PeerHealth | null;
|
|
119
|
+
maintenance: PeerMaintenance | null;
|
|
120
|
+
cpuLoad1m: number | null;
|
|
121
|
+
memUsedBytes: bigint | null;
|
|
122
|
+
memTotalBytes: bigint | null;
|
|
123
|
+
diskUsedBytes: bigint | null;
|
|
124
|
+
diskTotalBytes: bigint | null;
|
|
125
|
+
saturationTrend: number | null;
|
|
126
|
+
capabilitySet: string[];
|
|
127
|
+
softwareVersion: string | null;
|
|
128
|
+
forkedFrom: bigint | null;
|
|
129
|
+
}
|
|
130
|
+
export interface MetadataView {
|
|
131
|
+
nodeId: bigint;
|
|
132
|
+
daemonId: bigint;
|
|
133
|
+
daemonName: string;
|
|
134
|
+
maintenanceState: MaintenanceState;
|
|
135
|
+
/** Keyed by peer node id. The binding emits a list of entries
|
|
136
|
+
* so BigInt keys round-trip cleanly; we materialize a Map here. */
|
|
137
|
+
peers: Map<bigint, PeerSnapshot>;
|
|
138
|
+
}
|
|
139
|
+
export interface CausalEvent {
|
|
140
|
+
originHash: bigint;
|
|
141
|
+
sequence: bigint;
|
|
142
|
+
payload: Buffer;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Daemon health envelope. Either a plain string discriminator
|
|
146
|
+
* (`'healthy'` / `'degraded'` / `'unhealthy'`) or an object form
|
|
147
|
+
* carrying an optional `reason` that rides into the substrate's
|
|
148
|
+
* `Degraded { reason }` variant.
|
|
149
|
+
*/
|
|
150
|
+
export type DaemonHealth = 'healthy' | 'degraded' | 'unhealthy' | {
|
|
151
|
+
kind: 'healthy' | 'degraded' | 'unhealthy';
|
|
152
|
+
reason?: string;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Capability tag advertisement. Mirrors the `requiredCapabilities`
|
|
156
|
+
* / `optionalCapabilities` slot consumed by the substrate's
|
|
157
|
+
* placement filter. Either a static `string[]` or a `() => string[]`
|
|
158
|
+
* callable resolved once at registration time.
|
|
159
|
+
*/
|
|
160
|
+
export type CapabilityAdvert = string[] | (() => string[]);
|
|
161
|
+
/**
|
|
162
|
+
* A MeshOS-supervised daemon. Required: `name` (string property) +
|
|
163
|
+
* `process(event)` (returns `Buffer[]` per inbound event, or `[]`
|
|
164
|
+
* for sinks).
|
|
165
|
+
*
|
|
166
|
+
* Optional methods (the substrate falls back to defaults if
|
|
167
|
+
* absent):
|
|
168
|
+
* - `snapshot()` — returns the daemon's serialized state, or
|
|
169
|
+
* `null` when stateless.
|
|
170
|
+
* - `restore(state: Buffer)` — re-seed state at migration target.
|
|
171
|
+
* - `onControl(event: DaemonControl)` — trait callback; the SDK
|
|
172
|
+
* *also* delivers the same event over `controlEvents()`. Use
|
|
173
|
+
* whichever delivery model fits.
|
|
174
|
+
* - `health()` — return liveness state; default `'healthy'`.
|
|
175
|
+
* - `saturation()` — return load factor in `[0.0, 1.0]`; default 0.
|
|
176
|
+
*
|
|
177
|
+
* Optional properties:
|
|
178
|
+
* - `requiredCapabilities` / `optionalCapabilities` — static tag
|
|
179
|
+
* list or callable returning one. Resolved once at registration
|
|
180
|
+
* time and cached on the substrate side.
|
|
181
|
+
*/
|
|
182
|
+
export interface MeshOsDaemon {
|
|
183
|
+
name: string;
|
|
184
|
+
process(event: CausalEvent): Buffer[];
|
|
185
|
+
snapshot?(): Buffer | null;
|
|
186
|
+
restore?(state: Buffer): void;
|
|
187
|
+
onControl?(event: DaemonControl): void;
|
|
188
|
+
health?(): DaemonHealth;
|
|
189
|
+
saturation?(): number;
|
|
190
|
+
requiredCapabilities?: CapabilityAdvert;
|
|
191
|
+
optionalCapabilities?: CapabilityAdvert;
|
|
192
|
+
}
|
|
193
|
+
export interface MeshOsConfig {
|
|
194
|
+
thisNode?: bigint;
|
|
195
|
+
tickIntervalMs?: bigint;
|
|
196
|
+
eventQueueCapacity?: number;
|
|
197
|
+
actionQueueCapacity?: number;
|
|
198
|
+
}
|
|
199
|
+
export interface MeshOsDaemonSdkOptions {
|
|
200
|
+
/** Per-daemon control-channel capacity. Default 8 events. */
|
|
201
|
+
controlCapacity?: number;
|
|
202
|
+
/** Max time (ms) the bridge waits for a JS callback (`process`
|
|
203
|
+
* / `snapshot` / `restore` / `onControl`) to respond. Default
|
|
204
|
+
* 60_000. */
|
|
205
|
+
callbackTimeoutMs?: number;
|
|
206
|
+
}
|
|
207
|
+
export declare class MeshOsDaemonSdk {
|
|
208
|
+
private readonly raw;
|
|
209
|
+
private constructor();
|
|
210
|
+
/**
|
|
211
|
+
* Start the SDK with optional config + the substrate's
|
|
212
|
+
* `LoggingDispatcher`. Async so the napi-side factory runs in
|
|
213
|
+
* the napi tokio context (a nested local runtime would deadlock).
|
|
214
|
+
*/
|
|
215
|
+
static start(config?: MeshOsConfig, options?: MeshOsDaemonSdkOptions): Promise<MeshOsDaemonSdk>;
|
|
216
|
+
/**
|
|
217
|
+
* Register a daemon under the supplied identity. The daemon
|
|
218
|
+
* object must satisfy {@link MeshOsDaemon}; the binding eagerly
|
|
219
|
+
* resolves `process` (and optional `snapshot` / `restore` /
|
|
220
|
+
* `onControl`) into TSFNs at registration time, so a missing
|
|
221
|
+
* `process` raises immediately rather than later on the first
|
|
222
|
+
* event.
|
|
223
|
+
*/
|
|
224
|
+
registerDaemon(daemon: MeshOsDaemon, identity: Identity): Promise<MeshOsDaemonHandle>;
|
|
225
|
+
/** Diagnostic counter — total control events the router dropped
|
|
226
|
+
* across every registered daemon because a daemon's channel was
|
|
227
|
+
* full. */
|
|
228
|
+
droppedControlEvents(): Promise<bigint>;
|
|
229
|
+
/** Tear down the wrapped runtime. Subsequent calls throw
|
|
230
|
+
* `MeshOsSdkError(kind: "already_shutdown")`. */
|
|
231
|
+
shutdown(): Promise<void>;
|
|
232
|
+
}
|
|
233
|
+
export declare class MeshOsDaemonHandle {
|
|
234
|
+
private readonly raw;
|
|
235
|
+
constructor(raw: NapiHandle);
|
|
236
|
+
/** Substrate identifier (origin hash). Stable across the
|
|
237
|
+
* handle's lifetime, readable after shutdown. */
|
|
238
|
+
get daemonId(): bigint;
|
|
239
|
+
/** Daemon's `name` at registration. Readable after shutdown. */
|
|
240
|
+
get daemonName(): string;
|
|
241
|
+
/** Cached metadata view. Refresh via {@link refreshMetadata}. */
|
|
242
|
+
metadata(): Promise<MetadataView>;
|
|
243
|
+
/** Rebuild the metadata view from the runtime's latest snapshot. */
|
|
244
|
+
refreshMetadata(): Promise<MetadataView>;
|
|
245
|
+
/** Block until the next control event arrives, the runtime
|
|
246
|
+
* shuts down, or `timeoutMs` elapses. Resolves to the event or
|
|
247
|
+
* `null` on timeout / shutdown. */
|
|
248
|
+
nextControl(timeoutMs?: bigint): Promise<DaemonControl | null>;
|
|
249
|
+
/** Non-blocking control-event receive. Returns the next event
|
|
250
|
+
* or `null` if the channel is empty. */
|
|
251
|
+
tryNextControl(): Promise<DaemonControl | null>;
|
|
252
|
+
/** Async-iterable view over control events. Terminates when
|
|
253
|
+
* `gracefulShutdown` runs or the runtime exits. Use:
|
|
254
|
+
*
|
|
255
|
+
* ```ts
|
|
256
|
+
* for await (const ev of handle.controlEvents()) {
|
|
257
|
+
* if (ev.kind === 'Shutdown') break;
|
|
258
|
+
* }
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
controlEvents(): AsyncIterable<DaemonControl>;
|
|
262
|
+
/** Publish a log line tagged with this daemon's id. Non-blocking
|
|
263
|
+
* on the substrate side; the napi call is async because every
|
|
264
|
+
* handle method serializes on the inner mutex. Throws
|
|
265
|
+
* `MeshOsSdkError(kind: "queue_full" | "loop_closed")` when the
|
|
266
|
+
* substrate's log ring is saturated. */
|
|
267
|
+
publishLog(level: LogLevel, message: string): Promise<void>;
|
|
268
|
+
/** Publish (or update) the daemon's capability set. Slice 1 is
|
|
269
|
+
* a substrate-side stub — the call returns without committing. */
|
|
270
|
+
publishCapabilities(caps?: CapabilitySetJs | null): Promise<void>;
|
|
271
|
+
/** Drive a graceful shutdown. Sends
|
|
272
|
+
* `Shutdown { gracePeriodMs }` on the daemon's control channel,
|
|
273
|
+
* parks for `graceMs`, then unregisters. Consumes the handle —
|
|
274
|
+
* subsequent calls throw `already_shutdown`. */
|
|
275
|
+
gracefulShutdown(graceMs?: bigint): Promise<void>;
|
|
276
|
+
}
|
|
277
|
+
export type { CausalEventJs };
|
package/dist/meshos.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MeshOS daemon-author SDK — TypeScript wrapper.
|
|
4
|
+
*
|
|
5
|
+
* Sits on top of the napi-rs binding at `@net-mesh/core`. Adds:
|
|
6
|
+
*
|
|
7
|
+
* - {@link MeshOsDaemon} interface for daemon implementors.
|
|
8
|
+
* - Typed {@link DaemonControl} / {@link MaintenanceState} unions
|
|
9
|
+
* the binding emits as plain POJOs.
|
|
10
|
+
* - {@link MeshOsSdkError} typed Error subclass that parses the
|
|
11
|
+
* substrate `<<meshos-sdk-kind:KIND>>MSG` envelope into a
|
|
12
|
+
* structured `.kind` field.
|
|
13
|
+
* - `AsyncIterable<DaemonControl>` over the raw `nextControl()`
|
|
14
|
+
* napi method.
|
|
15
|
+
*
|
|
16
|
+
* Slice 1 (Phase 3) ships: `start` / `registerDaemon` /
|
|
17
|
+
* `nextControl` / `tryNextControl` / `publishLog` /
|
|
18
|
+
* `gracefulShutdown` / `metadata` / `refreshMetadata`. Capability
|
|
19
|
+
* publishing is a substrate-side stub (`publishCapabilities`
|
|
20
|
+
* returns without committing). Snapshot / restore work via the
|
|
21
|
+
* daemon object's optional methods; the supervisor invokes them
|
|
22
|
+
* on migration.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { Identity } from '@net-mesh/core';
|
|
27
|
+
* import { MeshOsDaemonSdk, type MeshOsDaemon } from '@net-mesh/sdk/meshos';
|
|
28
|
+
*
|
|
29
|
+
* const daemon: MeshOsDaemon = {
|
|
30
|
+
* name: 'echo',
|
|
31
|
+
* process: (event) => [event.payload],
|
|
32
|
+
* };
|
|
33
|
+
*
|
|
34
|
+
* const sdk = MeshOsDaemonSdk.start();
|
|
35
|
+
* const handle = sdk.registerDaemon(daemon, Identity.generate());
|
|
36
|
+
* handle.publishLog('info', 'started');
|
|
37
|
+
*
|
|
38
|
+
* for await (const ev of handle.controlEvents()) {
|
|
39
|
+
* if (ev.kind === 'Shutdown') break;
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* await handle.gracefulShutdown(5_000n);
|
|
43
|
+
* await sdk.shutdown();
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.MeshOsDaemonHandle = exports.MeshOsDaemonSdk = exports.MeshOsSdkError = void 0;
|
|
48
|
+
const core_1 = require("@net-mesh/core");
|
|
49
|
+
// ----------------------------------------------------------------------------
|
|
50
|
+
// Typed error envelope
|
|
51
|
+
// ----------------------------------------------------------------------------
|
|
52
|
+
/**
|
|
53
|
+
* Typed error raised by the MeshOS SDK. The Rust side embeds a
|
|
54
|
+
* `<<meshos-sdk-kind:KIND>>MSG` envelope in the message; this
|
|
55
|
+
* class parses the kind out so callers can branch programmatically
|
|
56
|
+
* instead of regexing the message.
|
|
57
|
+
*/
|
|
58
|
+
class MeshOsSdkError extends Error {
|
|
59
|
+
kind;
|
|
60
|
+
constructor(kind, message) {
|
|
61
|
+
super(`<<meshos-sdk-kind:${kind}>>${message}`);
|
|
62
|
+
this.name = 'MeshOsSdkError';
|
|
63
|
+
this.kind = kind;
|
|
64
|
+
Object.setPrototypeOf(this, MeshOsSdkError.prototype);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Parse the envelope on a caught napi error. Returns the typed
|
|
68
|
+
* subclass when the envelope is present; falls through to the
|
|
69
|
+
* original error otherwise.
|
|
70
|
+
*/
|
|
71
|
+
static fromCaught(err) {
|
|
72
|
+
if (err instanceof MeshOsSdkError)
|
|
73
|
+
return err;
|
|
74
|
+
if (!(err instanceof Error)) {
|
|
75
|
+
return new Error(String(err));
|
|
76
|
+
}
|
|
77
|
+
const parsed = parseEnvelope(err.message);
|
|
78
|
+
if (!parsed)
|
|
79
|
+
return err;
|
|
80
|
+
return new MeshOsSdkError(parsed.kind, parsed.body);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.MeshOsSdkError = MeshOsSdkError;
|
|
84
|
+
function parseEnvelope(message) {
|
|
85
|
+
const marker = '<<meshos-sdk-kind:';
|
|
86
|
+
const start = message.indexOf(marker);
|
|
87
|
+
if (start === -1)
|
|
88
|
+
return null;
|
|
89
|
+
const kindStart = start + marker.length;
|
|
90
|
+
const end = message.indexOf('>>', kindStart);
|
|
91
|
+
if (end === -1)
|
|
92
|
+
return null;
|
|
93
|
+
return { kind: message.slice(kindStart, end), body: message.slice(end + 2) };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Run an operation, rethrowing the envelope as a typed
|
|
97
|
+
* `MeshOsSdkError` whose `.kind` callers can branch on.
|
|
98
|
+
*/
|
|
99
|
+
function rethrow(fn) {
|
|
100
|
+
try {
|
|
101
|
+
return fn();
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
throw MeshOsSdkError.fromCaught(e);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function rethrowAsync(fn) {
|
|
108
|
+
try {
|
|
109
|
+
return await fn();
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
throw MeshOsSdkError.fromCaught(e);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// ----------------------------------------------------------------------------
|
|
116
|
+
// MeshOsDaemonSdk — entry point
|
|
117
|
+
// ----------------------------------------------------------------------------
|
|
118
|
+
class MeshOsDaemonSdk {
|
|
119
|
+
raw;
|
|
120
|
+
constructor(raw) {
|
|
121
|
+
this.raw = raw;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* @internal Accessor for sibling SDK modules (currently the
|
|
125
|
+
* Deck SDK's `DeckClient.fromMeshos`) that need to compose
|
|
126
|
+
* against the underlying napi handle. Not part of the public
|
|
127
|
+
* API; calling it from consumer code is unsupported.
|
|
128
|
+
*/
|
|
129
|
+
__rawNapiSdk() {
|
|
130
|
+
return this.raw;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Start the SDK with optional config + the substrate's
|
|
134
|
+
* `LoggingDispatcher`. Async so the napi-side factory runs in
|
|
135
|
+
* the napi tokio context (a nested local runtime would deadlock).
|
|
136
|
+
*/
|
|
137
|
+
static async start(config, options) {
|
|
138
|
+
return rethrowAsync(async () => {
|
|
139
|
+
const raw = await core_1.MeshOsDaemonSdk.start(config ? toNapiConfig(config) : undefined, options?.controlCapacity, options?.callbackTimeoutMs);
|
|
140
|
+
return new MeshOsDaemonSdk(raw);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Register a daemon under the supplied identity. The daemon
|
|
145
|
+
* object must satisfy {@link MeshOsDaemon}; the binding eagerly
|
|
146
|
+
* resolves `process` (and optional `snapshot` / `restore` /
|
|
147
|
+
* `onControl`) into TSFNs at registration time, so a missing
|
|
148
|
+
* `process` raises immediately rather than later on the first
|
|
149
|
+
* event.
|
|
150
|
+
*/
|
|
151
|
+
async registerDaemon(daemon, identity) {
|
|
152
|
+
return rethrowAsync(async () => {
|
|
153
|
+
// The napi `registerDaemon` accepts a plain object with the
|
|
154
|
+
// typed callable fields. Cast through `unknown` because the
|
|
155
|
+
// generated d.ts references an internal `DaemonObjectTsfns`
|
|
156
|
+
// type that isn't reified at the napi boundary.
|
|
157
|
+
const handle = await this.raw.registerDaemon(daemon, identity);
|
|
158
|
+
return new MeshOsDaemonHandle(handle);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/** Diagnostic counter — total control events the router dropped
|
|
162
|
+
* across every registered daemon because a daemon's channel was
|
|
163
|
+
* full. */
|
|
164
|
+
async droppedControlEvents() {
|
|
165
|
+
return rethrowAsync(() => this.raw.droppedControlEvents());
|
|
166
|
+
}
|
|
167
|
+
/** Tear down the wrapped runtime. Subsequent calls throw
|
|
168
|
+
* `MeshOsSdkError(kind: "already_shutdown")`. */
|
|
169
|
+
async shutdown() {
|
|
170
|
+
await rethrowAsync(() => this.raw.shutdown());
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.MeshOsDaemonSdk = MeshOsDaemonSdk;
|
|
174
|
+
// ----------------------------------------------------------------------------
|
|
175
|
+
// MeshOsDaemonHandle — per-daemon handle
|
|
176
|
+
// ----------------------------------------------------------------------------
|
|
177
|
+
class MeshOsDaemonHandle {
|
|
178
|
+
raw;
|
|
179
|
+
constructor(raw) {
|
|
180
|
+
this.raw = raw;
|
|
181
|
+
}
|
|
182
|
+
/** Substrate identifier (origin hash). Stable across the
|
|
183
|
+
* handle's lifetime, readable after shutdown. */
|
|
184
|
+
get daemonId() {
|
|
185
|
+
return this.raw.daemonId;
|
|
186
|
+
}
|
|
187
|
+
/** Daemon's `name` at registration. Readable after shutdown. */
|
|
188
|
+
get daemonName() {
|
|
189
|
+
return this.raw.daemonName;
|
|
190
|
+
}
|
|
191
|
+
/** Cached metadata view. Refresh via {@link refreshMetadata}. */
|
|
192
|
+
async metadata() {
|
|
193
|
+
return rethrowAsync(async () => fromNapiMetadata(await this.raw.metadata()));
|
|
194
|
+
}
|
|
195
|
+
/** Rebuild the metadata view from the runtime's latest snapshot. */
|
|
196
|
+
async refreshMetadata() {
|
|
197
|
+
return rethrowAsync(async () => fromNapiMetadata(await this.raw.refreshMetadata()));
|
|
198
|
+
}
|
|
199
|
+
/** Block until the next control event arrives, the runtime
|
|
200
|
+
* shuts down, or `timeoutMs` elapses. Resolves to the event or
|
|
201
|
+
* `null` on timeout / shutdown. */
|
|
202
|
+
async nextControl(timeoutMs) {
|
|
203
|
+
return rethrowAsync(async () => {
|
|
204
|
+
const ev = await this.raw.nextControl(timeoutMs);
|
|
205
|
+
return ev ? fromNapiControl(ev) : null;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/** Non-blocking control-event receive. Returns the next event
|
|
209
|
+
* or `null` if the channel is empty. */
|
|
210
|
+
async tryNextControl() {
|
|
211
|
+
return rethrowAsync(async () => {
|
|
212
|
+
const ev = await this.raw.tryNextControl();
|
|
213
|
+
return ev ? fromNapiControl(ev) : null;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/** Async-iterable view over control events. Terminates when
|
|
217
|
+
* `gracefulShutdown` runs or the runtime exits. Use:
|
|
218
|
+
*
|
|
219
|
+
* ```ts
|
|
220
|
+
* for await (const ev of handle.controlEvents()) {
|
|
221
|
+
* if (ev.kind === 'Shutdown') break;
|
|
222
|
+
* }
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
controlEvents() {
|
|
226
|
+
const handle = this;
|
|
227
|
+
return {
|
|
228
|
+
[Symbol.asyncIterator]() {
|
|
229
|
+
return {
|
|
230
|
+
async next() {
|
|
231
|
+
try {
|
|
232
|
+
const ev = await handle.nextControl();
|
|
233
|
+
if (ev === null)
|
|
234
|
+
return { value: undefined, done: true };
|
|
235
|
+
return { value: ev, done: false };
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
// After `gracefulShutdown` the napi handle throws
|
|
239
|
+
// `already_shutdown`. Terminate cleanly.
|
|
240
|
+
const err = MeshOsSdkError.fromCaught(e);
|
|
241
|
+
if (err instanceof MeshOsSdkError && err.kind === 'already_shutdown') {
|
|
242
|
+
return { value: undefined, done: true };
|
|
243
|
+
}
|
|
244
|
+
throw err;
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
async return() {
|
|
248
|
+
return { value: undefined, done: true };
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/** Publish a log line tagged with this daemon's id. Non-blocking
|
|
255
|
+
* on the substrate side; the napi call is async because every
|
|
256
|
+
* handle method serializes on the inner mutex. Throws
|
|
257
|
+
* `MeshOsSdkError(kind: "queue_full" | "loop_closed")` when the
|
|
258
|
+
* substrate's log ring is saturated. */
|
|
259
|
+
async publishLog(level, message) {
|
|
260
|
+
await rethrowAsync(() => this.raw.publishLog(level, message));
|
|
261
|
+
}
|
|
262
|
+
/** Publish (or update) the daemon's capability set. Slice 1 is
|
|
263
|
+
* a substrate-side stub — the call returns without committing. */
|
|
264
|
+
async publishCapabilities(caps) {
|
|
265
|
+
await rethrowAsync(() => this.raw.publishCapabilities(caps ?? null));
|
|
266
|
+
}
|
|
267
|
+
/** Drive a graceful shutdown. Sends
|
|
268
|
+
* `Shutdown { gracePeriodMs }` on the daemon's control channel,
|
|
269
|
+
* parks for `graceMs`, then unregisters. Consumes the handle —
|
|
270
|
+
* subsequent calls throw `already_shutdown`. */
|
|
271
|
+
async gracefulShutdown(graceMs) {
|
|
272
|
+
await rethrowAsync(() => this.raw.gracefulShutdown(graceMs));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
exports.MeshOsDaemonHandle = MeshOsDaemonHandle;
|
|
276
|
+
// ----------------------------------------------------------------------------
|
|
277
|
+
// POJO converters
|
|
278
|
+
// ----------------------------------------------------------------------------
|
|
279
|
+
function toNapiConfig(c) {
|
|
280
|
+
return {
|
|
281
|
+
thisNode: c.thisNode,
|
|
282
|
+
tickIntervalMs: c.tickIntervalMs,
|
|
283
|
+
eventQueueCapacity: c.eventQueueCapacity,
|
|
284
|
+
actionQueueCapacity: c.actionQueueCapacity,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function fromNapiControl(ev) {
|
|
288
|
+
switch (ev.kind) {
|
|
289
|
+
case 'Shutdown':
|
|
290
|
+
return { kind: 'Shutdown', gracePeriodMs: ev.gracePeriodMs ?? 0n };
|
|
291
|
+
case 'DrainStart':
|
|
292
|
+
return { kind: 'DrainStart', gracePeriodMs: ev.gracePeriodMs ?? 0n };
|
|
293
|
+
case 'DrainFinish':
|
|
294
|
+
return { kind: 'DrainFinish' };
|
|
295
|
+
case 'BackpressureOn':
|
|
296
|
+
return { kind: 'BackpressureOn', level: ev.level ?? 0 };
|
|
297
|
+
case 'BackpressureOff':
|
|
298
|
+
return { kind: 'BackpressureOff' };
|
|
299
|
+
default:
|
|
300
|
+
return { kind: 'Unknown' };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function fromNapiMaintenance(m) {
|
|
304
|
+
switch (m.kind) {
|
|
305
|
+
case 'Active':
|
|
306
|
+
return { kind: 'Active' };
|
|
307
|
+
case 'EnteringMaintenance':
|
|
308
|
+
return {
|
|
309
|
+
kind: 'EnteringMaintenance',
|
|
310
|
+
sinceMs: m.sinceMs ?? 0n,
|
|
311
|
+
deadlineRemainingMs: m.deadlineRemainingMs ?? null,
|
|
312
|
+
};
|
|
313
|
+
case 'Maintenance':
|
|
314
|
+
return { kind: 'Maintenance', sinceMs: m.sinceMs ?? 0n };
|
|
315
|
+
case 'ExitingMaintenance':
|
|
316
|
+
return { kind: 'ExitingMaintenance', sinceMs: m.sinceMs ?? 0n };
|
|
317
|
+
case 'DrainFailed':
|
|
318
|
+
return {
|
|
319
|
+
kind: 'DrainFailed',
|
|
320
|
+
sinceMs: m.sinceMs ?? 0n,
|
|
321
|
+
reason: m.reason ?? '',
|
|
322
|
+
};
|
|
323
|
+
case 'Recovery':
|
|
324
|
+
return { kind: 'Recovery', sinceMs: m.sinceMs ?? 0n };
|
|
325
|
+
default:
|
|
326
|
+
return { kind: 'Unknown' };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function fromNapiPeer(p) {
|
|
330
|
+
return {
|
|
331
|
+
rttMs: p.rttMs ?? null,
|
|
332
|
+
health: p.health ?? null,
|
|
333
|
+
maintenance: p.maintenance ?? null,
|
|
334
|
+
// napi-derive emits `cpu_load_1m` as `cpuLoad1M` (camelCase
|
|
335
|
+
// capitalizes digit-adjacent letters); accept that form.
|
|
336
|
+
cpuLoad1m: p.cpuLoad1M ?? null,
|
|
337
|
+
memUsedBytes: p.memUsedBytes ?? null,
|
|
338
|
+
memTotalBytes: p.memTotalBytes ?? null,
|
|
339
|
+
diskUsedBytes: p.diskUsedBytes ?? null,
|
|
340
|
+
diskTotalBytes: p.diskTotalBytes ?? null,
|
|
341
|
+
saturationTrend: p.saturationTrend ?? null,
|
|
342
|
+
capabilitySet: p.capabilitySet,
|
|
343
|
+
softwareVersion: p.softwareVersion ?? null,
|
|
344
|
+
forkedFrom: p.forkedFrom ?? null,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function fromNapiMetadata(v) {
|
|
348
|
+
const peers = new Map();
|
|
349
|
+
for (const entry of v.peers) {
|
|
350
|
+
peers.set(entry.nodeId, fromNapiPeer(entry.snapshot));
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
nodeId: v.nodeId,
|
|
354
|
+
daemonId: v.daemonId,
|
|
355
|
+
daemonName: v.daemonName,
|
|
356
|
+
maintenanceState: fromNapiMaintenance(v.maintenanceState),
|
|
357
|
+
peers,
|
|
358
|
+
};
|
|
359
|
+
}
|