@push.rocks/smartrust 2.3.0 → 2.5.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.
@@ -8,15 +8,25 @@
8
8
  export class RustBridgeRequestError extends Error {
9
9
  code;
10
10
  responseErrorCode;
11
+ /** Structured details from the Rust error response's `errorData`, as sent. */
12
+ responseErrorData;
11
13
  requestId;
12
14
  method;
15
+ /**
16
+ * Present when a timeout or abort made the bridge send a cancel frame for a
17
+ * request already written to Rust. Settles once Rust answers the original
18
+ * request or the transport closes. It never rejects.
19
+ */
20
+ remoteSettlement;
13
21
  constructor(message, options) {
14
22
  super(message, options.cause === undefined ? undefined : { cause: options.cause });
15
23
  this.name = 'RustBridgeRequestError';
16
24
  this.code = options.code;
17
25
  this.responseErrorCode = options.responseErrorCode;
26
+ this.responseErrorData = options.responseErrorData;
18
27
  this.requestId = options.requestId;
19
28
  this.method = options.method;
29
+ this.remoteSettlement = options.remoteSettlement;
20
30
  }
21
31
  }
22
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ydXN0YnJpZGdlcmVxdWVzdGVycm9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5ydXN0YnJpZGdlcmVxdWVzdGVycm9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQXFCQTs7Ozs7O0dBTUc7QUFDSCxNQUFNLE9BQU8sc0JBQXVCLFNBQVEsS0FBSztJQUMvQixJQUFJLENBQThCO0lBQ2xDLGlCQUFpQixDQUFVO0lBQzNCLFNBQVMsQ0FBVTtJQUNuQixNQUFNLENBQVU7SUFFaEMsWUFBWSxPQUFlLEVBQUUsT0FBdUM7UUFDbEUsS0FBSyxDQUFDLE9BQU8sRUFBRSxPQUFPLENBQUMsS0FBSyxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxPQUFPLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQztRQUNuRixJQUFJLENBQUMsSUFBSSxHQUFHLHdCQUF3QixDQUFDO1FBQ3JDLElBQUksQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQztRQUN6QixJQUFJLENBQUMsaUJBQWlCLEdBQUcsT0FBTyxDQUFDLGlCQUFpQixDQUFDO1FBQ25ELElBQUksQ0FBQyxTQUFTLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQztRQUNuQyxJQUFJLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUM7SUFDL0IsQ0FBQztDQUNGIn0=
32
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ydXN0YnJpZGdlcmVxdWVzdGVycm9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5ydXN0YnJpZGdlcmVxdWVzdGVycm9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWtEQTs7Ozs7O0dBTUc7QUFDSCxNQUFNLE9BQU8sc0JBQXVCLFNBQVEsS0FBSztJQUMvQixJQUFJLENBQThCO0lBQ2xDLGlCQUFpQixDQUFVO0lBQzNDLDhFQUE4RTtJQUM5RCxpQkFBaUIsQ0FBVztJQUM1QixTQUFTLENBQVU7SUFDbkIsTUFBTSxDQUFVO0lBQ2hDOzs7O09BSUc7SUFDYSxnQkFBZ0IsQ0FBd0M7SUFFeEUsWUFBWSxPQUFlLEVBQUUsT0FBdUM7UUFDbEUsS0FBSyxDQUFDLE9BQU8sRUFBRSxPQUFPLENBQUMsS0FBSyxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxPQUFPLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQztRQUNuRixJQUFJLENBQUMsSUFBSSxHQUFHLHdCQUF3QixDQUFDO1FBQ3JDLElBQUksQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQztRQUN6QixJQUFJLENBQUMsaUJBQWlCLEdBQUcsT0FBTyxDQUFDLGlCQUFpQixDQUFDO1FBQ25ELElBQUksQ0FBQyxpQkFBaUIsR0FBRyxPQUFPLENBQUMsaUJBQWlCLENBQUM7UUFDbkQsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDO1FBQ25DLElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQztRQUM3QixJQUFJLENBQUMsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLGdCQUFnQixDQUFDO0lBQ25ELENBQUM7Q0FDRiJ9
@@ -1,5 +1,5 @@
1
1
  export { RustBridge } from './classes.rustbridge.js';
