@push.rocks/smartrust 1.4.0 → 1.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.
package/.smartconfig.json CHANGED
@@ -21,12 +21,17 @@
21
21
  ]
22
22
  },
23
23
  "release": {
24
- "accessLevel": "public",
25
- "registries": [
26
- "https://verdaccio.lossless.digital",
27
- "https://registry.npmjs.org"
28
- ]
29
- }
24
+ "targets": {
25
+ "npm": {
26
+ "registries": [
27
+ "https://verdaccio.lossless.digital",
28
+ "https://registry.npmjs.org"
29
+ ],
30
+ "accessLevel": "public"
31
+ }
32
+ }
33
+ },
34
+ "schemaVersion": 2
30
35
  },
31
36
  "@git.zone/tsdoc": {
32
37
  "legal": "\n## License and Legal Information\n\nThis repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.\n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.\n\nUse of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.\n\n### Company Information\n\nTask Venture Capital GmbH\nRegistered at District Court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
@@ -34,4 +39,4 @@
34
39
  "@ship.zone/szci": {
35
40
  "npmGlobalTools": []
36
41
  }
37
- }
42
+ }
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartrust',
6
- version: '1.4.0',
6
+ version: '1.5.0',
7
7
  description: 'A type-safe bridge between JavaScript engines and Rust binaries.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx1QkFBdUI7SUFDN0IsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLGtFQUFrRTtDQUNoRixDQUFBIn0=
@@ -1,6 +1,6 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import { StreamingResponse } from './classes.streamingresponse.js';
3
- import type { IRustBridgeOptions, ISocketConnectOptions, TCommandMap, TStreamingCommandKeys, TExtractChunk } from './interfaces/index.js';
3
+ import type { IRustBridgeOptions, IRustBridgeRequestOptions, ISocketConnectOptions, TCommandMap, TStreamingCommandKeys, TExtractChunk } from './interfaces/index.js';
4
4
  /**
5
5
  * Generic bridge between TypeScript and a Rust binary.
6
6
  * Communicates via JSON-over-stdin/stdout IPC protocol (stdio mode)
@@ -38,7 +38,7 @@ export declare class RustBridge<TCommands extends TCommandMap = TCommandMap> ext
38
38
  /**
39
39
  * Send a typed command to the Rust process and wait for the response.
40
40
  */
41
- sendCommand<K extends string & keyof TCommands>(method: K, params: TCommands[K]['params']): Promise<TCommands[K]['result']>;
41
+ sendCommand<K extends string & keyof TCommands>(method: K, params: TCommands[K]['params'], options?: IRustBridgeRequestOptions): Promise<TCommands[K]['result']>;
42
42
  /**
43
43
  * Send a streaming command to the Rust process.
44
44
  * Returns a StreamingResponse that yields chunks via `for await...of`
@@ -56,5 +56,8 @@ export declare class RustBridge<TCommands extends TCommandMap = TCommandMap> ext
56
56
  */
57
57
  get running(): boolean;
58
58
  private handleLine;
59
+ private settlePendingRequest;
60
+ private rejectAllPendingRequests;
59
61
  private cleanup;
62
+ private getErrorMessage;
60
63
  }
@@ -1,5 +1,6 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import { RustBinaryLocator } from './classes.rustbinarylocator.js';
3
+ import { RustBridgeRequestError } from './classes.rustbridgerequesterror.js';
3
4
  import { StreamingResponse } from './classes.streamingresponse.js';
4
5
  import { StdioTransport } from './classes.stdiotransport.js';
5
6
  import { SocketTransport } from './classes.sockettransport.js';
@@ -87,12 +88,12 @@ export class RustBridge extends plugins.events.EventEmitter {
87
88
  });
88
89
  transport.on('close', (...args) => {
89
90
  this.logger.log('info', `Transport closed`);
90
- this.cleanup();
91
+ this.cleanup(`${this.options.binaryName} process exited`);
91
92
  this.emit('exit', ...args);
92
93
  });
93
94
  transport.on('error', (err) => {
94
95
  this.logger.log('error', `Transport error: ${err.message}`);
95
- this.cleanup();
96
+ this.cleanup(`${this.options.binaryName} process exited`, err);
96
97
  resolve(false);
97
98
  });
