@net-mesh/core 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 +45 -0
- package/errors.d.ts +62 -0
- package/errors.js +209 -0
- package/mesh_rpc.d.ts +297 -0
- package/mesh_rpc.js +780 -0
- package/net.darwin-arm64.node +0 -0
- package/net.darwin-x64.node +0 -0
- package/net.linux-arm64-gnu.node +0 -0
- package/net.linux-arm64-musl.node +0 -0
- package/net.linux-x64-gnu.node +0 -0
- package/net.linux-x64-musl.node +0 -0
- package/net.win32-arm64-msvc.node +0 -0
- package/net.win32-x64-msvc.node +0 -0
- package/package.json +95 -0
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @net-mesh/core
|
|
2
|
+
|
|
3
|
+
napi-rs binding to the `libnet` cdylib — the Node-facing surface for the Net mesh, RedEX, CortEX, NetDB, MeshDB, and MeshOS.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @net-mesh/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The package publishes pre-built `.node` artifacts for every platform listed in `package.json -> napi.targets`. Pulling the package from npm requires no Rust toolchain.
|
|
12
|
+
|
|
13
|
+
## Build from source
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install
|
|
17
|
+
npm run build # release; full feature set
|
|
18
|
+
npm run build:debug # debug; full feature set
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The canonical feature list lives in `package.json -> scripts.build` so CI artifacts and local builds stay aligned.
|
|
22
|
+
|
|
23
|
+
## Cargo features
|
|
24
|
+
|
|
25
|
+
The five feature flags that gate the storage / query / OS surfaces on this binding. Artifacts published to npm ship with every feature enabled; `napi build` invocations without a feature silently omit its symbols and the build never warns. The TypeScript wrapper destructures the napi exports lazily, so a missing feature surfaces as `undefined` at the import site rather than a load-time error.
|
|
26
|
+
|
|
27
|
+
| Feature | Surface enabled in the napi module |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `cortex` | `Redex`, `RedexFile`, `TasksAdapter`, `MemoriesAdapter`, `NetDb`, `Task`, `Memory`, watch iterators, `RedexError`, `CortexError`, `NetDbError` |
|
|
30
|
+
| `redex-disk` | Disk-backed RedEX persistence — the `persistentDir` ctor option and `persistent: true` on `openFile`. Without it the persistent path rejects with `RedexError`. |
|
|
31
|
+
| `netdb` | `NetDb` composition (requires `cortex`); the `net_netdb_*` FFI entry points ship with this feature. |
|
|
32
|
+
| `meshdb` | `MeshQuery`, `MeshQueryRunner`, `QueryBuilder`, `Predicate`, `InMemoryChainReader`, plus the `libnet_meshdb` cdylib. |
|
|
33
|
+
| `meshos` | `MeshOsDaemonSdk`, `MeshOsDaemonHandle`, plus the `libnet_meshos` cdylib. |
|
|
34
|
+
|
|
35
|
+
Enable at build time:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
napi build --platform --release --features "cortex netdb redex-disk meshdb meshos"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The repo's `npm run build` script already passes a superset of these flags (`redis,net,cortex,compute,groups,meshos,deck,meshdb`) — see `package.json -> scripts.build` for the exact invocation. Slim the list by editing that script or invoking `napi build` directly with the features you actually need.
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
Apache-2.0
|
package/errors.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export declare class CortexError extends Error {
|
|
2
|
+
constructor(detail?: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class NetDbError extends Error {
|
|
5
|
+
constructor(detail?: string);
|
|
6
|
+
}
|
|
7
|
+
export declare class RpcError extends Error {
|
|
8
|
+
constructor(detail?: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class RpcNoRouteError extends RpcError {
|
|
11
|
+
constructor(detail?: string);
|
|
12
|
+
}
|
|
13
|
+
export declare class RpcTimeoutError extends RpcError {
|
|
14
|
+
/** Caller-side elapsed time, parsed out of `elapsed_ms=N` in the message. */
|
|
15
|
+
readonly elapsedMs?: number;
|
|
16
|
+
constructor(detail?: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class RpcServerError extends RpcError {
|
|
19
|
+
/** Wire-level RpcStatus parsed out of `status=0xNNNN` in the message. */
|
|
20
|
+
readonly status?: number;
|
|
21
|
+
constructor(detail?: string);
|
|
22
|
+
}
|
|
23
|
+
export declare class RpcTransportError extends RpcError {
|
|
24
|
+
constructor(detail?: string);
|
|
25
|
+
}
|
|
26
|
+
export declare class RpcCodecError extends RpcError {
|
|
27
|
+
/** Which side of the call surfaced the codec failure. */
|
|
28
|
+
readonly direction?: 'encode' | 'decode';
|
|
29
|
+
constructor(detail?: string, direction?: 'encode' | 'decode');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Caller-driven cancellation. Raised when an in-flight unary
|
|
33
|
+
* call is aborted via `MeshRpc.cancelCall(token)` or via an
|
|
34
|
+
* AbortSignal attached through the typed wrapper's `opts.signal`.
|
|
35
|
+
* CANCEL has been published to the server; the server-side
|
|
36
|
+
* handler observes its `ctx.cancellation` token. Caller-fixable
|
|
37
|
+
* / terminal — NOT retried by the default retry policy.
|
|
38
|
+
*/
|
|
39
|
+
export declare class RpcCancelledError extends RpcError {
|
|
40
|
+
constructor(detail?: string);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Inspect an error's message prefix and return a typed error if it
|
|
44
|
+
* matches the napi binding's contract. Non-matching errors are
|
|
45
|
+
* returned unchanged — caller can `throw` the result unconditionally.
|
|
46
|
+
*
|
|
47
|
+
* Duck-typed on `e.message`: napi rejections are real Error
|
|
48
|
+
* instances, but top-level catch handlers may also see arbitrary
|
|
49
|
+
* non-Error values (a thrown string, a plain `{message}` object).
|
|
50
|
+
* We mirror the JS contract — accept anything with a string
|
|
51
|
+
* `message` field — so cross-runtime catch sites don't lose
|
|
52
|
+
* classification just because the throw happened in a context
|
|
53
|
+
* where `instanceof Error` is unreliable (vm boundaries, polyfills).
|
|
54
|
+
*/
|
|
55
|
+
export declare function classifyError(e: unknown): unknown;
|
|
56
|
+
/**
|
|
57
|
+
* Pull a `message` field off any value, with the same permissive
|
|
58
|
+
* semantics as `(e && e.message) || ''` from the JS source. Used
|
|
59
|
+
* by `classifyError` and by `defaultRetryable` in
|
|
60
|
+
* `@net-mesh/core/mesh_rpc` to keep the catch-site contract uniform.
|
|
61
|
+
*/
|
|
62
|
+
export declare function extractMessage(e: unknown): string;
|
package/errors.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Typed error classes for CortEX / NetDB / nRPC operations.
|
|
3
|
+
//
|
|
4
|
+
// The napi binding throws plain `Error` objects with stable prefixes
|
|
5
|
+
// (`cortex:` / `netdb:` / `nrpc:`) that `classifyError()` inspects to
|
|
6
|
+
// re-throw a typed error. Catch with `instanceof`:
|
|
7
|
+
//
|
|
8
|
+
// import { NetDb } from '@net-mesh/core';
|
|
9
|
+
// import { CortexError, classifyError } from '@net-mesh/core/errors';
|
|
10
|
+
//
|
|
11
|
+
// try {
|
|
12
|
+
// db.tasks.create(1n, 'x', 100n);
|
|
13
|
+
// } catch (e) {
|
|
14
|
+
// throw classifyError(e); // CortexError / NetDbError / original
|
|
15
|
+
// }
|
|
16
|
+
//
|
|
17
|
+
// Prefixes mirror `ERR_*_PREFIX` in `bindings/node/src/cortex.rs`
|
|
18
|
+
// and `bindings/node/src/mesh_rpc.rs`. Keep the strings in lockstep.
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.RpcCancelledError = exports.RpcCodecError = exports.RpcTransportError = exports.RpcServerError = exports.RpcTimeoutError = exports.RpcNoRouteError = exports.RpcError = exports.NetDbError = exports.CortexError = void 0;
|
|
21
|
+
exports.classifyError = classifyError;
|
|
22
|
+
exports.extractMessage = extractMessage;
|
|
23
|
+
const ERR_CORTEX_PREFIX = 'cortex:';
|
|
24
|
+
const ERR_NETDB_PREFIX = 'netdb:';
|
|
25
|
+
const ERR_NRPC_PREFIX = 'nrpc:';
|
|
26
|
+
class CortexError extends Error {
|
|
27
|
+
constructor(detail) {
|
|
28
|
+
super(detail ?? 'cortex adapter error');
|
|
29
|
+
this.name = 'CortexError';
|
|
30
|
+
Object.setPrototypeOf(this, CortexError.prototype);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.CortexError = CortexError;
|
|
34
|
+
class NetDbError extends Error {
|
|
35
|
+
constructor(detail) {
|
|
36
|
+
super(detail ?? 'netdb error');
|
|
37
|
+
this.name = 'NetDbError';
|
|
38
|
+
Object.setPrototypeOf(this, NetDbError.prototype);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.NetDbError = NetDbError;
|
|
42
|
+
// nRPC error hierarchy. Mirrors net::adapter::net::mesh_rpc::RpcError;
|
|
43
|
+
// the napi binding's mesh_rpc.rs::nrpc_err_from_inner emits each
|
|
44
|
+
// variant under a stable kind segment after the `nrpc:` prefix:
|
|
45
|
+
//
|
|
46
|
+
// nrpc:no_route -> RpcNoRouteError
|
|
47
|
+
// nrpc:timeout -> RpcTimeoutError
|
|
48
|
+
// nrpc:server_error -> RpcServerError
|
|
49
|
+
// nrpc:transport -> RpcTransportError
|
|
50
|
+
// nrpc:codec_encode -> RpcCodecError(direction='encode')
|
|
51
|
+
// nrpc:codec_decode -> RpcCodecError(direction='decode')
|
|
52
|
+
// nrpc:cancelled -> RpcCancelledError
|
|
53
|
+
// nrpc:* (anything else) -> RpcError (the base class)
|
|
54
|
+
//
|
|
55
|
+
// Catch with `instanceof RpcError` for "any nRPC failure", or
|
|
56
|
+
// drill down to a concrete subclass for specific handling. The
|
|
57
|
+
// default retry / circuit-breaker policies in @net-mesh/core/mesh_rpc
|
|
58
|
+
// skip RpcCodecError (caller-fixable local bug) by default — same
|
|
59
|
+
// behavior as the Rust SDK's default_retryable predicate.
|
|
60
|
+
class RpcError extends Error {
|
|
61
|
+
constructor(detail) {
|
|
62
|
+
super(detail ?? 'rpc error');
|
|
63
|
+
this.name = 'RpcError';
|
|
64
|
+
Object.setPrototypeOf(this, RpcError.prototype);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.RpcError = RpcError;
|
|
68
|
+
class RpcNoRouteError extends RpcError {
|
|
69
|
+
constructor(detail) {
|
|
70
|
+
super(detail ?? 'no route to target');
|
|
71
|
+
this.name = 'RpcNoRouteError';
|
|
72
|
+
Object.setPrototypeOf(this, RpcNoRouteError.prototype);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.RpcNoRouteError = RpcNoRouteError;
|
|
76
|
+
class RpcTimeoutError extends RpcError {
|
|
77
|
+
constructor(detail) {
|
|
78
|
+
super(detail ?? 'rpc timeout');
|
|
79
|
+
this.name = 'RpcTimeoutError';
|
|
80
|
+
Object.setPrototypeOf(this, RpcTimeoutError.prototype);
|
|
81
|
+
// Best-effort parse of `elapsed_ms=N` so callers can read
|
|
82
|
+
// `err.elapsedMs` without re-parsing the message.
|
|
83
|
+
const m = /elapsed_ms=(\d+)/.exec(detail ?? '');
|
|
84
|
+
if (m)
|
|
85
|
+
this.elapsedMs = Number(m[1]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.RpcTimeoutError = RpcTimeoutError;
|
|
89
|
+
class RpcServerError extends RpcError {
|
|
90
|
+
constructor(detail) {
|
|
91
|
+
super(detail ?? 'rpc server error');
|
|
92
|
+
this.name = 'RpcServerError';
|
|
93
|
+
Object.setPrototypeOf(this, RpcServerError.prototype);
|
|
94
|
+
// Parse `status=0xNNNN` so callers can pattern-match by
|
|
95
|
+
// status code (e.g. err.status === 0x8001 → typed-handler
|
|
96
|
+
// application error).
|
|
97
|
+
const m = /status=0x([0-9a-fA-F]+)/.exec(detail ?? '');
|
|
98
|
+
if (m)
|
|
99
|
+
this.status = parseInt(m[1], 16);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.RpcServerError = RpcServerError;
|
|
103
|
+
class RpcTransportError extends RpcError {
|
|
104
|
+
constructor(detail) {
|
|
105
|
+
super(detail ?? 'rpc transport error');
|
|
106
|
+
this.name = 'RpcTransportError';
|
|
107
|
+
Object.setPrototypeOf(this, RpcTransportError.prototype);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.RpcTransportError = RpcTransportError;
|
|
111
|
+
class RpcCodecError extends RpcError {
|
|
112
|
+
/** Which side of the call surfaced the codec failure. */
|
|
113
|
+
direction;
|
|
114
|
+
constructor(detail, direction) {
|
|
115
|
+
super(detail ?? 'rpc codec error');
|
|
116
|
+
this.name = 'RpcCodecError';
|
|
117
|
+
// `direction` is always passed through, so it's a regular
|
|
118
|
+
// assignment (no `declare`-dance needed — `'direction' in err`
|
|
119
|
+
// is `true` regardless of whether a specific value was provided).
|
|
120
|
+
this.direction = direction;
|
|
121
|
+
Object.setPrototypeOf(this, RpcCodecError.prototype);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.RpcCodecError = RpcCodecError;
|
|
125
|
+
/**
|
|
126
|
+
* Caller-driven cancellation. Raised when an in-flight unary
|
|
127
|
+
* call is aborted via `MeshRpc.cancelCall(token)` or via an
|
|
128
|
+
* AbortSignal attached through the typed wrapper's `opts.signal`.
|
|
129
|
+
* CANCEL has been published to the server; the server-side
|
|
130
|
+
* handler observes its `ctx.cancellation` token. Caller-fixable
|
|
131
|
+
* / terminal — NOT retried by the default retry policy.
|
|
132
|
+
*/
|
|
133
|
+
class RpcCancelledError extends RpcError {
|
|
134
|
+
constructor(detail) {
|
|
135
|
+
super(detail ?? 'rpc cancelled');
|
|
136
|
+
this.name = 'RpcCancelledError';
|
|
137
|
+
Object.setPrototypeOf(this, RpcCancelledError.prototype);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.RpcCancelledError = RpcCancelledError;
|
|
141
|
+
/**
|
|
142
|
+
* Inspect an error's message prefix and return a typed error if it
|
|
143
|
+
* matches the napi binding's contract. Non-matching errors are
|
|
144
|
+
* returned unchanged — caller can `throw` the result unconditionally.
|
|
145
|
+
*
|
|
146
|
+
* Duck-typed on `e.message`: napi rejections are real Error
|
|
147
|
+
* instances, but top-level catch handlers may also see arbitrary
|
|
148
|
+
* non-Error values (a thrown string, a plain `{message}` object).
|
|
149
|
+
* We mirror the JS contract — accept anything with a string
|
|
150
|
+
* `message` field — so cross-runtime catch sites don't lose
|
|
151
|
+
* classification just because the throw happened in a context
|
|
152
|
+
* where `instanceof Error` is unreliable (vm boundaries, polyfills).
|
|
153
|
+
*/
|
|
154
|
+
function classifyError(e) {
|
|
155
|
+
const msg = extractMessage(e);
|
|
156
|
+
if (msg.startsWith(ERR_CORTEX_PREFIX)) {
|
|
157
|
+
return new CortexError(msg);
|
|
158
|
+
}
|
|
159
|
+
if (msg.startsWith(ERR_NETDB_PREFIX)) {
|
|
160
|
+
return new NetDbError(msg);
|
|
161
|
+
}
|
|
162
|
+
if (msg.startsWith(ERR_NRPC_PREFIX)) {
|
|
163
|
+
return classifyRpcError(msg);
|
|
164
|
+
}
|
|
165
|
+
return e;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Pull a `message` field off any value, with the same permissive
|
|
169
|
+
* semantics as `(e && e.message) || ''` from the JS source. Used
|
|
170
|
+
* by `classifyError` and by `defaultRetryable` in
|
|
171
|
+
* `@net-mesh/core/mesh_rpc` to keep the catch-site contract uniform.
|
|
172
|
+
*/
|
|
173
|
+
function extractMessage(e) {
|
|
174
|
+
if (e === null || e === undefined)
|
|
175
|
+
return '';
|
|
176
|
+
if (typeof e === 'string')
|
|
177
|
+
return e;
|
|
178
|
+
if (typeof e !== 'object')
|
|
179
|
+
return '';
|
|
180
|
+
const msg = e.message;
|
|
181
|
+
return typeof msg === 'string' ? msg : '';
|
|
182
|
+
}
|
|
183
|
+
function classifyRpcError(msg) {
|
|
184
|
+
// Strip the `nrpc:` prefix; the next segment up to the first
|
|
185
|
+
// `:` is the kind. Examples:
|
|
186
|
+
// nrpc:no_route: target=0x... reason=...
|
|
187
|
+
// nrpc:codec_encode: client encode: ...
|
|
188
|
+
const after = msg.slice(ERR_NRPC_PREFIX.length);
|
|
189
|
+
const colonIdx = after.indexOf(':');
|
|
190
|
+
const kind = colonIdx === -1 ? after : after.slice(0, colonIdx);
|
|
191
|
+
switch (kind) {
|
|
192
|
+
case 'no_route':
|
|
193
|
+
return new RpcNoRouteError(msg);
|
|
194
|
+
case 'timeout':
|
|
195
|
+
return new RpcTimeoutError(msg);
|
|
196
|
+
case 'server_error':
|
|
197
|
+
return new RpcServerError(msg);
|
|
198
|
+
case 'transport':
|
|
199
|
+
return new RpcTransportError(msg);
|
|
200
|
+
case 'codec_encode':
|
|
201
|
+
return new RpcCodecError(msg, 'encode');
|
|
202
|
+
case 'codec_decode':
|
|
203
|
+
return new RpcCodecError(msg, 'decode');
|
|
204
|
+
case 'cancelled':
|
|
205
|
+
return new RpcCancelledError(msg);
|
|
206
|
+
default:
|
|
207
|
+
return new RpcError(msg);
|
|
208
|
+
}
|
|
209
|
+
}
|
package/mesh_rpc.d.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-call options forwarded to the raw napi `MeshRpc`. Mirrors
|
|
3
|
+
* the napi `CallOptions` struct. Routing policy + trace context
|
|
4
|
+
* land in a later phase.
|
|
5
|
+
*/
|
|
6
|
+
export interface CallOptions {
|
|
7
|
+
/** Hard deadline, milliseconds from now. */
|
|
8
|
+
deadlineMs?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Streaming-only: initial credit window for per-streaming-
|
|
11
|
+
* response flow control. `undefined` → unbounded.
|
|
12
|
+
*/
|
|
13
|
+
streamWindowInitial?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Caller-driven cancellation. Pass an `AbortSignal`; the typed
|
|
16
|
+
* wrapper attaches a one-shot listener that aborts the in-
|
|
17
|
+
* flight call. The call rejects with `RpcCancelledError`.
|
|
18
|
+
* Recognized by `TypedMeshRpc` only — the raw napi `MeshRpc`
|
|
19
|
+
* uses `cancelToken` directly.
|
|
20
|
+
*/
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
/**
|
|
23
|
+
* Raw cancel token (advanced; usually set automatically by the
|
|
24
|
+
* typed wrapper from `signal`). Mint via
|
|
25
|
+
* `MeshRpc.reserveCancelToken()` and pair with
|
|
26
|
+
* `MeshRpc.cancelCall(token)`. Most users should use `signal`
|
|
27
|
+
* instead.
|
|
28
|
+
*/
|
|
29
|
+
cancelToken?: bigint;
|
|
30
|
+
/**
|
|
31
|
+
* Caller-supplied request headers, appended to the wire
|
|
32
|
+
* `RpcRequestPayload.headers`. Used for application-level metadata
|
|
33
|
+
* the server needs at dispatch-time — most notably the
|
|
34
|
+
* `net-where:` predicate header for Phase 9b
|
|
35
|
+
* predicate-pushdown filtering.
|
|
36
|
+
*
|
|
37
|
+
* Convenience: build entries via `whereHeader(pred)` from
|
|
38
|
+
* `@net-mesh/sdk` for predicate-pushdown.
|
|
39
|
+
*/
|
|
40
|
+
requestHeaders?: RpcRequestHeader[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Single request-header entry. Names follow the lowercase
|
|
44
|
+
* `cyberdeck-*` / `nrpc-*` convention.
|
|
45
|
+
*/
|
|
46
|
+
export interface RpcRequestHeader {
|
|
47
|
+
name: string;
|
|
48
|
+
/**
|
|
49
|
+
* Header value bytes. For text-like headers (predicates,
|
|
50
|
+
* trace-context), encode with `Buffer.from(str)`.
|
|
51
|
+
*/
|
|
52
|
+
value: Buffer;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Handle returned by {@link TypedMeshRpc.serve}. Calling `close()`
|
|
56
|
+
* unregisters the service; in-flight handlers continue to
|
|
57
|
+
* completion. Always call `close()` explicitly when done — V8 GC
|
|
58
|
+
* eventually finalizes the underlying napi class but timing is
|
|
59
|
+
* non-deterministic.
|
|
60
|
+
*/
|
|
61
|
+
export interface ServeHandle {
|
|
62
|
+
close(): void;
|
|
63
|
+
isClosed(): boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Raw napi `MeshRpc` shape. Test stubs and the runtime-loaded
|
|
67
|
+
* `native.MeshRpc` instance both conform to this interface. Used
|
|
68
|
+
* as the `TypedMeshRpc` constructor parameter type.
|
|
69
|
+
*/
|
|
70
|
+
export interface RawMeshRpc {
|
|
71
|
+
serve(service: string, handler: (req: Buffer) => Promise<Buffer>): ServeHandle;
|
|
72
|
+
call(targetNodeId: bigint, service: string, request: Buffer, opts?: CallOptions): Promise<Buffer>;
|
|
73
|
+
callService(service: string, request: Buffer, opts?: CallOptions): Promise<Buffer>;
|
|
74
|
+
callStreaming(targetNodeId: bigint, service: string, request: Buffer, opts?: CallOptions): Promise<RawRpcStream>;
|
|
75
|
+
findServiceNodes(service: string): bigint[];
|
|
76
|
+
/** Mint a fresh cancel token (`bigint`). */
|
|
77
|
+
reserveCancelToken(): bigint;
|
|
78
|
+
/** Abort the in-flight call associated with `token`. Idempotent. */
|
|
79
|
+
cancelCall(token: bigint): void;
|
|
80
|
+
}
|
|
81
|
+
/** Raw napi `RpcStream` — minimal shape consumed by `TypedRpcStream`. */
|
|
82
|
+
export interface RawRpcStream {
|
|
83
|
+
next(): Promise<Buffer | null>;
|
|
84
|
+
grant(n: number): Promise<void>;
|
|
85
|
+
flowControlled(): Promise<boolean>;
|
|
86
|
+
close(): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
/** Handler signature: receives the decoded request and returns a response. */
|
|
89
|
+
export type TypedHandler<Req = unknown, Resp = unknown> = (req: Req) => Resp | Promise<Resp>;
|
|
90
|
+
export declare class TypedMeshRpc {
|
|
91
|
+
private readonly _raw;
|
|
92
|
+
/**
|
|
93
|
+
* Build a TypedMeshRpc against a NetMesh. Cheap; returns a
|
|
94
|
+
* new wrapper around an internal raw MeshRpc.
|
|
95
|
+
*/
|
|
96
|
+
static fromMesh(mesh: object): TypedMeshRpc;
|
|
97
|
+
/**
|
|
98
|
+
* Build a TypedMeshRpc from an already-constructed raw MeshRpc.
|
|
99
|
+
* Useful if you need the raw + typed surface side by side.
|
|
100
|
+
*/
|
|
101
|
+
constructor(rawMeshRpc: RawMeshRpc);
|
|
102
|
+
/** Underlying raw `MeshRpc` for users who want the Buffer-level surface. */
|
|
103
|
+
get raw(): RawMeshRpc;
|
|
104
|
+
/**
|
|
105
|
+
* Register a typed handler. The handler receives a decoded
|
|
106
|
+
* `Req` and returns a `Resp` (or a Promise of one). JSON
|
|
107
|
+
* encode/decode happens at the binding boundary; encode
|
|
108
|
+
* failure inside the handler surfaces to the caller as
|
|
109
|
+
* `RpcServerError(status=0x0006 Internal)`.
|
|
110
|
+
*/
|
|
111
|
+
serve<Req = unknown, Resp = unknown>(service: string, handler: TypedHandler<Req, Resp>): ServeHandle;
|
|
112
|
+
/**
|
|
113
|
+
* Direct-addressed typed call. Encodes `req` as JSON, calls,
|
|
114
|
+
* decodes the response. Throws an `RpcError` subclass on
|
|
115
|
+
* failure (matched by the napi prefix → JS class mapping in
|
|
116
|
+
* `errors.js`).
|
|
117
|
+
*
|
|
118
|
+
* Pass `opts.signal` (AbortSignal) for caller-driven
|
|
119
|
+
* cancellation. The wrapper mints a cancel token via the raw
|
|
120
|
+
* binding's `reserveCancelToken()`, attaches an abort listener,
|
|
121
|
+
* and lets the abort fire `cancelCall(token)` to drop the in-
|
|
122
|
+
* flight call (CANCEL fires on the wire, the call rejects with
|
|
123
|
+
* `nrpc:cancelled:`).
|
|
124
|
+
*/
|
|
125
|
+
call<Req = unknown, Resp = unknown>(targetNodeId: bigint, service: string, req: Req, opts?: CallOptions): Promise<Resp>;
|
|
126
|
+
/**
|
|
127
|
+
* Service-discovery typed call. Resolves `service` against the
|
|
128
|
+
* local capability index, picks a target per the routing
|
|
129
|
+
* policy, calls. Throws an `RpcError` subclass on failure.
|
|
130
|
+
*/
|
|
131
|
+
callService<Req = unknown, Resp = unknown>(service: string, req: Req, opts?: CallOptions): Promise<Resp>;
|
|
132
|
+
/**
|
|
133
|
+
* Open a typed streaming call. Returns a `TypedRpcStream` that
|
|
134
|
+
* yields decoded `Resp` values per `next()` until EOF.
|
|
135
|
+
*/
|
|
136
|
+
callStreaming<Req = unknown, Resp = unknown>(targetNodeId: bigint, service: string, req: Req, opts?: CallOptions): Promise<TypedRpcStream<Resp>>;
|
|
137
|
+
/** Pass-through to `MeshRpc.findServiceNodes`. */
|
|
138
|
+
findServiceNodes(service: string): bigint[];
|
|
139
|
+
/**
|
|
140
|
+
* Direct-addressed typed call with retry. See {@link RetryPolicy}.
|
|
141
|
+
*/
|
|
142
|
+
callWithRetry<Req = unknown, Resp = unknown>(targetNodeId: bigint, service: string, req: Req, opts: CallOptions | undefined, policy: RetryPolicy): Promise<Resp>;
|
|
143
|
+
/**
|
|
144
|
+
* Hedge typed call across the listed targets. First reply
|
|
145
|
+
* (Ok or Err) wins; if every target fails, the surfaced error
|
|
146
|
+
* is the primary's (target index 0) for stable diagnostics.
|
|
147
|
+
*/
|
|
148
|
+
callWithHedgeTo<Req = unknown, Resp = unknown>(targets: bigint[], service: string, req: Req, opts: CallOptions | undefined, policy: HedgePolicy): Promise<Resp>;
|
|
149
|
+
}
|
|
150
|
+
export declare class TypedRpcStream<Resp = unknown> implements AsyncIterable<Resp> {
|
|
151
|
+
private readonly _raw;
|
|
152
|
+
private _done;
|
|
153
|
+
constructor(rawRpcStream: RawRpcStream);
|
|
154
|
+
/**
|
|
155
|
+
* Pull the next decoded value. Returns `null` on clean EOF.
|
|
156
|
+
* Throws `RpcCodecError(direction='decode')` if a chunk fails
|
|
157
|
+
* to decode (terminates the stream — the underlying CANCEL is
|
|
158
|
+
* fired by the raw RpcStream's drop).
|
|
159
|
+
*/
|
|
160
|
+
next(): Promise<Resp | null>;
|
|
161
|
+
/** Async iterator support: `for await (const chunk of stream) { ... }`. */
|
|
162
|
+
[Symbol.asyncIterator](): AsyncIterator<Resp>;
|
|
163
|
+
/** Grant `n` flow-control credits to the server pump. */
|
|
164
|
+
grant(n: number): Promise<void>;
|
|
165
|
+
/** `true` if the call set `streamWindowInitial`. */
|
|
166
|
+
flowControlled(): Promise<boolean>;
|
|
167
|
+
/** Close the stream; emits CANCEL to the server. Idempotent. */
|
|
168
|
+
close(): Promise<void>;
|
|
169
|
+
}
|
|
170
|
+
export declare function defaultRetryable(err: unknown): boolean;
|
|
171
|
+
export interface RetryPolicyOptions {
|
|
172
|
+
/** Total attempts (NOT additional retries). Default 3. Must be >= 1. */
|
|
173
|
+
maxAttempts?: number;
|
|
174
|
+
/** Backoff before the first retry, in ms. Default 50. */
|
|
175
|
+
initialBackoffMs?: number;
|
|
176
|
+
/** Upper bound on per-attempt backoff (true ceiling, after jitter). Default 1000. */
|
|
177
|
+
maxBackoffMs?: number;
|
|
178
|
+
/** Multiplicative growth factor between attempts. Default 2.0. */
|
|
179
|
+
backoffMultiplier?: number;
|
|
180
|
+
/** Full-half jitter on backoffs. Default true. */
|
|
181
|
+
jitter?: boolean;
|
|
182
|
+
/** Predicate: should this error be retried? Default {@link defaultRetryable}. */
|
|
183
|
+
retryable?: (err: unknown) => boolean;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Retry policy. Defaults: 3 attempts, 50ms→1s exponential backoff,
|
|
187
|
+
* full-half jitter on, retryable predicate matches the Rust SDK's
|
|
188
|
+
* `default_retryable` (skips RpcCodecError, RpcNoRouteError, and
|
|
189
|
+
* non-transient ServerError statuses).
|
|
190
|
+
*/
|
|
191
|
+
export declare class RetryPolicy {
|
|
192
|
+
readonly maxAttempts: number;
|
|
193
|
+
readonly initialBackoffMs: number;
|
|
194
|
+
readonly maxBackoffMs: number;
|
|
195
|
+
readonly backoffMultiplier: number;
|
|
196
|
+
readonly jitter: boolean;
|
|
197
|
+
readonly retryable: (err: unknown) => boolean;
|
|
198
|
+
constructor(opts?: RetryPolicyOptions);
|
|
199
|
+
/**
|
|
200
|
+
* Compute the backoff for `attempt` (1-indexed). Caps at
|
|
201
|
+
* `maxBackoffMs` AFTER jitter so the cap is a true ceiling.
|
|
202
|
+
*/
|
|
203
|
+
computeBackoffMs(attempt: number): number;
|
|
204
|
+
}
|
|
205
|
+
export interface HedgePolicyOptions {
|
|
206
|
+
/** Wait this long after the primary before firing the first hedge. Default 50ms. */
|
|
207
|
+
delayMs?: number;
|
|
208
|
+
/** Number of hedge requests in addition to the primary. Default 1. */
|
|
209
|
+
hedges?: number;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Hedge policy. Fire-then-race: primary at t=0, hedges at
|
|
213
|
+
* t = delayMs * idx. First reply wins; if every hedge fails,
|
|
214
|
+
* the primary's error is surfaced deterministically.
|
|
215
|
+
*/
|
|
216
|
+
export declare class HedgePolicy {
|
|
217
|
+
readonly delayMs: number;
|
|
218
|
+
readonly hedges: number;
|
|
219
|
+
constructor(opts?: HedgePolicyOptions);
|
|
220
|
+
}
|
|
221
|
+
export type BreakerState = 'closed' | 'open' | 'half-open';
|
|
222
|
+
export declare function defaultBreakerFailure(err: unknown): boolean;
|
|
223
|
+
export declare class BreakerOpenError extends Error {
|
|
224
|
+
constructor();
|
|
225
|
+
}
|
|
226
|
+
export interface CircuitBreakerOptions {
|
|
227
|
+
/** Consecutive failures before tripping. Default 5. */
|
|
228
|
+
failureThreshold?: number;
|
|
229
|
+
/** Cooldown before transitioning Open → HalfOpen. Default 30000. */
|
|
230
|
+
resetAfterMs?: number;
|
|
231
|
+
/** Successful probes needed to close from HalfOpen. Default 1. */
|
|
232
|
+
successThreshold?: number;
|
|
233
|
+
/** Predicate: does this error count as a failure? Default {@link defaultBreakerFailure}. */
|
|
234
|
+
failurePredicate?: (err: unknown) => boolean;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Three-state circuit breaker. Long-lived; instantiate once per
|
|
238
|
+
* logical downstream and share. `breaker.call(() => ...)`
|
|
239
|
+
* composes around any async op.
|
|
240
|
+
*/
|
|
241
|
+
export declare class CircuitBreaker {
|
|
242
|
+
readonly failureThreshold: number;
|
|
243
|
+
readonly resetAfterMs: number;
|
|
244
|
+
readonly successThreshold: number;
|
|
245
|
+
readonly failurePredicate: (err: unknown) => boolean;
|
|
246
|
+
private _state;
|
|
247
|
+
private _consecutiveFailures;
|
|
248
|
+
private _consecutiveSuccesses;
|
|
249
|
+
private _openedAt;
|
|
250
|
+
private _probeInFlight;
|
|
251
|
+
constructor(opts?: CircuitBreakerOptions);
|
|
252
|
+
state(): BreakerState;
|
|
253
|
+
consecutiveFailures(): number;
|
|
254
|
+
reset(): void;
|
|
255
|
+
/**
|
|
256
|
+
* Wrap an async op. Returns its value on success, throws on
|
|
257
|
+
* failure (or on rejection). When the breaker is `Open` within
|
|
258
|
+
* its cooldown, throws `BreakerOpenError` without invoking
|
|
259
|
+
* `op`.
|
|
260
|
+
*
|
|
261
|
+
* Both success and failure paths record the outcome through
|
|
262
|
+
* `_recordOutcome`, which is responsible for clearing
|
|
263
|
+
* `_probeInFlight` on the half-open-probe admission. A
|
|
264
|
+
* synchronous throw inside `op` (rare — `await` is always at
|
|
265
|
+
* `op`'s call site) is still routed through the catch arm.
|
|
266
|
+
*/
|
|
267
|
+
call<T>(op: () => Promise<T>): Promise<T>;
|
|
268
|
+
private _tryAdmit;
|
|
269
|
+
private _recordOutcome;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Build an Error a typed serve handler can throw to surface a
|
|
273
|
+
* specific application status code to the caller. The Rust
|
|
274
|
+
* binding parses messages of the form
|
|
275
|
+
* `nrpc:app_error:0x<code>:<body>` and maps them to
|
|
276
|
+
* `RpcStatus::Application(code)` — without this prefix the
|
|
277
|
+
* thrown error becomes a generic `RpcStatus::Internal`. Mirrors
|
|
278
|
+
* the Python binding's `RpcAppError(code, body)`.
|
|
279
|
+
*
|
|
280
|
+
* Use cases: typed handlers that want to return 4xx-style
|
|
281
|
+
* application errors (`NRPC_TYPED_BAD_REQUEST`,
|
|
282
|
+
* `NRPC_TYPED_HANDLER_ERROR`, custom app codes >= 0x8000).
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* rpc.serve('echo', (req) => {
|
|
286
|
+
* if (typeof req.text !== 'string') {
|
|
287
|
+
* throw appError(NRPC_TYPED_BAD_REQUEST,
|
|
288
|
+
* JSON.stringify({error: 'missing text'}))
|
|
289
|
+
* }
|
|
290
|
+
* return { echo: req.text }
|
|
291
|
+
* })
|
|
292
|
+
*/
|
|
293
|
+
export declare function appError(code: number, body: string | Buffer): Error;
|
|
294
|
+
/** RpcStatus::Application(0x8000): typed handler bad-request body. */
|
|
295
|
+
export declare const NRPC_TYPED_BAD_REQUEST: 32768;
|
|
296
|
+
/** RpcStatus::Application(0x8001): typed handler returned `throw`. */
|
|
297
|
+
export declare const NRPC_TYPED_HANDLER_ERROR: 32769;
|