@push.rocks/smartrust 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.rustbridge.d.ts +19 -8
- package/dist_ts/classes.rustbridge.js +307 -78
- package/dist_ts/classes.sockettransport.d.ts +16 -2
- package/dist_ts/classes.sockettransport.js +239 -61
- package/dist_ts/classes.stdiotransport.d.ts +15 -3
- package/dist_ts/classes.stdiotransport.js +268 -59
- package/dist_ts/interfaces/config.d.ts +8 -0
- package/dist_ts/interfaces/transport.d.ts +9 -0
- package/package.json +1 -1
- package/readme.md +35 -5
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.rustbridge.ts +368 -83
- package/ts/classes.sockettransport.ts +312 -60
- package/ts/classes.stdiotransport.ts +362 -58
- package/ts/interfaces/config.ts +9 -0
- package/ts/interfaces/transport.ts +12 -0
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import { LineScanner } from './classes.linescanner.js';
|
|
3
3
|
import type { IRustBridgeLogger } from './interfaces/index.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
IRustTransport,
|
|
6
|
+
IRustTransportDisconnectOptions,
|
|
7
|
+
} from './interfaces/transport.js';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_DISCONNECT_GRACE_PERIOD_MS = 5000;
|
|
10
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
5
11
|
|
|
6
12
|
export interface IStdioTransportOptions {
|
|
7
13
|
binaryPath: string;
|
|
@@ -11,6 +17,21 @@ export interface IStdioTransportOptions {
|
|
|
11
17
|
logger: IRustBridgeLogger;
|
|
12
18
|
}
|
|
13
19
|
|
|
20
|
+
interface IStdioTransportGeneration {
|
|
21
|
+
childProcess: plugins.childProcess.ChildProcess;
|
|
22
|
+
stderrRemainder: string;
|
|
23
|
+
exited: boolean;
|
|
24
|
+
closed: boolean;
|
|
25
|
+
closePromise: Promise<void>;
|
|
26
|
+
resolveClose: () => void;
|
|
27
|
+
disconnectPromise: Promise<void> | null;
|
|
28
|
+
rejectDisconnect: ((error: Error) => void) | null;
|
|
29
|
+
forceKillTimer: ReturnType<typeof setTimeout> | null;
|
|
30
|
+
sigtermAttempted: boolean;
|
|
31
|
+
sigkillAttempted: boolean;
|
|
32
|
+
sigtermError: Error | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
14
35
|
/**
|
|
15
36
|
* Transport that spawns a child process and communicates via stdin/stdout.
|
|
16
37
|
* Extracted from the original RustBridge process management logic.
|
|
@@ -18,8 +39,8 @@ export interface IStdioTransportOptions {
|
|
|
18
39
|
export class StdioTransport extends plugins.events.EventEmitter implements IRustTransport {
|
|
19
40
|
private options: IStdioTransportOptions;
|
|
20
41
|
private childProcess: plugins.childProcess.ChildProcess | null = null;
|
|
42
|
+
private generation: IStdioTransportGeneration | null = null;
|
|
21
43
|
private lineScanner: LineScanner;
|
|
22
|
-
private stderrRemainder: string = '';
|
|
23
44
|
private _connected: boolean = false;
|
|
24
45
|
|
|
25
46
|
constructor(options: IStdioTransportOptions) {
|
|
@@ -35,22 +56,59 @@ export class StdioTransport extends plugins.events.EventEmitter implements IRust
|
|
|
35
56
|
/**
|
|
36
57
|
* Spawn the child process. Resolves when the process is running (not necessarily ready).
|
|
37
58
|
*/
|
|
38
|
-
public
|
|
59
|
+
public connect(): Promise<void> {
|
|
60
|
+
if (this.generation && !this.generation.closed) {
|
|
61
|
+
return Promise.reject(
|
|
62
|
+
new Error('Stdio transport cannot connect until the previous child has closed'),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
39
66
|
const env = this.options.env
|
|
40
67
|
? { ...process.env, ...this.options.env }
|
|
41
68
|
: { ...process.env };
|
|
42
69
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
70
|
+
let childProcess: plugins.childProcess.ChildProcess;
|
|
71
|
+
try {
|
|
72
|
+
childProcess = plugins.childProcess.spawn(
|
|
73
|
+
this.options.binaryPath,
|
|
74
|
+
this.options.cliArgs,
|
|
75
|
+
{ stdio: ['pipe', 'pipe', 'pipe'], env },
|
|
76
|
+
);
|
|
77
|
+
} catch (error: unknown) {
|
|
78
|
+
return Promise.reject(this.toError(error, 'Failed to spawn child process'));
|
|
79
|
+
}
|
|
48
80
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
let resolveClose!: () => void;
|
|
82
|
+
const closePromise = new Promise<void>((resolve) => {
|
|
83
|
+
resolveClose = resolve;
|
|
84
|
+
});
|
|
85
|
+
const generation: IStdioTransportGeneration = {
|
|
86
|
+
childProcess,
|
|
87
|
+
stderrRemainder: '',
|
|
88
|
+
exited: false,
|
|
89
|
+
closed: false,
|
|
90
|
+
closePromise,
|
|
91
|
+
resolveClose,
|
|
92
|
+
disconnectPromise: null,
|
|
93
|
+
rejectDisconnect: null,
|
|
94
|
+
forceKillTimer: null,
|
|
95
|
+
sigtermAttempted: false,
|
|
96
|
+
sigkillAttempted: false,
|
|
97
|
+
sigtermError: null,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
this.generation = generation;
|
|
101
|
+
this.childProcess = childProcess;
|
|
102
|
+
|
|
103
|
+
// Handle stderr with cross-chunk buffering.
|
|
104
|
+
childProcess.stderr?.on('data', (data: Buffer) => {
|
|
105
|
+
if (this.generation !== generation || generation.closed) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
generation.stderrRemainder += data.toString();
|
|
110
|
+
const lines = generation.stderrRemainder.split('\n');
|
|
111
|
+
generation.stderrRemainder = lines.pop()!;
|
|
54
112
|
for (const line of lines) {
|
|
55
113
|
const trimmed = line.trim();
|
|
56
114
|
if (trimmed) {
|
|
@@ -59,53 +117,125 @@ export class StdioTransport extends plugins.events.EventEmitter implements IRust
|
|
|
59
117
|
}
|
|
60
118
|
});
|
|
61
119
|
|
|
62
|
-
// Handle stdout via LineScanner
|
|
63
|
-
|
|
120
|
+
// Handle stdout via LineScanner.
|
|
121
|
+
childProcess.stdout!.on('data', (chunk: Buffer) => {
|
|
122
|
+
if (this.generation !== generation || generation.closed) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
64
126
|
this.lineScanner.push(chunk, (line) => {
|
|
65
|
-
this.
|
|
127
|
+
if (this.generation === generation && !generation.closed) {
|
|
128
|
+
this.emit('line', line);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
childProcess.on('exit', () => {
|
|
134
|
+
generation.exited = true;
|
|
135
|
+
if (this.generation === generation) {
|
|
136
|
+
this._connected = false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// A descendant may inherit the child's stdio descriptors and otherwise
|
|
140
|
+
// keep ChildProcess 'close' from ever firing after the owned child exits.
|
|
141
|
+
// Allow already-queued data events through this poll turn, then close our
|
|
142
|
+
// endpoints so confirmation cannot retain that pipe graph indefinitely.
|
|
143
|
+
setImmediate(() => {
|
|
144
|
+
if (generation.closed) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.destroyOwnedStdioEndpoint(generation, 'stdin');
|
|
148
|
+
this.destroyOwnedStdioEndpoint(generation, 'stdout');
|
|
149
|
+
this.destroyOwnedStdioEndpoint(generation, 'stderr');
|
|
66
150
|
});
|
|
67
151
|
});
|
|
68
152
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
153
|
+
// ChildProcess 'close' is the termination confirmation: the process has exited
|
|
154
|
+
// and its stdio streams have closed.
|
|
155
|
+
childProcess.on('close', (code: number | null, signal: NodeJS.Signals | null) => {
|
|
156
|
+
if (generation.closed) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Flush stderr while this generation is still considered active so a
|
|
161
|
+
// synchronous listener cannot start a replacement child mid-cleanup.
|
|
162
|
+
if (
|
|
163
|
+
this.generation === generation
|
|
164
|
+
&& generation.stderrRemainder.trim()
|
|
165
|
+
) {
|
|
166
|
+
this.emit('stderr', generation.stderrRemainder.trim());
|
|
74
167
|
}
|
|
168
|
+
generation.stderrRemainder = '';
|
|
169
|
+
generation.exited = true;
|
|
170
|
+
generation.closed = true;
|
|
171
|
+
if (generation.forceKillTimer) {
|
|
172
|
+
clearTimeout(generation.forceKillTimer);
|
|
173
|
+
generation.forceKillTimer = null;
|
|
174
|
+
}
|
|
175
|
+
generation.resolveClose();
|
|
176
|
+
|
|
177
|
+
if (this.generation !== generation) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
75
181
|
this._connected = false;
|
|
182
|
+
this.childProcess = null;
|
|
76
183
|
this.lineScanner.clear();
|
|
77
|
-
this.stderrRemainder = '';
|
|
78
184
|
this.emit('close', code, signal);
|
|
79
185
|
});
|
|
80
186
|
|
|
81
|
-
|
|
187
|
+
childProcess.on('error', (error: Error) => {
|
|
188
|
+
if (this.generation !== generation || generation.closed) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
82
192
|
this._connected = false;
|
|
83
|
-
|
|
193
|
+
if (generation.sigkillAttempted && generation.rejectDisconnect) {
|
|
194
|
+
generation.rejectDisconnect(
|
|
195
|
+
this.toError(error, 'Unable to confirm child termination after SIGKILL'),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (this.listenerCount('error') > 0) {
|
|
200
|
+
this.emit('error', error);
|
|
201
|
+
} else if (generation.disconnectPromise) {
|
|
202
|
+
this.options.logger.log('error', 'Stdio transport error during disconnect: ' + error.message);
|
|
203
|
+
} else {
|
|
204
|
+
this.emit('error', error);
|
|
205
|
+
}
|
|
84
206
|
});
|
|
85
207
|
|
|
86
208
|
this._connected = true;
|
|
209
|
+
return Promise.resolve();
|
|
87
210
|
}
|
|
88
211
|
|
|
89
212
|
/**
|
|
90
213
|
* Write data to stdin with backpressure support.
|
|
91
214
|
*/
|
|
92
|
-
public
|
|
215
|
+
public write(data: string): Promise<void> {
|
|
93
216
|
return new Promise<void>((resolve, reject) => {
|
|
94
|
-
|
|
217
|
+
const generation = this.generation;
|
|
218
|
+
if (
|
|
219
|
+
!generation
|
|
220
|
+
|| generation.closed
|
|
221
|
+
|| generation.childProcess !== this.childProcess
|
|
222
|
+
|| !this._connected
|
|
223
|
+
|| !generation.childProcess.stdin
|
|
224
|
+
) {
|
|
95
225
|
reject(new Error('stdin not available'));
|
|
96
226
|
return;
|
|
97
227
|
}
|
|
98
228
|
|
|
99
|
-
const canContinue =
|
|
100
|
-
if (
|
|
101
|
-
reject(
|
|
229
|
+
const canContinue = generation.childProcess.stdin.write(data, 'utf8', (error) => {
|
|
230
|
+
if (error) {
|
|
231
|
+
reject(error);
|
|
102
232
|
}
|
|
103
233
|
});
|
|
104
234
|
|
|
105
235
|
if (canContinue) {
|
|
106
236
|
resolve();
|
|
107
237
|
} else {
|
|
108
|
-
|
|
238
|
+
generation.childProcess.stdin.once('drain', () => {
|
|
109
239
|
resolve();
|
|
110
240
|
});
|
|
111
241
|
}
|
|
@@ -113,37 +243,211 @@ export class StdioTransport extends plugins.events.EventEmitter implements IRust
|
|
|
113
243
|
}
|
|
114
244
|
|
|
115
245
|
/**
|
|
116
|
-
* Kill the child process
|
|
246
|
+
* Kill the child process without waiting for confirmation.
|
|
117
247
|
*/
|
|
118
248
|
public disconnect(): void {
|
|
119
|
-
|
|
249
|
+
try {
|
|
250
|
+
void this.disconnectAndWait().catch((error: unknown) => {
|
|
251
|
+
this.options.logger.log(
|
|
252
|
+
'error',
|
|
253
|
+
'Failed to disconnect stdio transport: ' + this.getErrorMessage(error),
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
} catch (error: unknown) {
|
|
257
|
+
this.options.logger.log(
|
|
258
|
+
'error',
|
|
259
|
+
'Failed to disconnect stdio transport: ' + this.getErrorMessage(error),
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
120
263
|
|
|
121
|
-
|
|
122
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Send SIGTERM, escalate to SIGKILL after the grace period, and wait for
|
|
266
|
+
* ChildProcess 'close' to confirm termination.
|
|
267
|
+
*/
|
|
268
|
+
public disconnectAndWait(
|
|
269
|
+
options: IRustTransportDisconnectOptions = {},
|
|
270
|
+
): Promise<void> {
|
|
271
|
+
const gracePeriodMs = options.gracePeriodMs ?? DEFAULT_DISCONNECT_GRACE_PERIOD_MS;
|
|
272
|
+
if (!Number.isSafeInteger(gracePeriodMs) || gracePeriodMs < 0) {
|
|
273
|
+
throw new RangeError(
|
|
274
|
+
'StdioTransport gracePeriodMs must be a non-negative safe integer',
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const generation = this.generation;
|
|
279
|
+
if (!generation) {
|
|
280
|
+
return Promise.resolve();
|
|
281
|
+
}
|
|
282
|
+
if (generation.disconnectPromise) {
|
|
283
|
+
return generation.disconnectPromise;
|
|
284
|
+
}
|
|
285
|
+
if (generation.closed) {
|
|
286
|
+
generation.disconnectPromise = generation.closePromise;
|
|
287
|
+
return generation.disconnectPromise;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let rejectDisconnect!: (error: Error) => void;
|
|
291
|
+
const disconnectPromise = new Promise<void>((resolve, reject) => {
|
|
292
|
+
rejectDisconnect = reject;
|
|
293
|
+
void generation.closePromise.then(resolve);
|
|
294
|
+
});
|
|
295
|
+
generation.disconnectPromise = disconnectPromise;
|
|
296
|
+
generation.rejectDisconnect = rejectDisconnect;
|
|
123
297
|
this._connected = false;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
298
|
+
void disconnectPromise.then(
|
|
299
|
+
() => {
|
|
300
|
+
if (generation.disconnectPromise === disconnectPromise) {
|
|
301
|
+
generation.rejectDisconnect = null;
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
() => {
|
|
305
|
+
if (generation.disconnectPromise !== disconnectPromise) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (generation.forceKillTimer) {
|
|
309
|
+
clearTimeout(generation.forceKillTimer);
|
|
310
|
+
generation.forceKillTimer = null;
|
|
311
|
+
}
|
|
312
|
+
generation.disconnectPromise = null;
|
|
313
|
+
generation.rejectDisconnect = null;
|
|
314
|
+
generation.sigtermAttempted = false;
|
|
315
|
+
generation.sigkillAttempted = false;
|
|
316
|
+
generation.sigtermError = null;
|
|
317
|
+
},
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
if (this.hasExited(generation)) {
|
|
321
|
+
this.destroyOwnedStdioEndpoint(generation, 'stdin');
|
|
322
|
+
this.destroyOwnedStdioEndpoint(generation, 'stdout');
|
|
323
|
+
this.destroyOwnedStdioEndpoint(generation, 'stderr');
|
|
324
|
+
return disconnectPromise;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
generation.sigtermAttempted = true;
|
|
328
|
+
generation.sigtermError = this.sendSignal(generation, 'SIGTERM');
|
|
329
|
+
|
|
330
|
+
if (generation.closed || this.hasExited(generation)) {
|
|
331
|
+
return disconnectPromise;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (gracePeriodMs === 0) {
|
|
335
|
+
this.forceKill(generation);
|
|
336
|
+
} else {
|
|
337
|
+
this.scheduleForceKill(generation, gracePeriodMs);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return disconnectPromise;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private scheduleForceKill(
|
|
344
|
+
generation: IStdioTransportGeneration,
|
|
345
|
+
remainingMs: number,
|
|
346
|
+
): void {
|
|
347
|
+
if (generation.closed || this.hasExited(generation)) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const delayMs = Math.min(remainingMs, MAX_TIMER_DELAY_MS);
|
|
352
|
+
const scheduledAt = Date.now();
|
|
353
|
+
try {
|
|
354
|
+
generation.forceKillTimer = setTimeout(() => {
|
|
355
|
+
generation.forceKillTimer = null;
|
|
356
|
+
if (generation.closed || this.hasExited(generation)) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const elapsedMs = Math.max(1, Date.now() - scheduledAt);
|
|
361
|
+
const nextRemainingMs = remainingMs - elapsedMs;
|
|
362
|
+
if (nextRemainingMs > 0) {
|
|
363
|
+
this.scheduleForceKill(generation, nextRemainingMs);
|
|
364
|
+
} else {
|
|
365
|
+
this.forceKill(generation);
|
|
366
|
+
}
|
|
367
|
+
}, delayMs);
|
|
368
|
+
} catch (error: unknown) {
|
|
369
|
+
generation.rejectDisconnect?.(
|
|
370
|
+
this.toError(error, 'Unable to schedule SIGKILL escalation'),
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private forceKill(generation: IStdioTransportGeneration): void {
|
|
376
|
+
if (
|
|
377
|
+
generation.closed
|
|
378
|
+
|| generation.sigkillAttempted
|
|
379
|
+
|| this.hasExited(generation)
|
|
380
|
+
) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
generation.sigkillAttempted = true;
|
|
385
|
+
const sigkillError = this.sendSignal(generation, 'SIGKILL');
|
|
386
|
+
if (sigkillError && !generation.closed && !this.hasExited(generation)) {
|
|
387
|
+
const sigtermDetail = generation.sigtermError
|
|
388
|
+
? ' SIGTERM also failed: ' + generation.sigtermError.message + '.'
|
|
389
|
+
: '';
|
|
390
|
+
generation.rejectDisconnect?.(
|
|
391
|
+
new Error(sigkillError.message + '.' + sigtermDetail),
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private sendSignal(
|
|
397
|
+
generation: IStdioTransportGeneration,
|
|
398
|
+
signal: NodeJS.Signals,
|
|
399
|
+
): Error | null {
|
|
400
|
+
try {
|
|
401
|
+
const delivered = generation.childProcess.kill(signal);
|
|
402
|
+
if (delivered || generation.closed || this.hasExited(generation)) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
return new Error('Failed to send ' + signal + ' to child process');
|
|
406
|
+
} catch (error: unknown) {
|
|
407
|
+
if (generation.closed || this.hasExited(generation)) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
return this.toError(error, 'Failed to send ' + signal + ' to child process');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private hasExited(generation: IStdioTransportGeneration): boolean {
|
|
415
|
+
return (
|
|
416
|
+
generation.exited
|
|
417
|
+
|| generation.childProcess.exitCode !== null
|
|
418
|
+
|| generation.childProcess.signalCode !== null
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private destroyOwnedStdioEndpoint(
|
|
423
|
+
generation: IStdioTransportGeneration,
|
|
424
|
+
endpointName: 'stdin' | 'stdout' | 'stderr',
|
|
425
|
+
): void {
|
|
426
|
+
const endpoint = generation.childProcess[endpointName];
|
|
427
|
+
if (!endpoint || endpoint.destroyed) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
endpoint.destroy();
|
|
433
|
+
} catch (error: unknown) {
|
|
434
|
+
const endpointError = this.toError(
|
|
435
|
+
error,
|
|
436
|
+
'Failed to close inherited child ' + endpointName,
|
|
437
|
+
);
|
|
438
|
+
generation.rejectDisconnect?.(endpointError);
|
|
439
|
+
this.options.logger.log('error', endpointError.message);
|
|
440
|
+
if (this.listenerCount('error') > 0) {
|
|
441
|
+
this.emit('error', endpointError);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private toError(error: unknown, prefix: string): Error {
|
|
447
|
+
return new Error(prefix + ': ' + this.getErrorMessage(error));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private getErrorMessage(error: unknown): string {
|
|
451
|
+
return error instanceof Error ? error.message : String(error);
|
|
148
452
|
}
|
|
149
453
|
}
|
package/ts/interfaces/config.ts
CHANGED
|
@@ -15,6 +15,15 @@ export interface IRustBridgeRequestOptions {
|
|
|
15
15
|
timeoutMs?: number;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/** Options for confirmed bridge termination. */
|
|
19
|
+
export interface IRustBridgeTerminateOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Time to allow an owned stdio child to exit after SIGTERM before SIGKILL.
|
|
22
|
+
* Zero requests immediate SIGKILL. Defaults to 5000 milliseconds.
|
|
23
|
+
*/
|
|
24
|
+
gracePeriodMs?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
/**
|
|
19
28
|
* Options for locating a Rust binary.
|
|
20
29
|
*/
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type * as plugins from '../plugins.js';
|
|
2
2
|
|
|
3
|
+
/** Options for confirmed transport disconnection. */
|
|
4
|
+
export interface IRustTransportDisconnectOptions {
|
|
5
|
+
/** Grace period before escalating process termination, in milliseconds. */
|
|
6
|
+
gracePeriodMs?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
/**
|
|
4
10
|
* Abstract transport for communicating with a Rust process.
|
|
5
11
|
* Both stdio and socket transports implement this interface.
|
|
@@ -21,6 +27,12 @@ export interface IRustTransport extends plugins.events.EventEmitter {
|
|
|
21
27
|
/** Disconnect the transport. For stdio: kills the process. For socket: closes the connection. */
|
|
22
28
|
disconnect(): void;
|
|
23
29
|
|
|
30
|
+
/** Disconnect and resolve only after the local transport has confirmed closure. */
|
|
31
|
+
disconnectAndWait?(options?: IRustTransportDisconnectOptions): Promise<void>;
|
|
32
|
+
|
|
24
33
|
/** Whether the transport is currently connected and writable. */
|
|
25
34
|
readonly connected: boolean;
|
|
35
|
+
|
|
36
|
+
/** Whether the transport is preserving its lifecycle for an automatic reconnect. */
|
|
37
|
+
readonly reconnecting?: boolean;
|
|
26
38
|
}
|