@push.rocks/smartrust 1.5.0 → 1.6.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.rustbridge.d.ts +19 -8
- package/dist_ts/classes.rustbridge.js +302 -78
- package/dist_ts/classes.sockettransport.d.ts +16 -2
- package/dist_ts/classes.sockettransport.js +234 -61
- package/dist_ts/classes.stdiotransport.d.ts +15 -3
- package/dist_ts/classes.stdiotransport.js +253 -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 +30 -5
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.rustbridge.ts +363 -83
- package/ts/classes.sockettransport.ts +304 -60
- package/ts/classes.stdiotransport.ts +345 -58
- package/ts/interfaces/config.ts +9 -0
- package/ts/interfaces/transport.ts +12 -0
package/ts/classes.rustbridge.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { SocketTransport } from './classes.sockettransport.js';
|
|
|
7
7
|
import type {
|
|
8
8
|
IRustBridgeOptions,
|
|
9
9
|
IRustBridgeRequestOptions,
|
|
10
|
+
IRustBridgeTerminateOptions,
|
|
10
11
|
IRustBridgeLogger,
|
|
11
12
|
ISocketConnectOptions,
|
|
12
13
|
TCommandMap,
|
|
@@ -16,6 +17,7 @@ import type {
|
|
|
16
17
|
TStreamingCommandKeys,
|
|
17
18
|
TExtractChunk,
|
|
18
19
|
IRustTransport,
|
|
20
|
+
IRustTransportDisconnectOptions,
|
|
19
21
|
} from './interfaces/index.js';
|
|
20
22
|
|
|
21
23
|
const defaultLogger: IRustBridgeLogger = {
|
|
@@ -36,6 +38,12 @@ type TPendingRequestSettlement =
|
|
|
36
38
|
| { outcome: 'resolve'; value: any }
|
|
37
39
|
| { outcome: 'reject'; error: Error };
|
|
38
40
|
|
|
41
|
+
type TRustBridgeLifecycleState = 'idle' | 'starting' | 'ready' | 'stopping';
|
|
42
|
+
|
|
43
|
+
type TConfirmableRustTransport = IRustTransport & {
|
|
44
|
+
disconnectAndWait(optionsArg?: IRustTransportDisconnectOptions): Promise<void>;
|
|
45
|
+
};
|
|
46
|
+
|
|
39
47
|
/**
|
|
40
48
|
* Generic bridge between TypeScript and a Rust binary.
|
|
41
49
|
* Communicates via JSON-over-stdin/stdout IPC protocol (stdio mode)
|
|
@@ -47,11 +55,15 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
47
55
|
private locator: RustBinaryLocator;
|
|
48
56
|
private options: Required<Pick<IRustBridgeOptions, 'cliArgs' | 'requestTimeoutMs' | 'readyTimeoutMs' | 'readyEventName' | 'maxPayloadSize'>> & IRustBridgeOptions;
|
|
49
57
|
private logger: IRustBridgeLogger;
|
|
50
|
-
private transport:
|
|
58
|
+
private transport: TConfirmableRustTransport | null = null;
|
|
51
59
|
private pendingRequests = new Map<string, IPendingRequest>();
|
|
52
60
|
private requestCounter = 0;
|
|
53
61
|
private isRunning = false;
|
|
54
62
|
private binaryPath: string | null = null;
|
|
63
|
+
private lifecycleState: TRustBridgeLifecycleState = 'idle';
|
|
64
|
+
private lifecycleGeneration = 0;
|
|
65
|
+
private activationPromise: Promise<boolean> | null = null;
|
|
66
|
+
private terminationPromise: Promise<void> | null = null;
|
|
55
67
|
|
|
56
68
|
constructor(options: IRustBridgeOptions) {
|
|
57
69
|
super();
|
|
@@ -71,21 +83,25 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
71
83
|
* Spawn the Rust binary and wait for it to signal readiness.
|
|
72
84
|
* Returns true if the binary was found and spawned successfully.
|
|
73
85
|
*/
|
|
74
|
-
public
|
|
75
|
-
this.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
public spawn(): Promise<boolean> {
|
|
87
|
+
return this.activate(async (generationArg) => {
|
|
88
|
+
const binaryPath = await this.locator.findBinary();
|
|
89
|
+
if (!this.isStartingGeneration(generationArg)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
this.binaryPath = binaryPath;
|
|
93
|
+
if (!binaryPath) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
79
96
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
return new StdioTransport({
|
|
98
|
+
binaryPath,
|
|
99
|
+
cliArgs: this.options.cliArgs,
|
|
100
|
+
env: this.options.env,
|
|
101
|
+
maxPayloadSize: this.options.maxPayloadSize,
|
|
102
|
+
logger: this.logger,
|
|
103
|
+
});
|
|
86
104
|
});
|
|
87
|
-
|
|
88
|
-
return this.connectWithTransport(transport);
|
|
89
105
|
}
|
|
90
106
|
|
|
91
107
|
/**
|
|
@@ -95,8 +111,8 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
95
111
|
* @param socketPath - Path to Unix socket or Windows named pipe
|
|
96
112
|
* @param socketOptions - Optional socket connection options (reconnect, etc.)
|
|
97
113
|
*/
|
|
98
|
-
public
|
|
99
|
-
|
|
114
|
+
public connect(socketPath: string, socketOptions?: ISocketConnectOptions): Promise<boolean> {
|
|
115
|
+
return this.activate(async () => new SocketTransport({
|
|
100
116
|
socketPath,
|
|
101
117
|
maxPayloadSize: this.options.maxPayloadSize,
|
|
102
118
|
logger: this.logger,
|
|
@@ -104,68 +120,252 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
104
120
|
reconnectBaseDelayMs: socketOptions?.reconnectBaseDelayMs,
|
|
105
121
|
reconnectMaxDelayMs: socketOptions?.reconnectMaxDelayMs,
|
|
106
122
|
maxReconnectAttempts: socketOptions?.maxReconnectAttempts,
|
|
107
|
-
});
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
108
125
|
|
|
109
|
-
|
|
126
|
+
/** Reserve activation synchronously, or sequence it after confirmed termination. */
|
|
127
|
+
private activate(
|
|
128
|
+
createTransportArg: (generationArg: number) => Promise<TConfirmableRustTransport | null>,
|
|
129
|
+
): Promise<boolean> {
|
|
130
|
+
if (this.lifecycleState === 'stopping' && this.terminationPromise) {
|
|
131
|
+
return this.terminationPromise.then(() => this.beginActivation(createTransportArg));
|
|
132
|
+
}
|
|
133
|
+
if (this.lifecycleState !== 'idle') {
|
|
134
|
+
return Promise.reject(
|
|
135
|
+
new Error(`RustBridge cannot activate while lifecycle state is '${this.lifecycleState}'`),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return this.beginActivation(createTransportArg);
|
|
110
139
|
}
|
|
111
140
|
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
141
|
+
/** Start one transport generation. A second concurrent activation is rejected. */
|
|
142
|
+
private beginActivation(
|
|
143
|
+
createTransportArg: (generationArg: number) => Promise<TConfirmableRustTransport | null>,
|
|
144
|
+
): Promise<boolean> {
|
|
145
|
+
if (this.lifecycleState !== 'idle') {
|
|
146
|
+
throw new Error(`RustBridge cannot activate while lifecycle state is '${this.lifecycleState}'`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const generation = ++this.lifecycleGeneration;
|
|
150
|
+
this.lifecycleState = 'starting';
|
|
151
|
+
|
|
152
|
+
const activationPromise = (async (): Promise<boolean> => {
|
|
117
153
|
try {
|
|
154
|
+
const transport = await createTransportArg(generation);
|
|
155
|
+
if (!transport || !this.isStartingGeneration(generation)) {
|
|
156
|
+
if (this.isStartingGeneration(generation)) {
|
|
157
|
+
this.lifecycleState = 'idle';
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
118
162
|
this.transport = transport;
|
|
163
|
+
return await this.connectWithTransport(transport, generation);
|
|
164
|
+
} catch (error: unknown) {
|
|
165
|
+
this.logger.log('error', `Failed to connect: ${this.getErrorMessage(error)}`);
|
|
166
|
+
if (this.isStartingGeneration(generation)) {
|
|
167
|
+
this.lifecycleState = 'idle';
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
})();
|
|
172
|
+
|
|
173
|
+
this.activationPromise = activationPromise;
|
|
174
|
+
void activationPromise.then(
|
|
175
|
+
() => {
|
|
176
|
+
if (this.activationPromise === activationPromise) {
|
|
177
|
+
this.activationPromise = null;
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
() => {
|
|
181
|
+
if (this.activationPromise === activationPromise) {
|
|
182
|
+
this.activationPromise = null;
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
return activationPromise;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Wire one transport generation and wait for its ready handshake. */
|
|
190
|
+
private connectWithTransport(
|
|
191
|
+
transportArg: TConfirmableRustTransport,
|
|
192
|
+
generationArg?: number,
|
|
193
|
+
): Promise<boolean> {
|
|
194
|
+
const generation = generationArg ?? ++this.lifecycleGeneration;
|
|
195
|
+
if (generationArg === undefined) {
|
|
196
|
+
if (this.lifecycleState !== 'idle') {
|
|
197
|
+
throw new Error(`RustBridge cannot activate while lifecycle state is '${this.lifecycleState}'`);
|
|
198
|
+
}
|
|
199
|
+
this.lifecycleState = 'starting';
|
|
200
|
+
this.transport = transportArg;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return new Promise<boolean>((resolve) => {
|
|
204
|
+
let settled = false;
|
|
205
|
+
let readyTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
206
|
+
let reconnectReadyTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
207
|
+
|
|
208
|
+
const isCurrent = (): boolean => this.isCurrentTransport(transportArg, generation);
|
|
209
|
+
const settle = (valueArg: boolean): void => {
|
|
210
|
+
if (readyTimeout) {
|
|
211
|
+
clearTimeout(readyTimeout);
|
|
212
|
+
readyTimeout = null;
|
|
213
|
+
}
|
|
214
|
+
if (reconnectReadyTimeout) {
|
|
215
|
+
clearTimeout(reconnectReadyTimeout);
|
|
216
|
+
reconnectReadyTimeout = null;
|
|
217
|
+
}
|
|
218
|
+
if (settled) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
settled = true;
|
|
222
|
+
resolve(valueArg);
|
|
223
|
+
};
|
|
119
224
|
|
|
120
|
-
|
|
121
|
-
|
|
225
|
+
const requestConfirmedCleanup = (): void => {
|
|
226
|
+
let termination: Promise<void>;
|
|
227
|
+
try {
|
|
228
|
+
termination = this.terminate();
|
|
229
|
+
} catch (error: unknown) {
|
|
230
|
+
this.logger.log('error', `Transport cleanup failed: ${this.getErrorMessage(error)}`);
|
|
231
|
+
settle(false);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
void termination.then(
|
|
235
|
+
() => settle(false),
|
|
236
|
+
(error: unknown) => {
|
|
237
|
+
this.logger.log('error', `Transport cleanup failed: ${this.getErrorMessage(error)}`);
|
|
238
|
+
settle(false);
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
};
|
|
122
242
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
243
|
+
transportArg.on('line', (line: string) => {
|
|
244
|
+
if (!isCurrent()) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
this.handleLine(line, () => {
|
|
248
|
+
if (!isCurrent() || this.lifecycleState !== 'starting') {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (reconnectReadyTimeout) {
|
|
252
|
+
clearTimeout(reconnectReadyTimeout);
|
|
253
|
+
reconnectReadyTimeout = null;
|
|
254
|
+
}
|
|
255
|
+
this.lifecycleState = 'ready';
|
|
256
|
+
this.isRunning = true;
|
|
257
|
+
this.logger.log('info', `Bridge connected to ${this.options.binaryName}`);
|
|
258
|
+
this.emit('ready');
|
|
259
|
+
settle(true);
|
|
126
260
|
});
|
|
261
|
+
});
|
|
127
262
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
263
|
+
transportArg.on('stderr', (line: string) => {
|
|
264
|
+
if (!isCurrent()) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
this.logger.log('debug', `[${this.options.binaryName}] ${line}`);
|
|
268
|
+
this.emit('stderr', line);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
transportArg.on('close', (...args: any[]) => {
|
|
272
|
+
if (!isCurrent()) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const intentional = this.lifecycleState === 'stopping';
|
|
276
|
+
this.logger.log('info', 'Transport closed');
|
|
277
|
+
this.isRunning = false;
|
|
278
|
+
this.rejectAllPendingRequests(`${this.options.binaryName} process exited`);
|
|
279
|
+
if (!intentional && transportArg.reconnecting === true) {
|
|
280
|
+
if (reconnectReadyTimeout) {
|
|
281
|
+
clearTimeout(reconnectReadyTimeout);
|
|
282
|
+
reconnectReadyTimeout = null;
|
|
283
|
+
}
|
|
284
|
+
this.lifecycleState = 'starting';
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
this.transport = null;
|
|
289
|
+
if (!intentional) {
|
|
290
|
+
this.lifecycleState = 'idle';
|
|
131
291
|
this.emit('exit', ...args);
|
|
132
|
-
}
|
|
292
|
+
}
|
|
293
|
+
settle(false);
|
|
294
|
+
});
|
|
133
295
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
});
|
|
296
|
+
transportArg.on('error', (errorArg: Error) => {
|
|
297
|
+
if (!isCurrent()) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
this.logger.log('error', `Transport error: ${errorArg.message}`);
|
|
301
|
+
this.isRunning = false;
|
|
302
|
+
this.rejectAllPendingRequests(`${this.options.binaryName} process exited`, errorArg);
|
|
303
|
+
});
|
|
139
304
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
305
|
+
transportArg.on('reconnected', () => {
|
|
306
|
+
if (!isCurrent()) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
this.logger.log('info', 'Transport reconnected, waiting for ready event');
|
|
310
|
+
this.lifecycleState = 'starting';
|
|
311
|
+
if (reconnectReadyTimeout) {
|
|
312
|
+
clearTimeout(reconnectReadyTimeout);
|
|
313
|
+
}
|
|
314
|
+
reconnectReadyTimeout = setTimeout(() => {
|
|
315
|
+
if (!isCurrent() || this.lifecycleState !== 'starting') {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
this.logger.log(
|
|
319
|
+
'error',
|
|
320
|
+
`Reconnected process did not send ready event within ${this.options.readyTimeoutMs}ms`,
|
|
321
|
+
);
|
|
322
|
+
requestConfirmedCleanup();
|
|
323
|
+
}, this.options.readyTimeoutMs);
|
|
324
|
+
this.emit('reconnected');
|
|
325
|
+
});
|
|
144
326
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
327
|
+
transportArg.on('reconnect_failed', () => {
|
|
328
|
+
if (!isCurrent()) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (reconnectReadyTimeout) {
|
|
332
|
+
clearTimeout(reconnectReadyTimeout);
|
|
333
|
+
reconnectReadyTimeout = null;
|
|
334
|
+
}
|
|
335
|
+
this.isRunning = false;
|
|
336
|
+
this.transport = null;
|
|
337
|
+
this.lifecycleState = 'idle';
|
|
338
|
+
this.rejectAllPendingRequests(`${this.options.binaryName} reconnect failed`);
|
|
339
|
+
this.emit('exit');
|
|
340
|
+
settle(false);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
readyTimeout = setTimeout(() => {
|
|
344
|
+
if (!isCurrent() || this.lifecycleState !== 'starting') {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
this.logger.log(
|
|
348
|
+
'error',
|
|
349
|
+
`Process did not send ready event within ${this.options.readyTimeoutMs}ms`,
|
|
350
|
+
);
|
|
351
|
+
requestConfirmedCleanup();
|
|
352
|
+
}, this.options.readyTimeoutMs);
|
|
353
|
+
|
|
354
|
+
let connectionPromise: Promise<void>;
|
|
355
|
+
try {
|
|
356
|
+
connectionPromise = transportArg.connect();
|
|
357
|
+
} catch (error: unknown) {
|
|
358
|
+
this.logger.log('error', `Transport connect failed: ${this.getErrorMessage(error)}`);
|
|
359
|
+
requestConfirmedCleanup();
|
|
360
|
+
return;
|
|
168
361
|
}
|
|
362
|
+
void connectionPromise.catch((error: unknown) => {
|
|
363
|
+
if (!isCurrent()) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
this.logger.log('error', `Transport connect failed: ${this.getErrorMessage(error)}`);
|
|
367
|
+
requestConfirmedCleanup();
|
|
368
|
+
});
|
|
169
369
|
});
|
|
170
370
|
}
|
|
171
371
|
|
|
@@ -349,22 +549,89 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
349
549
|
}
|
|
350
550
|
|
|
351
551
|
/**
|
|
352
|
-
*
|
|
353
|
-
* For stdio
|
|
354
|
-
* For socket
|
|
552
|
+
* Terminate the active transport and wait for confirmed closure.
|
|
553
|
+
* For stdio this confirms child exit after SIGTERM/SIGKILL escalation.
|
|
554
|
+
* For socket mode this confirms local socket closure and never kills the daemon.
|
|
355
555
|
*/
|
|
356
|
-
public
|
|
357
|
-
if (
|
|
358
|
-
|
|
556
|
+
public terminate(optionsArg: IRustBridgeTerminateOptions = {}): Promise<void> {
|
|
557
|
+
if (
|
|
558
|
+
optionsArg.gracePeriodMs !== undefined
|
|
559
|
+
&& (!Number.isSafeInteger(optionsArg.gracePeriodMs) || optionsArg.gracePeriodMs < 0)
|
|
560
|
+
) {
|
|
561
|
+
throw new RangeError('RustBridge terminate gracePeriodMs must be a non-negative safe integer');
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (this.terminationPromise) {
|
|
565
|
+
return this.terminationPromise;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (
|
|
569
|
+
this.lifecycleState === 'idle'
|
|
570
|
+
&& !this.transport
|
|
571
|
+
&& !this.activationPromise
|
|
572
|
+
) {
|
|
573
|
+
return Promise.resolve();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const generation = this.lifecycleGeneration;
|
|
577
|
+
const transport = this.transport;
|
|
578
|
+
const activationPromise = this.activationPromise;
|
|
579
|
+
|
|
580
|
+
this.lifecycleState = 'stopping';
|
|
581
|
+
this.isRunning = false;
|
|
582
|
+
this.rejectAllPendingRequests(`${this.options.binaryName} process killed`);
|
|
583
|
+
|
|
584
|
+
const terminationPromise = (async (): Promise<void> => {
|
|
585
|
+
if (transport) {
|
|
586
|
+
await transport.disconnectAndWait({ gracePeriodMs: optionsArg.gracePeriodMs });
|
|
587
|
+
} else if (activationPromise) {
|
|
588
|
+
// No transport exists yet (for example, binary lookup is pending). The
|
|
589
|
+
// stopping state prevents that activation from publishing a transport.
|
|
590
|
+
await activationPromise;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (this.lifecycleGeneration !== generation) {
|
|
594
|
+
throw new Error('RustBridge lifecycle generation changed during termination');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const lateTransport = this.transport;
|
|
598
|
+
if (lateTransport && lateTransport !== transport) {
|
|
599
|
+
await lateTransport.disconnectAndWait({ gracePeriodMs: optionsArg.gracePeriodMs });
|
|
600
|
+
}
|
|
601
|
+
|
|
359
602
|
this.transport = null;
|
|
360
603
|
this.isRunning = false;
|
|
604
|
+
this.lifecycleState = 'idle';
|
|
605
|
+
})();
|
|
606
|
+
|
|
607
|
+
this.terminationPromise = terminationPromise;
|
|
608
|
+
void terminationPromise.then(
|
|
609
|
+
() => {
|
|
610
|
+
if (this.terminationPromise === terminationPromise) {
|
|
611
|
+
this.terminationPromise = null;
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
() => {
|
|
615
|
+
// Keep the rejected promise and stopping state cached: closure was not confirmed.
|
|
616
|
+
},
|
|
617
|
+
);
|
|
618
|
+
return terminationPromise;
|
|
619
|
+
}
|
|
361
620
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
621
|
+
/**
|
|
622
|
+
* Fire-and-forget compatibility wrapper around confirmed termination.
|
|
623
|
+
*/
|
|
624
|
+
public kill(): void {
|
|
625
|
+
let termination: Promise<void>;
|
|
626
|
+
try {
|
|
627
|
+
termination = this.terminate();
|
|
628
|
+
} catch (error: unknown) {
|
|
629
|
+
this.logger.log('error', `Bridge termination failed: ${this.getErrorMessage(error)}`);
|
|
630
|
+
return;
|
|
367
631
|
}
|
|
632
|
+
void termination.catch((error: unknown) => {
|
|
633
|
+
this.logger.log('error', `Bridge termination failed: ${this.getErrorMessage(error)}`);
|
|
634
|
+
});
|
|
368
635
|
}
|
|
369
636
|
|
|
370
637
|
/**
|
|
@@ -374,7 +641,24 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
374
641
|
return this.isRunning;
|
|
375
642
|
}
|
|
376
643
|
|
|
377
|
-
private
|
|
644
|
+
private isStartingGeneration(generationArg: number): boolean {
|
|
645
|
+
return (
|
|
646
|
+
this.lifecycleGeneration === generationArg
|
|
647
|
+
&& this.lifecycleState === 'starting'
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
private isCurrentTransport(
|
|
652
|
+
transportArg: TConfirmableRustTransport,
|
|
653
|
+
generationArg: number,
|
|
654
|
+
): boolean {
|
|
655
|
+
return (
|
|
656
|
+
this.lifecycleGeneration === generationArg
|
|
657
|
+
&& this.transport === transportArg
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
private handleLine(line: string, onReadyArg: () => void): void {
|
|
378
662
|
if (!line) return;
|
|
379
663
|
|
|
380
664
|
let parsed: any;
|
|
@@ -388,6 +672,9 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
388
672
|
// Check if it's an event (has 'event' field, no 'id')
|
|
389
673
|
if ('event' in parsed && !('id' in parsed)) {
|
|
390
674
|
const event = parsed as IManagementEvent;
|
|
675
|
+
if (event.event === this.options.readyEventName) {
|
|
676
|
+
onReadyArg();
|
|
677
|
+
}
|
|
391
678
|
this.emit(`management:${event.event}`, event.data);
|
|
392
679
|
return;
|
|
393
680
|
}
|
|
@@ -488,13 +775,6 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
488
775
|
}
|
|
489
776
|
}
|
|
490
777
|
|
|
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
778
|
private getErrorMessage(error: unknown): string {
|
|
499
779
|
return error instanceof Error ? error.message : String(error);
|
|
500
780
|
}
|