98
99
  transport.on('reconnected', () => {
@@ -128,10 +129,23 @@ export class RustBridge extends plugins.events.EventEmitter {
128
129
  /**
129
130
  * Send a typed command to the Rust process and wait for the response.
130
131
  */
131
- async sendCommand(method, params) {
132
+ async sendCommand(method, params, options) {
133
+ const timeoutMs = options?.timeoutMs ?? this.options.requestTimeoutMs;
134
+ if (options?.timeoutMs !== undefined
135
+ && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0)) {
136
+ throw new RangeError('RustBridge request timeoutMs must be a positive safe integer');
137
+ }
138
+ if (options?.signal?.aborted) {
139
+ throw new RustBridgeRequestError(`Command '${method}' was aborted before it was sent`, {
140
+ code: 'ERR_RUST_BRIDGE_REQUEST_ABORTED',
141
+ method,
142
+ cause: options.signal.reason,
143
+ });
144
+ }
132
145
  if (!this.transport?.connected || !this.isRunning) {
133
146
  throw new Error(`${this.options.binaryName} bridge is not running`);
134
147
  }
148
+ const transport = this.transport;
135
149
  const id = `req_${++this.requestCounter}`;
136
150
  const request = { id, method, params };
137
151
  const json = JSON.stringify(request);
@@ -141,16 +155,62 @@ export class RustBridge extends plugins.events.EventEmitter {
141
155
  throw new Error(`Outbound message exceeds maxPayloadSize (${byteLength} > ${this.options.maxPayloadSize})`);
142
156
  }
143
157
  return new Promise((resolve, reject) => {
144
- const timer = setTimeout(() => {
145
- this.pendingRequests.delete(id);
146
- reject(new Error(`Command '${method}' timed out after ${this.options.requestTimeoutMs}ms`));
147
- }, this.options.requestTimeoutMs);
148
- this.pendingRequests.set(id, { resolve, reject, timer });
149
- this.transport.write(json + '\n').catch((err) => {
150
- clearTimeout(timer);
151
- this.pendingRequests.delete(id);
152
- reject(new Error(`Failed to write to transport: ${err.message}`));
153
- });
158
+ const pending = {
159
+ resolve,
160
+ reject,
161
+ timer: null,
162
+ method,
163
+ abortSignal: options?.signal,
164
+ };
165
+ this.pendingRequests.set(id, pending);
166
+ if (options?.signal) {
167
+ pending.abortHandler = () => {
168
+ this.settlePendingRequest(id, {
169
+ outcome: 'reject',
170
+ error: new RustBridgeRequestError(`Command '${method}' was aborted`, {
171
+ code: 'ERR_RUST_BRIDGE_REQUEST_ABORTED',
172
+ requestId: id,
173
+ method,
174
+ cause: options.signal?.reason,
175
+ }),
176
+ });
177
+ };
178
+ options.signal.addEventListener('abort', pending.abortHandler, { once: true });
179
+ }
180
+ pending.timer = setTimeout(() => {
181
+ this.settlePendingRequest(id, {
182
+ outcome: 'reject',
183
+ error: new RustBridgeRequestError(`Command '${method}' timed out after ${timeoutMs}ms`, {
184
+ code: 'ERR_RUST_BRIDGE_REQUEST_TIMEOUT',
185
+ requestId: id,
186
+ method,
187
+ }),
188
+ });
189
+ }, timeoutMs);
190
+ try {
191
+ void transport.write(json + '\n').catch((err) => {
192
+ this.settlePendingRequest(id, {
193
+ outcome: 'reject',
194
+ error: new RustBridgeRequestError(`Failed to write to transport: ${this.getErrorMessage(err)}`, {
195
+ code: 'ERR_RUST_BRIDGE_REQUEST_WRITE',
196
+ requestId: id,
197
+ method,
198
+ cause: err,
199
+ }),
200
+ });
201
+ });
202
+ }
203
+ catch (err) {
204
+ this.settlePendingRequest(id, {
205
+ outcome: 'reject',
206
+ error: new RustBridgeRequestError(`Failed to write to transport: ${this.getErrorMessage(err)}`, {
207
+ code: 'ERR_RUST_BRIDGE_REQUEST_WRITE',
208
+ requestId: id,
209
+ method,
210
+ cause: err,
211
+ }),
212
+ });
213
+ }
154
214
  });
155
215
  }
156
216
  /**
@@ -174,20 +234,32 @@ export class RustBridge extends plugins.events.EventEmitter {
174
234
  }
175
235
  const timeoutMs = this.options.streamTimeoutMs ?? this.options.requestTimeoutMs;
176
236
  const timer = setTimeout(() => {
177
- this.pendingRequests.delete(id);
178
- streaming.fail(new Error(`Streaming command '${method}' timed out after ${timeoutMs}ms`));
237
+ this.settlePendingRequest(id, {
238
+ outcome: 'reject',
239
+ error: new Error(`Streaming command '${method}' timed out after ${timeoutMs}ms`),
240
+ });
179
241
  }, timeoutMs);
180
242
  this.pendingRequests.set(id, {
181
243
  resolve: (result) => streaming.finish(result),
182
244
  reject: (error) => streaming.fail(error),
183
245
  timer,
246
+ method,
184
247
  streaming,
185
248
  });
186
- this.transport.write(json + '\n').catch((err) => {
187
- clearTimeout(timer);
188
- this.pendingRequests.delete(id);
189
- streaming.fail(new Error(`Failed to write to transport: ${err.message}`));
190
- });
249
+ try {
250
+ void this.transport.write(json + '\n').catch((err) => {
251
+ this.settlePendingRequest(id, {
252
+ outcome: 'reject',
253
+ error: new Error(`Failed to write to transport: ${this.getErrorMessage(err)}`),
254
+ });
255
+ });
256
+ }
257
+ catch (err) {
258
+ this.settlePendingRequest(id, {
259
+ outcome: 'reject',
260
+ error: new Error(`Failed to write to transport: ${this.getErrorMessage(err)}`),
261
+ });
262
+ }
191
263
  return streaming;
192
264
  }
193
265
  /**
@@ -201,11 +273,7 @@ export class RustBridge extends plugins.events.EventEmitter {
201
273
  this.transport = null;
202
274
  this.isRunning = false;
203
275
  // Reject pending requests
204
- for (const [, pending] of this.pendingRequests) {
205
- clearTimeout(pending.timer);
206
- pending.reject(new Error(`${this.options.binaryName} process killed`));
207
- }
208
- this.pendingRequests.clear();
276
+ this.rejectAllPendingRequests(`${this.options.binaryName} process killed`);
209
277
  transport.removeAllListeners();
210
278
  transport.disconnect();
211
279
  }
@@ -238,11 +306,15 @@ export class RustBridge extends plugins.events.EventEmitter {
238
306
  const pending = this.pendingRequests.get(parsed.id);
239
307
  if (pending?.streaming) {
240
308
  // Reset inactivity timeout
241
- clearTimeout(pending.timer);
309
+ if (pending.timer) {
310
+ clearTimeout(pending.timer);
311
+ }
242
312
  const timeoutMs = this.options.streamTimeoutMs ?? this.options.requestTimeoutMs;
243
313
  pending.timer = setTimeout(() => {
244
- this.pendingRequests.delete(parsed.id);
245
- pending.reject(new Error(`Streaming command timed out after ${timeoutMs}ms of inactivity`));
314
+ this.settlePendingRequest(parsed.id, {
315
+ outcome: 'reject',
316
+ error: new Error(`Streaming command timed out after ${timeoutMs}ms of inactivity`),
317
+ });
246
318
  }, timeoutMs);
247
319
  pending.streaming.pushChunk(parsed.data);
248
320
  }
@@ -253,26 +325,72 @@ export class RustBridge extends plugins.events.EventEmitter {
253
325
  const response = parsed;
254
326
  const pending = this.pendingRequests.get(response.id);
255
327
  if (pending) {
256
- clearTimeout(pending.timer);
257
- this.pendingRequests.delete(response.id);
258
328
  if (response.success) {
259
- pending.resolve(response.result);
329
+ this.settlePendingRequest(response.id, {
330
+ outcome: 'resolve',
331
+ value: response.result,
332
+ });
260
333
  }
261
334
  else {
262
- pending.reject(new Error(response.error || 'Unknown error from Rust process'));
335
+ const message = response.error || 'Unknown error from Rust process';
336
+ this.settlePendingRequest(response.id, {
337
+ outcome: 'reject',
338
+ error: pending.streaming
339
+ ? new Error(message)
340
+ : new RustBridgeRequestError(message, {
341
+ code: 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE',
342
+ requestId: response.id,
343
+ method: pending.method,
344
+ }),
345
+ });
263
346
  }
264
347
  }
265
348
  }
266
349
  }
267
- cleanup() {
268
- this.isRunning = false;
269
- this.transport = null;
270
- // Reject all pending requests
271
- for (const [, pending] of this.pendingRequests) {
350
+ settlePendingRequest(id, settlement) {
351
+ const pending = this.pendingRequests.get(id);
352
+ if (!pending) {
353
+ return false;
354
+ }
355
+ this.pendingRequests.delete(id);
356
+ if (pending.timer) {
272
357
  clearTimeout(pending.timer);
273
- pending.reject(new Error(`${this.options.binaryName} process exited`));
358
+ pending.timer = null;
274
359
  }
275
- this.pendingRequests.clear();
360
+ if (pending.abortSignal && pending.abortHandler) {
361
+ pending.abortSignal.removeEventListener('abort', pending.abortHandler);
362
+ pending.abortHandler = undefined;
363
+ }
364
+ if (settlement.outcome === 'resolve') {
365
+ pending.resolve(settlement.value);
366
+ }
367
+ else {
368
+ pending.reject(settlement.error);
369
+ }
370
+ return true;
371
+ }
372
+ rejectAllPendingRequests(message, cause) {
373
+ for (const [id, pending] of [...this.pendingRequests]) {
374
+ this.settlePendingRequest(id, {
375
+ outcome: 'reject',
376
+ error: pending.streaming
377
+ ? new Error(message)
378
+ : new RustBridgeRequestError(message, {
379
+ code: 'ERR_RUST_BRIDGE_REQUEST_TRANSPORT',
380
+ requestId: id,
381
+ method: pending.method,
382
+ cause,
383
+ }),
384
+ });
385
+ }
386
+ }
387
+ cleanup(message, cause) {
388
+ this.isRunning = false;
389
+ this.transport = null;
390
+ this.rejectAllPendingRequests(message, cause);
391
+ }
392
+ getErrorMessage(error) {
393
+ return error instanceof Error ? error.message : String(error);
276
394
  }
277
395
  }
278
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ydXN0YnJpZGdlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5ydXN0YnJpZGdlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUM3RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFjL0QsTUFBTSxhQUFhLEdBQXNCO0lBQ3ZDLEdBQUcsS0FBSSxDQUFDO0NBQ1QsQ0FBQztBQUVGOzs7Ozs7R0FNRztBQUNILE1BQU0sT0FBTyxVQUF3RCxTQUFRLE9BQU8sQ0FBQyxNQUFNLENBQUMsWUFBWTtJQUM5RixPQUFPLENBQW9CO0lBQzNCLE9BQU8sQ0FBbUo7SUFDMUosTUFBTSxDQUFvQjtJQUMxQixTQUFTLEdBQTBCLElBQUksQ0FBQztJQUN4QyxlQUFlLEdBQUcsSUFBSSxHQUFHLEVBSzdCLENBQUM7SUFDRyxjQUFjLEdBQUcsQ0FBQyxDQUFDO0lBQ25CLFNBQVMsR0FBRyxLQUFLLENBQUM7SUFDbEIsVUFBVSxHQUFrQixJQUFJLENBQUM7SUFFekMsWUFBWSxPQUEyQjtRQUNyQyxLQUFLLEVBQUUsQ0FBQztRQUNSLElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sSUFBSSxhQUFhLENBQUM7UUFDOUMsSUFBSSxDQUFDLE9BQU8sR0FBRztZQUNiLE9BQU8sRUFBRSxDQUFDLGNBQWMsQ0FBQztZQUN6QixnQkFBZ0IsRUFBRSxLQUFLO1lBQ3ZCLGNBQWMsRUFBRSxLQUFLO1lBQ3JCLGNBQWMsRUFBRSxPQUFPO1lBQ3ZCLGNBQWMsRUFBRSxFQUFFLEdBQUcsSUFBSSxHQUFHLElB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
396
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ydXN0YnJpZGdlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5ydXN0YnJpZGdlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBQzdFLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUM3RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFlL0QsTUFBTSxhQUFhLEdBQXNCO0lBQ3ZDLEdBQUcsS0FBSSxDQUFDO0NBQ1QsQ0FBQztBQWdCRjs7Ozs7O0dBTUc7QUFDSCxNQUFNLE9BQU8sVUFBd0QsU0FBUSxPQUFPLENBQUMsTUFBTSxDQUFDLFlBQVk7SUFDOUYsT0FBTyxDQUFvQjtJQUMzQixPQUFPLENBQW1KO0lBQzFKLE1BQU0sQ0FBb0I7SUFDMUIsU0FBUyxHQUEwQixJQUFJLENBQUM7SUFDeEMsZUFBZSxHQUFHLElBQUksR0FBRyxFQUEyQixDQUFDO0lBQ3JELGNBQWMsR0FBRyxDQUFDLENBQUM7SUFDbkIsU0FBUyxHQUFHLEtBQUssQ0FBQztJQUNsQixVQUFVLEdBQWtCLElBQUksQ0FBQztJQUV6QyxZQUFZLE9BQTJCO1FBQ3JDLEtBQUssRUFBRSxDQUFDO1FBQ1IsSUFBSSxDQUFDLE1BQU0sR0FBRyxPQUFPLENBQUMsTUFBTSxJQUFJLGFBQWEsQ0FBQztRQUM5QyxJQUFJLENBQUMsT0FBTyxHQUFHO1lBQ2IsT0FBTyxFQUFFLENBQUMsY0FBYyxDQUFDO1lBQ3pCLGdCQUFnQixFQUFFLEtBQUs7WUFDdkIsY0FBYyxFQUFFLEtBQUs7WUFDckIsY0FBYyxF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
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Stable error codes for failures while waiting for a Rust bridge request.
3
+ */
4
+ export type TRustBridgeRequestErrorCode = 'ERR_RUST_BRIDGE_REQUEST_TIMEOUT' | 'ERR_RUST_BRIDGE_REQUEST_ABORTED' | 'ERR_RUST_BRIDGE_REQUEST_WRITE' | 'ERR_RUST_BRIDGE_REQUEST_TRANSPORT' | 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE';
5
+ export interface IRustBridgeRequestErrorOptions {
6
+ code: TRustBridgeRequestErrorCode;
7
+ requestId?: string;
8
+ method?: string;
9
+ cause?: unknown;
10
+ }
11
+ /**
12
+ * Error raised while preparing or waiting for a Rust bridge request.
13
+ *
14
+ * Existing error messages are preserved while `code` provides a stable way to
15
+ * distinguish timeout, cancellation, transport, write, and Rust response
16
+ * failures.
17
+ */
18
+ export declare class RustBridgeRequestError extends Error {
19
+ readonly code: TRustBridgeRequestErrorCode;
20
+ readonly requestId?: string;
21
+ readonly method?: string;
22
+ constructor(message: string, options: IRustBridgeRequestErrorOptions);
23
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Error raised while preparing or waiting for a Rust bridge request.
3
+ *
4
+ * Existing error messages are preserved while `code` provides a stable way to
5
+ * distinguish timeout, cancellation, transport, write, and Rust response
6
+ * failures.
7
+ */
8
+ export class RustBridgeRequestError extends Error {
9
+ code;
10
+ requestId;
11
+ method;
12
+ constructor(message, options) {
13
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
14
+ this.name = 'RustBridgeRequestError';
15
+ this.code = options.code;
16
+ this.requestId = options.requestId;
17
+ this.method = options.method;
18
+ }
19
+ }
20
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ydXN0YnJpZGdlcmVxdWVzdGVycm9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5ydXN0YnJpZGdlcmVxdWVzdGVycm9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWlCQTs7Ozs7O0dBTUc7QUFDSCxNQUFNLE9BQU8sc0JBQXVCLFNBQVEsS0FBSztJQUMvQixJQUFJLENBQThCO0lBQ2xDLFNBQVMsQ0FBVTtJQUNuQixNQUFNLENBQVU7SUFFaEMsWUFBWSxPQUFlLEVBQUUsT0FBdUM7UUFDbEUsS0FBSyxDQUFDLE9BQU8sRUFBRSxPQUFPLENBQUMsS0FBSyxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxPQUFPLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQztRQUNuRixJQUFJLENBQUMsSUFBSSxHQUFHLHdCQUF3QixDQUFDO1FBQ3JDLElBQUksQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQztRQUN6QixJQUFJLENBQUMsU0FBUyxHQUFHLE9BQU8sQ0FBQyxTQUFTLENBQUM7UUFDbkMsSUFBSSxDQUFDLE1BQU0sR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDO0lBQy9CLENBQUM7Q0FDRiJ9
@@ -1,4 +1,5 @@
1
1
  export { RustBridge } from './classes.rustbridge.js';
2
+ export { RustBridgeRequestError, type IRustBridgeRequestErrorOptions, type TRustBridgeRequestErrorCode, } from './classes.rustbridgerequesterror.js';
2
3
  export { RustBinaryLocator } from './classes.rustbinarylocator.js';
3
4
  export { StreamingResponse } from './classes.streamingresponse.js';
4
5
  export { StdioTransport } from './classes.stdiotransport.js';
package/dist_ts/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  export { RustBridge } from './classes.rustbridge.js';
2
+ export { RustBridgeRequestError, } from './classes.rustbridgerequesterror.js';
2
3
  export { RustBinaryLocator } from './classes.rustbinarylocator.js';
3
4
  export { StreamingResponse } from './classes.streamingresponse.js';
4
5
  export { StdioTransport } from './classes.stdiotransport.js';
5
6
  export { SocketTransport } from './classes.sockettransport.js';
6
7
  export { LineScanner } from './classes.linescanner.js';
7
8
  export * from './interfaces/index.js';
8
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDckQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDbkUsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDbkUsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQzdELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUMvRCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDdkQsY0FBYyx1QkFBdUIsQ0FBQyJ9
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDckQsT0FBTyxFQUNMLHNCQUFzQixHQUd2QixNQUFNLHFDQUFxQyxDQUFDO0FBQzdDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUM3RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDL0QsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3ZELGNBQWMsdUJBQXVCLENBQUMifQ==
@@ -4,6 +4,15 @@
4
4
  export interface IRustBridgeLogger {
5
5
  log(level: string, message: string, data?: Record<string, any>): void;
6
6
  }
7
+ /**
8
+ * Options for one non-streaming Rust bridge request.
9
+ */
10
+ export interface IRustBridgeRequestOptions {
11
+ /** Abort only the local wait for this request. */
12
+ signal?: AbortSignal;
13
+ /** Override the bridge request timeout for this call, in milliseconds. */
14
+ timeoutMs?: number;
15
+ }
7
16
  /**
8
17
  * Options for locating a Rust binary.
9
18
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartrust",
3
- "version": "1.4.0",
3
+ "version": "1.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",
@@ -8,16 +8,11 @@
8
8
  "type": "module",
9
9
  "author": "Task Venture Capital GmbH",
10
10
  "license": "MIT",
11
- "scripts": {
12
- "test": "(tstest test/ --verbose --logfile --timeout 60)",
13
- "build": "(tsbuild tsfolders)",
14
- "buildDocs": "(tsdoc)"
15
- },
16
11
  "devDependencies": {
17
- "@git.zone/tsbuild": "^4.4.0",
18
- "@git.zone/tsrun": "^2.0.2",
19
- "@git.zone/tstest": "^3.6.3",
20
- "@types/node": "^25.6.0"
12
+ "@git.zone/tsbuild": "^4.4.2",
13
+ "@git.zone/tsrun": "^2.0.5",
14
+ "@git.zone/tstest": "^3.6.7",
15
+ "@types/node": "^26.1.1"
21
16
  },
22
17
  "dependencies": {
23
18
  "@push.rocks/smartpath": "^6.0.0"
@@ -51,5 +46,9 @@
51
46
  "bugs": {
52
47
  "url": "https://code.foss.global/push.rocks/smartrust/issues"
53
48
  },
54
- "packageManager": "pnpm@10.28.2"
55
- }
49
+ "scripts": {
50
+ "test": "(tstest test/ --verbose --logfile --timeout 60)",
51
+ "build": "(tsbuild tsfolders)",
52
+ "buildDocs": "(tsdoc)"
53
+ }
54
+ }
package/readme.md CHANGED
@@ -103,6 +103,57 @@ bridge.on('management:configChanged', (data) => {
103
103
  bridge.kill();
104
104
  ```
105
105
 
106
+ ### Per-Command Deadlines and Cancellation
107
+
108
+ Non-streaming commands accept an optional `IRustBridgeRequestOptions` argument:
109
+
110
+ ```typescript
111
+ import { RustBridgeRequestError } from '@push.rocks/smartrust';
112
+
113
+ const controller = new AbortController();
114
+
115
+ try {
116
+ const metrics = await bridge.sendCommand(
117
+ 'getMetrics',
118
+ {},
119
+ {
120
+ timeoutMs: 2000,
121
+ signal: controller.signal,
122
+ },
123
+ );
124
+ } catch (error) {
125
+ if (error instanceof RustBridgeRequestError) {
126
+ console.error(error.code, error.message);
127
+ }
128
+ }
129
+ ```
130
+
131
+ `timeoutMs` must be a positive safe integer. When it is omitted, the bridge's
132
+ constructor-level `requestTimeoutMs` remains the default. An already-aborted
133
+ signal rejects before a request ID is allocated or anything is written.
134
+
135
+ Timeout and abort only abandon the local TypeScript wait. They do not kill or
136
+ disconnect the shared bridge, late responses are ignored, and later commands
137
+ can continue using the same bridge.
138
+
139
+ > The JSON-lines protocol has no cancellation message. After a command has
140
+ > been written, Rust may still execute it even if the local wait times out or
141
+ > is aborted. Do not blindly retry mutating commands; first establish whether
142
+ > the original command executed or design the command to be idempotent.
143
+
144
+ Timeout, cancellation, transport-write/exit, and Rust response failures use
145
+ `RustBridgeRequestError`.
146
+ Existing message text remains available, while `code` is stable for
147
+ programmatic handling:
148
+
149
+ | Code | Meaning |
150
+ |---|---|
151
+ | `ERR_RUST_BRIDGE_REQUEST_TIMEOUT` | The local request deadline elapsed |
152
+ | `ERR_RUST_BRIDGE_REQUEST_ABORTED` | The caller's signal aborted the local wait |
153
+ | `ERR_RUST_BRIDGE_REQUEST_WRITE` | The transport rejected the request write |
154
+ | `ERR_RUST_BRIDGE_REQUEST_TRANSPORT` | The transport exited, disconnected, or was killed |
155
+ | `ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE` | Rust returned `success: false` |
156
+
106
157
  ### Socket Mode — Connect to a Running Daemon 🔗
107
158
 
108
159
  When the Rust binary runs as a system service (e.g., via `systemd`, `launchd`, or a Windows Service), use `connect()` to talk to it over a Unix socket or named pipe:
@@ -282,7 +333,7 @@ const bridge = new RustBridge<TMyCommands>({
282
333
 
283
334
  // --- Bridge Options ---
284
335
  cliArgs: ['--management'], // optional: args passed to binary (default: ['--management'])
285
- requestTimeoutMs: 30000, // optional: per-request timeout (default: 30000)
336
+ requestTimeoutMs: 30000, // optional: default request timeout (default: 30000)
286
337
  streamTimeoutMs: 30000, // optional: streaming inactivity timeout (default: requestTimeoutMs)
287
338
  readyTimeoutMs: 10000, // optional: ready event timeout (default: 10000)
288
339
  maxPayloadSize: 50 * 1024 * 1024, // optional: max message size in bytes (default: 50MB)
@@ -486,7 +537,7 @@ fn main() {
486
537
  | `constructor` | `new RustBridge<T>(options: IRustBridgeOptions)` | Create a new bridge instance |
487
538
  | `spawn()` | `Promise<boolean>` | **Stdio mode**: Spawn the binary and wait for ready; returns `false` on failure |
488
539
  | `connect(socketPath, options?)` | `Promise<boolean>` | **Socket mode**: Connect to a running daemon; returns `false` on failure |
489
- | `sendCommand(method, params)` | `Promise<TCommands[K]['result']>` | Send a typed command and await the response |
540
+ | `sendCommand(method, params, options?)` | `Promise<TCommands[K]['result']>` | Send a typed command with an optional per-call timeout or abort signal |
490
541
  | `sendCommandStreaming(method, params)` | `StreamingResponse<TChunk, TResult>` | Send a streaming command; returns immediately |
491
542
  | `kill()` | `void` | Stdio: SIGTERM the process, SIGKILL after 5s. Socket: close the connection (daemon stays alive) |
492
543
  | `running` | `boolean` | Whether the bridge is currently connected and ready |
@@ -539,6 +590,7 @@ fn main() {
539
590
  | Interface / Type | Description |
540
591
  |---|---|
541
592
  | `IRustBridgeOptions` | Full configuration for `RustBridge` |
593
+ | `IRustBridgeRequestOptions` | Per-command options: `{ signal?, timeoutMs? }` |
542
594
  | `IBinaryLocatorOptions` | Configuration for `RustBinaryLocator` |
543
595
  | `ISocketConnectOptions` | Socket connection options (reconnect settings) |
544
596
  | `IRustBridgeLogger` | Logger interface: `{ log(level, message, data?) }` |
@@ -551,6 +603,8 @@ fn main() {
551
603
  | `TCommandMap` | `Record<string, ICommandDefinition>` |
552
604
  | `TStreamingCommandKeys<T>` | Extracts keys from a command map that have a `chunk` field |
553
605
  | `TExtractChunk<T>` | Extracts the chunk type from a streaming command definition |
606
+ | `RustBridgeRequestError` | Request error class with stable `code`, `requestId`, and `method` fields |
607
+ | `TRustBridgeRequestErrorCode` | Union of stable request error code strings |
554
608
 
555
609
  ## License and Legal Information
556
610
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartrust',
6
- version: '1.4.0',
6
+ version: '1.5.0',
7
7
  description: 'A type-safe bridge between JavaScript engines and Rust binaries.'
8
8
  }
@@ -1,10 +1,12 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import { RustBinaryLocator } from './classes.rustbinarylocator.js';
3
+ import { RustBridgeRequestError } from './classes.rustbridgerequesterror.js';
3
4
  import { StreamingResponse } from './classes.streamingresponse.js';
4
5
  import { StdioTransport } from './classes.stdiotransport.js';
5
6
  import { SocketTransport } from './classes.sockettransport.js';
6
7
  import type {
7
8
  IRustBridgeOptions,
9
+ IRustBridgeRequestOptions,
8
10
  IRustBridgeLogger,
9
11
  ISocketConnectOptions,
10
12
  TCommandMap,
@@ -20,6 +22,20 @@ const defaultLogger: IRustBridgeLogger = {
20
22
  log() {},
21
23
  };
22
24
 
25
+ interface IPendingRequest {
26
+ resolve: (value: any) => void;
27
+ reject: (error: Error) => void;
28
+ timer: ReturnType<typeof setTimeout> | null;
29
+ method: string;
30
+ abortSignal?: AbortSignal;
31
+ abortHandler?: () => void;
32
+ streaming?: StreamingResponse<any, any>;
33
+ }
34
+
35
+ type TPendingRequestSettlement =
36
+ | { outcome: 'resolve'; value: any }
37
+ | { outcome: 'reject'; error: Error };
38
+
23
39
  /**
24
40
  * Generic bridge between TypeScript and a Rust binary.
25
41
  * Communicates via JSON-over-stdin/stdout IPC protocol (stdio mode)
@@ -32,12 +48,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
32
48
  private options: Required<Pick<IRustBridgeOptions, 'cliArgs' | 'requestTimeoutMs' | 'readyTimeoutMs' | 'readyEventName' | 'maxPayloadSize'>> & IRustBridgeOptions;
33
49
  private logger: IRustBridgeLogger;
34
50
  private transport: IRustTransport | null = null;
35
- private pendingRequests = new Map<string, {
36
- resolve: (value: any) => void;
37
- reject: (error: Error) => void;
38
- timer: ReturnType<typeof setTimeout>;
39
- streaming?: StreamingResponse<any, any>;
40
- }>();
51
+ private pendingRequests = new Map<string, IPendingRequest>();
41
52
  private requestCounter = 0;
42
53
  private isRunning = false;
43
54
  private binaryPath: string | null = null;
@@ -116,13 +127,13 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
116
127
 
117
128
  transport.on('close', (...args: any[]) => {
118
129
  this.logger.log('info', `Transport closed`);
119
- this.cleanup();
130
+ this.cleanup(`${this.options.binaryName} process exited`);
120
131
  this.emit('exit', ...args);
121
132
  });
122
133
 
123
134
  transport.on('error', (err: Error) => {
124
135
  this.logger.log('error', `Transport error: ${err.message}`);
125
- this.cleanup();
136
+ this.cleanup(`${this.options.binaryName} process exited`, err);
126
137
  resolve(false);
127
138
  });
128
139
 
@@ -164,11 +175,32 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
164
175
  public async sendCommand<K extends string & keyof TCommands>(
165
176
  method: K,
166
177
  params: TCommands[K]['params'],
178
+ options?: IRustBridgeRequestOptions,
167
179
  ): Promise<TCommands[K]['result']> {
180
+ const timeoutMs = options?.timeoutMs ?? this.options.requestTimeoutMs;
181
+ if (
182
+ options?.timeoutMs !== undefined
183
+ && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0)
184
+ ) {
185
+ throw new RangeError('RustBridge request timeoutMs must be a positive safe integer');
186
+ }
187
+
188
+ if (options?.signal?.aborted) {
189
+ throw new RustBridgeRequestError(
190
+ `Command '${method}' was aborted before it was sent`,
191
+ {
192
+ code: 'ERR_RUST_BRIDGE_REQUEST_ABORTED',
193
+ method,
194
+ cause: options.signal.reason,
195
+ },
196
+ );
197
+ }
198
+
168
199
  if (!this.transport?.connected || !this.isRunning) {
169
200
  throw new Error(`${this.options.binaryName} bridge is not running`);
170
201
  }
171
202
 
203
+ const transport = this.transport;
172
204
  const id = `req_${++this.requestCounter}`;
173
205
  const request: IManagementRequest = { id, method, params };
174
206
  const json = JSON.stringify(request);
@@ -182,18 +214,76 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
182
214
  }
183
215
 
184
216
  return new Promise<TCommands[K]['result']>((resolve, reject) => {
185
- const timer = setTimeout(() => {
186
- this.pendingRequests.delete(id);
187
- reject(new Error(`Command '${method}' timed out after ${this.options.requestTimeoutMs}ms`));
188
- }, this.options.requestTimeoutMs);
217
+ const pending: IPendingRequest = {
218
+ resolve,
219
+ reject,
220
+ timer: null,
221
+ method,
222
+ abortSignal: options?.signal,
223
+ };
224
+ this.pendingRequests.set(id, pending);
225
+
226
+ if (options?.signal) {
227
+ pending.abortHandler = () => {
228
+ this.settlePendingRequest(id, {
229
+ outcome: 'reject',
230
+ error: new RustBridgeRequestError(
231
+ `Command '${method}' was aborted`,
232
+ {
233
+ code: 'ERR_RUST_BRIDGE_REQUEST_ABORTED',
234
+ requestId: id,
235
+ method,
236
+ cause: options.signal?.reason,
237
+ },
238
+ ),
239
+ });
240
+ };
241
+ options.signal.addEventListener('abort', pending.abortHandler, { once: true });
242
+ }
189
243
 
190
- this.pendingRequests.set(id, { resolve, reject, timer });
244
+ pending.timer = setTimeout(() => {
245
+ this.settlePendingRequest(id, {
246
+ outcome: 'reject',
247
+ error: new RustBridgeRequestError(
248
+ `Command '${method}' timed out after ${timeoutMs}ms`,
249
+ {
250
+ code: 'ERR_RUST_BRIDGE_REQUEST_TIMEOUT',
251
+ requestId: id,
252
+ method,
253
+ },
254
+ ),
255
+ });
256
+ }, timeoutMs);
191
257
 
192
- this.transport!.write(json + '\n').catch((err) => {
193
- clearTimeout(timer);
194
- this.pendingRequests.delete(id);
195
- reject(new Error(`Failed to write to transport: ${err.message}`));
196
- });
258
+ try {
259
+ void transport.write(json + '\n').catch((err: unknown) => {
260
+ this.settlePendingRequest(id, {
261
+ outcome: 'reject',
262
+ error: new RustBridgeRequestError(
263
+ `Failed to write to transport: ${this.getErrorMessage(err)}`,
264
+ {
265
+ code: 'ERR_RUST_BRIDGE_REQUEST_WRITE',
266
+ requestId: id,
267
+ method,
268
+ cause: err,
269
+ },
270
+ ),
271
+ });
272
+ });
273
+ } catch (err: unknown) {
274
+ this.settlePendingRequest(id, {
275
+ outcome: 'reject',
276
+ error: new RustBridgeRequestError(
277
+ `Failed to write to transport: ${this.getErrorMessage(err)}`,
278
+ {
279
+ code: 'ERR_RUST_BRIDGE_REQUEST_WRITE',
280
+ requestId: id,
281
+ method,
282
+ cause: err,
283
+ },
284
+ ),
285
+ });
286
+ }
197
287
  });
198
288
  }
199
289
 
@@ -227,22 +317,33 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
227
317
 
228
318
  const timeoutMs = this.options.streamTimeoutMs ?? this.options.requestTimeoutMs;
229
319
  const timer = setTimeout(() => {
230
- this.pendingRequests.delete(id);
231
- streaming.fail(new Error(`Streaming command '${method}' timed out after ${timeoutMs}ms`));
320
+ this.settlePendingRequest(id, {
321
+ outcome: 'reject',
322
+ error: new Error(`Streaming command '${method}' timed out after ${timeoutMs}ms`),
323
+ });
232
324
  }, timeoutMs);
233
325
 
234
326
  this.pendingRequests.set(id, {
235
327
  resolve: (result: any) => streaming.finish(result),
236
328
  reject: (error: Error) => streaming.fail(error),
237
329
  timer,
330
+ method,
238
331
  streaming,
239
332
  });
240
333
 
241
- this.transport!.write(json + '\n').catch((err) => {
242
- clearTimeout(timer);
243
- this.pendingRequests.delete(id);
244
- streaming.fail(new Error(`Failed to write to transport: ${err.message}`));
245
- });
334
+ try {
335
+ void this.transport!.write(json + '\n').catch((err: unknown) => {
336
+ this.settlePendingRequest(id, {
337
+ outcome: 'reject',
338
+ error: new Error(`Failed to write to transport: ${this.getErrorMessage(err)}`),
339
+ });
340
+ });
341
+ } catch (err: unknown) {
342
+ this.settlePendingRequest(id, {
343
+ outcome: 'reject',
344
+ error: new Error(`Failed to write to transport: ${this.getErrorMessage(err)}`),
345
+ });
346
+ }
246
347
 
247
348
  return streaming;
248
349
  }
@@ -259,11 +360,7 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
259
360
  this.isRunning = false;
260
361
 
261
362
  // Reject pending requests
262
- for (const [, pending] of this.pendingRequests) {
263
- clearTimeout(pending.timer);
264
- pending.reject(new Error(`${this.options.binaryName} process killed`));
265
- }
266
- this.pendingRequests.clear();
363
+ this.rejectAllPendingRequests(`${this.options.binaryName} process killed`);
267
364
 
268
365
  transport.removeAllListeners();
269
366
  transport.disconnect();
@@ -300,11 +397,15 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
300
397
  const pending = this.pendingRequests.get(parsed.id);
301
398
  if (pending?.streaming) {
302
399
  // Reset inactivity timeout
303
- clearTimeout(pending.timer);
400
+ if (pending.timer) {
401
+ clearTimeout(pending.timer);
402
+ }
304
403
  const timeoutMs = this.options.streamTimeoutMs ?? this.options.requestTimeoutMs;
305
404
  pending.timer = setTimeout(() => {
306
- this.pendingRequests.delete(parsed.id);
307
- pending.reject(new Error(`Streaming command timed out after ${timeoutMs}ms of inactivity`));
405
+ this.settlePendingRequest(parsed.id, {
406
+ outcome: 'reject',
407
+ error: new Error(`Streaming command timed out after ${timeoutMs}ms of inactivity`),
408
+ });
308
409
  }, timeoutMs);
309
410
  pending.streaming.pushChunk(parsed.data);
310
411
  }
@@ -316,26 +417,85 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
316
417
  const response = parsed as IManagementResponse;
317
418
  const pending = this.pendingRequests.get(response.id);
318
419
  if (pending) {
319
- clearTimeout(pending.timer);
320
- this.pendingRequests.delete(response.id);
321
420
  if (response.success) {
322
- pending.resolve(response.result);
421
+ this.settlePendingRequest(response.id, {
422
+ outcome: 'resolve',
423
+ value: response.result,
424
+ });
323
425
  } else {
324
- pending.reject(new Error(response.error || 'Unknown error from Rust process'));
426
+ const message = response.error || 'Unknown error from Rust process';
427
+ this.settlePendingRequest(response.id, {
428
+ outcome: 'reject',
429
+ error: pending.streaming
430
+ ? new Error(message)
431
+ : new RustBridgeRequestError(
432
+ message,
433
+ {
434
+ code: 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE',
435
+ requestId: response.id,
436
+ method: pending.method,
437
+ },
438
+ ),
439
+ });
325
440
  }
326
441
  }
327
442
  }
328
443
  }
329
444
 
330
- private cleanup(): void {
331
- this.isRunning = false;
332
- this.transport = null;
445
+ private settlePendingRequest(
446
+ id: string,
447
+ settlement: TPendingRequestSettlement,
448
+ ): boolean {
449
+ const pending = this.pendingRequests.get(id);
450
+ if (!pending) {
451
+ return false;
452
+ }
333
453
 
334
- // Reject all pending requests
335
- for (const [, pending] of this.pendingRequests) {
454
+ this.pendingRequests.delete(id);
455
+ if (pending.timer) {
336
456
  clearTimeout(pending.timer);
337
- pending.reject(new Error(`${this.options.binaryName} process exited`));
457
+ pending.timer = null;
458
+ }
459
+ if (pending.abortSignal && pending.abortHandler) {
460
+ pending.abortSignal.removeEventListener('abort', pending.abortHandler);
461
+ pending.abortHandler = undefined;
462
+ }
463
+
464
+ if (settlement.outcome === 'resolve') {
465
+ pending.resolve(settlement.value);
466
+ } else {
467
+ pending.reject(settlement.error);
338
468
  }
339
- this.pendingRequests.clear();
469
+ return true;
470
+ }
471
+
472
+ private rejectAllPendingRequests(message: string, cause?: unknown): void {
473
+ for (const [id, pending] of [...this.pendingRequests]) {
474
+ this.settlePendingRequest(id, {
475
+ outcome: 'reject',
476
+ error: pending.streaming
477
+ ? new Error(message)
478
+ : new RustBridgeRequestError(
479
+ message,
480
+ {
481
+ code: 'ERR_RUST_BRIDGE_REQUEST_TRANSPORT',
482
+ requestId: id,
483
+ method: pending.method,
484
+ cause,
485
+ },
486
+ ),
487
+ });
488
+ }
489
+ }
490
+
491
+ private cleanup(message: string, cause?: unknown): void {
492
+ this.isRunning = false;
493
+ this.transport = null;
494
+
495
+ this.rejectAllPendingRequests(message, cause);
496
+ }
497
+
498
+ private getErrorMessage(error: unknown): string {
499
+ return error instanceof Error ? error.message : String(error);
340
500
  }
341
501
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Stable error codes for failures while waiting for a Rust bridge request.
3
+ */
4
+ export type TRustBridgeRequestErrorCode =
5
+ | 'ERR_RUST_BRIDGE_REQUEST_TIMEOUT'
6
+ | 'ERR_RUST_BRIDGE_REQUEST_ABORTED'
7
+ | 'ERR_RUST_BRIDGE_REQUEST_WRITE'
8
+ | 'ERR_RUST_BRIDGE_REQUEST_TRANSPORT'
9
+ | 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE';
10
+
11
+ export interface IRustBridgeRequestErrorOptions {
12
+ code: TRustBridgeRequestErrorCode;
13
+ requestId?: string;
14
+ method?: string;
15
+ cause?: unknown;
16
+ }
17
+
18
+ /**
19
+ * Error raised while preparing or waiting for a Rust bridge request.
20
+ *
21
+ * Existing error messages are preserved while `code` provides a stable way to
22
+ * distinguish timeout, cancellation, transport, write, and Rust response
23
+ * failures.
24
+ */
25
+ export class RustBridgeRequestError extends Error {
26
+ public readonly code: TRustBridgeRequestErrorCode;
27
+ public readonly requestId?: string;
28
+ public readonly method?: string;
29
+
30
+ constructor(message: string, options: IRustBridgeRequestErrorOptions) {
31
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
32
+ this.name = 'RustBridgeRequestError';
33
+ this.code = options.code;
34
+ this.requestId = options.requestId;
35
+ this.method = options.method;
36
+ }
37
+ }
package/ts/index.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  export { RustBridge } from './classes.rustbridge.js';
2
+ export {
3
+ RustBridgeRequestError,
4
+ type IRustBridgeRequestErrorOptions,
5
+ type TRustBridgeRequestErrorCode,
6
+ } from './classes.rustbridgerequesterror.js';
2
7
  export { RustBinaryLocator } from './classes.rustbinarylocator.js';
3
8
  export { StreamingResponse } from './classes.streamingresponse.js';
4
9
  export { StdioTransport } from './classes.stdiotransport.js';
@@ -5,6 +5,16 @@ export interface IRustBridgeLogger {
5
5
  log(level: string, message: string, data?: Record<string, any>): void;
6
6
  }
7
7
 
8
+ /**
9
+ * Options for one non-streaming Rust bridge request.
10
+ */
11
+ export interface IRustBridgeRequestOptions {
12
+ /** Abort only the local wait for this request. */
13
+ signal?: AbortSignal;
14
+ /** Override the bridge request timeout for this call, in milliseconds. */
15
+ timeoutMs?: number;
16
+ }
17
+
8
18
  /**
9
19
  * Options for locating a Rust binary.
10
20
  */