2
- export { RustBridgeRequestError, type IRustBridgeRequestErrorOptions, type TRustBridgeRequestErrorCode, } from './classes.rustbridgerequesterror.js';
2
+ export { RustBridgeRequestError, type IRustBridgeRequestErrorOptions, type IRustBridgeRemoteSettlement, type TRustBridgeRemoteSettlementOutcome, type TRustBridgeRequestErrorCode, } from './classes.rustbridgerequesterror.js';
3
3
  export { RustBinaryLocator } from './classes.rustbinarylocator.js';
4
4
  export { RustBinaryLocatorError } from './classes.rustbinarylocatorerror.js';
5
5
  export { StreamingResponse } from './classes.streamingresponse.js';
package/dist_ts/index.js CHANGED
@@ -7,4 +7,4 @@ export { StdioTransport } from './classes.stdiotransport.js';
7
7
  export { SocketTransport } from './classes.sockettransport.js';
8
8
  export { LineScanner } from './classes.linescanner.js';
9
9
  export * from './interfaces/index.js';
10
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDckQsT0FBTyxFQUNMLHNCQUFzQixHQUd2QixNQUFNLHFDQUFxQyxDQUFDO0FBQzdDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBQzdFLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUM3RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDL0QsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3ZELGNBQWMsdUJBQXVCLENBQUMifQ==
10
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDckQsT0FBTyxFQUNMLHNCQUFzQixHQUt2QixNQUFNLHFDQUFxQyxDQUFDO0FBQzdDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBQzdFLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUM3RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDL0QsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3ZELGNBQWMsdUJBQXVCLENBQUMifQ==
@@ -8,11 +8,26 @@ export interface IRustBridgeLogger {
8
8
  * Options for one non-streaming Rust bridge request.
9
9
  */
10
10
  export interface IRustBridgeRequestOptions {
11
- /** Abort only the local wait for this request. */
11
+ /**
12
+ * Abort the local wait for this request. When the binary advertised the
13
+ * `cancel` capability and the request was already written, the bridge also
14
+ * sends a cancel frame.
15
+ */
12
16
  signal?: AbortSignal;
13
17
  /** Override the bridge request timeout for this call, in milliseconds. */
14
18
  timeoutMs?: number;
15
19
  }
20
+ /**
21
+ * Options for one streaming Rust bridge request.
22
+ */
23
+ export interface IRustBridgeStreamingRequestOptions {
24
+ /**
25
+ * Abort the local stream. When the binary advertised the `cancel`
26
+ * capability and the request was already written, the bridge also sends a
27
+ * cancel frame.
28
+ */
29
+ signal?: AbortSignal;
30
+ }
16
31
  /** Options for confirmed bridge termination. */
17
32
  export interface IRustBridgeTerminateOptions {
18
33
  /**
@@ -8,6 +8,16 @@ export interface IManagementRequest {
8
8
  /** Value-free metadata for an associated one-shot binary frame. */
9
9
  sensitive?: IManagementSensitivePayload;
10
10
  }
11
+ /**
12
+ * Cancel frame sent to a binary that advertised the `cancel` capability, after
13
+ * a timeout or abort abandoned a request that was already written. The binary
14
+ * ignores it for ids it no longer runs and otherwise still answers the
15
+ * original id exactly once.
16
+ */
17
+ export interface IManagementCancelRequest {
18
+ id: string;
19
+ cancel: true;
20
+ }
11
21
  export interface IManagementSensitivePayload {
12
22
  protocol: 'smartrust-sensitive-v1';
13
23
  fileDescriptor: number;
@@ -27,6 +37,10 @@ export interface IManagementResponse {
27
37
  result?: any;
28
38
  error?: string;
29
39
  errorCode?: string;
40
+ /** Optional structured details of a failure, for example per-field validation errors. */
41
+ errorData?: unknown;
42
+ /** Set with `success: false` when the command stopped because of a cancel frame. */
43
+ cancelled?: boolean;
30
44
  /** Required after the receiver consumed the exact associated binary frame. */
31
45
  sensitiveAck?: IManagementSensitiveAck;
32
46
  }
@@ -36,6 +50,11 @@ export interface IManagementResponse {
36
50
  export interface IManagementEvent {
37
51
  event: string;
38
52
  data: any;
53
+ /**
54
+ * Protocol capabilities, read only from the ready event. `cancel` opts the
55
+ * current transport generation in to cancel frames.
56
+ */
57
+ capabilities?: string[];
39
58
  }
40
59
  /**
41
60
  * Definition of a single command supported by a Rust binary.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartrust",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "private": false,
5
5
  "description": "A type-safe bridge between JavaScript engines and Rust binaries.",
6
6
  "main": "dist_ts/index.js",
@@ -9,10 +9,10 @@
9
9
  "author": "Task Venture Capital GmbH",
10
10
  "license": "MIT",
11
11
  "devDependencies": {
12
- "@git.zone/tsbuild": "^4.5.0",
13
- "@git.zone/tsrun": "^2.0.6",
14
- "@git.zone/tstest": "^6.1.1",
15
- "@types/node": "^26.5.1"
12
+ "@git.zone/tsbuild": "^5.0.0",
13
+ "@git.zone/tsrun": "^3.0.0",
14
+ "@git.zone/tstest": "^6.2.0",
15
+ "@types/node": "^26.6.1"
16
16
  },
17
17
  "dependencies": {
18
18
  "@push.rocks/smartpath": "^6.0.0"
package/readme.md CHANGED
@@ -47,9 +47,12 @@ If you're integrating Rust into a Node.js project, you'll inevitably need:
47
47
  |-----------|--------|-------------|
48
48
  | **TS → Rust** (Request) | `{"id": "req_1", "method": "start", "params": {...}}` | Command with unique ID |
49
49
  | **Rust → TS** (Response) | `{"id": "req_1", "success": true, "result": {...}}` | Final response correlated by ID |
50
- | **Rust → TS** (Error) | `{"id": "req_1", "success": false, "error": "msg", "errorCode": "EAPP_INVALID"}` | Error correlated by ID; `errorCode` is optional |
50
+ | **Rust → TS** (Error) | `{"id": "req_1", "success": false, "error": "msg", "errorCode": "EAPP_INVALID", "errorData": {...}}` | Error correlated by ID; `errorCode` and `errorData` are optional |
51
51
  | **Rust → TS** (Stream Chunk) | `{"id": "req_1", "stream": true, "data": {...}}` | Intermediate chunk (zero or more) |
52
52
  | **Rust → TS** (Event) | `{"event": "ready", "data": {...}}` | Unsolicited event (no ID) |
53
+ | **Rust → TS** (Ready with capabilities) | `{"event": "ready", "data": {...}, "capabilities": ["cancel"]}` | Optional opt-in to cancel frames for this connection |
54
+ | **TS → Rust** (Cancel) | `{"id": "req_1", "cancel": true}` | Sent only to binaries that advertised `cancel`, after a timeout or abort |
55
+ | **Rust → TS** (Cancelled) | `{"id": "req_1", "success": false, "cancelled": true, "error": "msg"}` | The one answer to a cancelled request; `errorCode` and `errorData` are optional |
53
56
 
54
57
  This protocol works identically over stdio and socket transports. Your Rust binary reads JSON lines from one end and writes JSON lines to the other. That's it.
55
58
 
@@ -222,9 +225,10 @@ try {
222
225
  constructor-level `requestTimeoutMs` remains the default. An already-aborted
223
226
  signal rejects before a request ID is allocated or anything is written.
224
227
 
225
- Timeout and abort only abandon the local TypeScript wait. They do not kill or
226
- disconnect the shared bridge, late responses are ignored, and later commands
227
- can continue using the same bridge.
228
+ Timeout and abort reject the local wait at once. They do not kill or
229
+ disconnect the shared bridge, and later commands can continue using the same
230
+ bridge. When the binary supports it, the bridge also asks Rust to stop the
231
+ command; see [Cancelling the Rust Side](#cancelling-the-rust-side).
228
232
 
229
233
  Use `maxPendingRequestsByMethod` to fail fast before serialization and transport
230
234
  writes when one command type reaches its local pending-request limit. Limits are
@@ -244,14 +248,66 @@ Capacity is released when the local request resolves, rejects, times out, or is
244
248
  aborted, when its transport write fails, or when the bridge terminates. Regular
245
249
  and streaming calls to the same method share its capacity. Every configured
246
250
  limit must be a positive safe integer; invalid values throw `RangeError` during
247
- construction. As with cancellation generally, timeout or abort cannot stop work
248
- that was already written to Rust, so the limit bounds local pending state rather
249
- than remote execution after local abandonment.
251
+ construction. The limit bounds local pending state, not remote execution: a
252
+ timed-out or aborted request frees its capacity immediately, even while Rust is
253
+ still working on it or answering its cancel frame.
250
254
 
251
- > The JSON-lines protocol has no cancellation message. After a command has
252
- > been written, Rust may still execute it even if the local wait times out or
253
- > is aborted. Do not blindly retry mutating commands; first establish whether
254
- > the original command executed or design the command to be idempotent.
255
+ #### Cancelling the Rust Side
256
+
257
+ A binary opts in to cancellation by adding `"capabilities": ["cancel"]` to its
258
+ ready event. The bridge records this per connection: a respawn or socket
259
+ reconnect starts without it until the next ready event advertises it again.
260
+ Binaries that do not advertise `cancel` never receive a cancel frame, so the
261
+ Rust command may keep running after the local wait ends.
262
+
263
+ With the capability, a timeout or abort of a request that was already written
264
+ sends one cancel frame, `{"id":"req_1","cancel":true}`. No cancel frame is sent
265
+ when the response already arrived, when the signal was aborted before the
266
+ request was written, or for `sendCommandWithSensitiveBytes`, whose failures
267
+ terminate the child instead.
268
+
269
+ The rejection's `remoteSettlement` then reports how Rust settled the request.
270
+ It resolves once and never rejects:
271
+
272
+ | `outcome` | Meaning |
273
+ |---|---|
274
+ | `cancelled` | Rust stopped and answered `success: false, cancelled: true` |
275
+ | `completed` | Rust finished anyway; `result` holds the result |
276
+ | `failed` | Rust answered `success: false` without `cancelled: true` |
277
+ | `disconnected` | The transport closed before Rust answered; the outcome is unknown |
278
+
279
+ `cancelled` and `failed` carry Rust's `error`, string `errorCode` and
280
+ `errorData` when it sent them. `remoteSettlement` is undefined when no cancel
281
+ frame was sent. Stream chunks and answers that arrive for a cancelled request
282
+ are consumed by the bridge and never reach the caller.
283
+
284
+ A Rust command may take a while to reach a point where it can stop, and a
285
+ faulty binary may never answer. Bound the wait yourself and terminate the child
286
+ when it runs out; the settlement then resolves as `disconnected`:
287
+
288
+ ```typescript
289
+ try {
290
+ await bridge.sendCommand('ingest', params, { timeoutMs: 60_000 });
291
+ } catch (error) {
292
+ if (error instanceof RustBridgeRequestError && error.remoteSettlement) {
293
+ let timer: ReturnType<typeof setTimeout> | undefined;
294
+ const deadline = new Promise<null>((resolve) => {
295
+ timer = setTimeout(() => resolve(null), 30_000);
296
+ });
297
+ const settlement = await Promise.race([error.remoteSettlement, deadline]);
298
+ clearTimeout(timer);
299
+ if (!settlement) {
300
+ await bridge.terminate(); // respawn with bridge.spawn() on next use
301
+ }
302
+ }
303
+ throw error;
304
+ }
305
+ ```
306
+
307
+ > Without the `cancel` capability, or when the settlement is `completed` or
308
+ > `disconnected`, Rust may have executed the command. Do not blindly retry
309
+ > mutating commands; first establish whether the original command executed or
310
+ > design the command to be idempotent.
255
311
 
256
312
  Timeout, cancellation, transport-write/exit, and Rust response failures use
257
313
  `RustBridgeRequestError`.
@@ -260,8 +316,8 @@ programmatic handling:
260
316
 
261
317
  | Code | Meaning |
262
318
  |---|---|
263
- | `ERR_RUST_BRIDGE_REQUEST_TIMEOUT` | The local request deadline elapsed |
264
- | `ERR_RUST_BRIDGE_REQUEST_ABORTED` | The caller's signal aborted the local wait |
319
+ | `ERR_RUST_BRIDGE_REQUEST_TIMEOUT` | The local request deadline or streaming inactivity deadline elapsed |
320
+ | `ERR_RUST_BRIDGE_REQUEST_ABORTED` | The caller's signal aborted the local wait or stream |
265
321
  | `ERR_RUST_BRIDGE_REQUEST_LIMIT` | The configured pending-request limit for this method is full |
266
322
  | `ERR_RUST_BRIDGE_REQUEST_WRITE` | The transport rejected the request write |
267
323
  | `ERR_RUST_BRIDGE_REQUEST_TRANSPORT` | The transport exited, disconnected, or was killed |
@@ -274,6 +330,24 @@ message. The bridge-owned `code` remains
274
330
  failures from application-specific Rust failures. Legacy responses and
275
331
  non-string `errorCode` values leave `responseErrorCode` undefined.
276
332
 
333
+ When a failure has structured details, for example one reason per invalid
334
+ input, Rust can send them as the optional response `errorData`, any JSON
335
+ value. `RustBridgeRequestError.responseErrorData` carries it as sent, typed
336
+ `unknown`, so the caller narrows it for its own error codes. A response
337
+ without `errorData` leaves `responseErrorData` undefined.
338
+
339
+ ```typescript
340
+ // Rust: {"id":"req_7","success":false,"error":"Validation failed",
341
+ // "errorCode":"EVALIDATION","errorData":{"fields":[{"name":"port","messages":["must be 1-65535"]}]}}
342
+ try {
343
+ await bridge.sendCommand('configure', { port: 0 });
344
+ } catch (error) {
345
+ if (error instanceof RustBridgeRequestError && error.responseErrorCode === 'EVALIDATION') {
346
+ console.error(error.responseErrorData);
347
+ }
348
+ }
349
+ ```
350
+
277
351
  ### Confirmed Termination
278
352
 
279
353
  Use `await bridge.terminate()` when later work must not overlap the current
@@ -427,12 +501,28 @@ try {
427
501
  ```
428
502
 
429
503
  A final `success: false` response rejects both paths with the same
430
- `RustBridgeRequestError`. When the response includes an `errorCode`, its value
431
- is available as `responseErrorCode` on that error.
504
+ `RustBridgeRequestError`. When the response includes an `errorCode` or
505
+ `errorData`, they are available as `responseErrorCode` and `responseErrorData`
506
+ on that error. When the transport exits, disconnects or is terminated, both
507
+ paths reject with a `RustBridgeRequestError` whose code is
508
+ `ERR_RUST_BRIDGE_REQUEST_TRANSPORT`, as regular commands do.
509
+
510
+ #### Aborting a Stream
511
+
512
+ Pass an `AbortSignal` to stop consuming a stream. The iterator and `.result`
513
+ reject with `ERR_RUST_BRIDGE_REQUEST_ABORTED`, and a binary with the `cancel`
514
+ capability receives a cancel frame, exactly as for regular commands:
515
+
516
+ ```typescript
517
+ const controller = new AbortController();
518
+ const stream = bridge.sendCommandStreaming('processData', { count: 100 }, {
519
+ signal: controller.signal,
520
+ });
521
+ ```
432
522
 
433
523
  #### Stream Timeout
434
524
 
435
- By default, streaming commands use the same timeout as regular commands (`requestTimeoutMs`). The timeout **resets on each chunk received**, so it acts as an inactivity timeout rather than an absolute timeout. You can configure it separately:
525
+ By default, streaming commands use the same timeout as regular commands (`requestTimeoutMs`). The timeout **resets on each chunk received**, so it acts as an inactivity timeout rather than an absolute timeout. When it elapses, the stream rejects with a `RustBridgeRequestError` whose code is `ERR_RUST_BRIDGE_REQUEST_TIMEOUT`, and a binary with the `cancel` capability receives a cancel frame. You can configure it separately:
436
526
 
437
527
  ```typescript
438
528
  const bridge = new RustBridge<TMyCommands>({
@@ -602,6 +692,8 @@ Your Rust binary needs to implement a simple protocol. The transport (stdio or s
602
692
 
603
693
  6. **Use stderr** for logging — it won't interfere with the IPC protocol
604
694
 
695
+ 7. **Optionally support cancellation**: add `"capabilities":["cancel"]` to the ready event, then treat `{"id":"...","cancel":true}` as a request to stop that command at its next safe point. Answer the original id exactly once, either `{"id":"...","success":false,"cancelled":true,"error":"..."}` or its normal result when the command could no longer stop. Ignore cancel frames for ids that already finished or are unknown, and keep reading stdin while commands run so cancel frames arrive promptly.
696
+
605
697
  #### Socket Mode (Daemon)
606
698
 
607
699
  1. **Listen** on a Unix socket (e.g., `/var/run/my-daemon.sock`) or Windows named pipe
@@ -735,7 +827,7 @@ fn main() {
735
827
  | `connect(socketPath, options?)` | `Promise<boolean>` | **Socket mode**: Connect to a running daemon; returns `false` on connection/readiness failure and rejects stdio-only descriptor/byte-channel options |
736
828
  | `sendCommand(method, params, options?)` | `Promise<TCommands[K]['result']>` | Send a typed command with an optional per-call timeout or abort signal |
737
829
  | `sendCommandWithSensitiveBytes(method, params, bytes, options?)` | `Promise<TCommands[K]['result']>` | Send one exclusive JSON command with its raw bytes on the generation-owned one-shot pipe |
738
- | `sendCommandStreaming(method, params)` | `StreamingResponse<TChunk, TResult>` | Send a streaming command; returns immediately |
830
+ | `sendCommandStreaming(method, params, options?)` | `StreamingResponse<TChunk, TResult>` | Send a streaming command with an optional abort signal; returns immediately |
739
831
  | `terminate(options?)` | `Promise<void>` | Confirm stdio child exit or local socket closure; `gracePeriodMs` controls SIGKILL escalation |
740
832
  | `kill()` | `void` | Fire-and-forget compatibility wrapper around `terminate()` |
741
833
  | `running` | `boolean` | Whether the bridge is currently connected and ready |
@@ -792,6 +884,9 @@ fn main() {
792
884
  |---|---|
793
885
  | `IRustBridgeOptions` | Full configuration for `RustBridge` |
794
886
  | `IRustBridgeRequestOptions` | Per-command options: `{ signal?, timeoutMs? }` |
887
+ | `IRustBridgeStreamingRequestOptions` | Per-stream options: `{ signal? }` |
888
+ | `IRustBridgeRemoteSettlement` | Remote outcome of a cancelled request: `{ outcome, result?, error?, errorCode?, errorData? }` |
889
+ | `TRustBridgeRemoteSettlementOutcome` | `'cancelled' \| 'completed' \| 'failed' \| 'disconnected'` |
795
890
  | `IRustBridgeTerminateOptions` | Confirmed termination options: `{ gracePeriodMs? }` |
796
891
  | `IRustTransportDisconnectOptions` | Optional confirmed-disconnect grace period for owned process transports |
797
892
  | `IBinaryLocatorOptions` | Configuration for `RustBinaryLocator` |
@@ -799,14 +894,15 @@ fn main() {
799
894
  | `IRustBridgeLogger` | Logger interface: `{ log(level, message, data?) }` |
800
895
  | `IRustTransport` | Transport interface (extends `EventEmitter`) |
801
896
  | `IManagementRequest` | IPC request shape: `{ id, method, params }` |
802
- | `IManagementResponse` | IPC response shape: `{ id, success, result?, error?, errorCode? }` |
803
- | `IManagementEvent` | IPC event shape: `{ event, data }` |
897
+ | `IManagementResponse` | IPC response shape: `{ id, success, result?, error?, errorCode?, errorData?, cancelled? }` |
898
+ | `IManagementCancelRequest` | IPC cancel frame shape: `{ id, cancel: true }` |
899
+ | `IManagementEvent` | IPC event shape: `{ event, data, capabilities? }` |
804
900
  | `IManagementStreamChunk` | IPC stream chunk shape: `{ id, stream: true, data }` |
805
901
  | `ICommandDefinition` | Single command definition: `{ params, result }` |
806
902
  | `TCommandMap` | `Record<string, ICommandDefinition>` |
807
903
  | `TStreamingCommandKeys<T>` | Extracts keys from a command map that have a `chunk` field |
808
904
  | `TExtractChunk<T>` | Extracts the chunk type from a streaming command definition |
809
- | `RustBridgeRequestError` | Request error class with stable `code`, optional `responseErrorCode`, `requestId`, and `method` fields |
905
+ | `RustBridgeRequestError` | Request error class with stable `code`, optional `responseErrorCode`, `responseErrorData`, `requestId`, `method`, and `remoteSettlement` fields |
810
906
  | `TRustBridgeRequestErrorCode` | Union of stable request error code strings |
811
907
 
812
908
  ## License and Legal Information
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartrust',
6
- version: '2.3.0',
6
+ version: '2.5.0',
7
7
  description: 'A type-safe bridge between JavaScript engines and Rust binaries.'
8
8
  }