electron-effect-rpc 0.8.0 → 0.10.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/CHANGELOG.md +90 -0
- package/README.md +145 -71
- package/dist/boundary.d.ts +17 -0
- package/dist/boundary.d.ts.map +1 -0
- package/dist/boundary.js +89 -0
- package/dist/contract.d.ts +7 -9
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +9 -10
- package/dist/kit.d.ts +25 -9
- package/dist/kit.d.ts.map +1 -1
- package/dist/kit.js +15 -7
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +109 -68
- package/dist/preload-bridge.d.ts +36 -8
- package/dist/preload-bridge.d.ts.map +1 -1
- package/dist/preload-bridge.js +26 -22
- package/dist/protocol.d.ts +12 -12
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +35 -53
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +177 -107
- package/dist/testing.d.ts +4 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/types.d.ts +71 -27
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/package.json +13 -9
package/dist/renderer.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import * as S from "
|
|
2
|
-
import { Cause, Effect, Exit, Stream } from "effect";
|
|
1
|
+
import * as S from "effect/Schema";
|
|
2
|
+
import { Cause, Effect, Exit, Queue, Result, Stream } from "effect";
|
|
3
|
+
import { isRecord, toDiagnosticCause, } from "./boundary.js";
|
|
3
4
|
import { exitSchemaFor, isNoErrorSchema, } from "./contract.js";
|
|
4
|
-
import { extractStreamIdFromRaw, formatUnknown,
|
|
5
|
+
import { extractStreamIdFromRaw, formatUnknown, parseRpcResponseEnvelope, parseStreamFrame, safelyCall, } from "./protocol.js";
|
|
5
6
|
import { RpcDefectError, } from "./types.js";
|
|
6
7
|
function requireInvoke(options) {
|
|
7
8
|
if (!options?.invoke) {
|
|
@@ -15,6 +16,9 @@ function requireSubscribe(options) {
|
|
|
15
16
|
}
|
|
16
17
|
return options.subscribe;
|
|
17
18
|
}
|
|
19
|
+
function emptyRpcClient() {
|
|
20
|
+
return Object.create(null);
|
|
21
|
+
}
|
|
18
22
|
function rpcDefect(code, message, cause) {
|
|
19
23
|
return new RpcDefectError(code, message, cause);
|
|
20
24
|
}
|
|
@@ -32,13 +36,13 @@ function decodeLegacyExit(method, raw) {
|
|
|
32
36
|
if (Exit.isSuccess(exit)) {
|
|
33
37
|
return Effect.succeed(exit.value);
|
|
34
38
|
}
|
|
35
|
-
const failureOption = Cause.
|
|
39
|
+
const failureOption = Cause.findErrorOption(exit.cause);
|
|
36
40
|
if (failureOption._tag === "Some") {
|
|
37
41
|
return Effect.fail(failureOption.value);
|
|
38
42
|
}
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
const defect =
|
|
43
|
+
const defectResult = Cause.findDefect(exit.cause);
|
|
44
|
+
if (Result.isSuccess(defectResult)) {
|
|
45
|
+
const defect = defectResult.success;
|
|
42
46
|
const message = defect instanceof Error ? defect.message : String(defect);
|
|
43
47
|
return Effect.fail(rpcDefect("remote_defect", message, defect));
|
|
44
48
|
}
|
|
@@ -59,7 +63,7 @@ export function createRpcClient(contract, options) {
|
|
|
59
63
|
scope: "rpc-response",
|
|
60
64
|
name: method.name,
|
|
61
65
|
payload: envelope.data,
|
|
62
|
-
cause,
|
|
66
|
+
cause: toDiagnosticCause(cause),
|
|
63
67
|
});
|
|
64
68
|
return rpcDefect("success_payload_decoding_failed", `RPC ${method.name} success payload decoding failed: ${formatUnknown(cause)}`, cause);
|
|
65
69
|
},
|
|
@@ -75,7 +79,7 @@ export function createRpcClient(contract, options) {
|
|
|
75
79
|
scope: "rpc-response",
|
|
76
80
|
name: method.name,
|
|
77
81
|
payload: envelope.error,
|
|
78
|
-
cause,
|
|
82
|
+
cause: toDiagnosticCause(cause),
|
|
79
83
|
});
|
|
80
84
|
return rpcDefect("failure_payload_decoding_failed", `RPC ${method.name} failure payload decoding failed: ${formatUnknown(cause)}`, cause);
|
|
81
85
|
},
|
|
@@ -90,8 +94,8 @@ export function createRpcClient(contract, options) {
|
|
|
90
94
|
safelyCall(diagnostics?.onDecodeFailure, {
|
|
91
95
|
scope: "rpc-request",
|
|
92
96
|
name: method.name,
|
|
93
|
-
payload: input,
|
|
94
|
-
cause,
|
|
97
|
+
payload: toDiagnosticCause(input),
|
|
98
|
+
cause: toDiagnosticCause(cause),
|
|
95
99
|
});
|
|
96
100
|
return rpcDefect("request_encoding_failed", `RPC ${method.name} request encoding failed: ${formatUnknown(cause)}`, cause);
|
|
97
101
|
},
|
|
@@ -101,7 +105,7 @@ export function createRpcClient(contract, options) {
|
|
|
101
105
|
safelyCall(diagnostics?.onProtocolError, {
|
|
102
106
|
method: method.name,
|
|
103
107
|
response: undefined,
|
|
104
|
-
cause,
|
|
108
|
+
cause: toDiagnosticCause(cause),
|
|
105
109
|
});
|
|
106
110
|
return rpcDefect("invoke_failed", `RPC ${method.name} invoke failed: ${formatUnknown(cause)}`, cause);
|
|
107
111
|
},
|
|
@@ -116,7 +120,7 @@ export function createRpcClient(contract, options) {
|
|
|
116
120
|
return Effect.sync(() => safelyCall(diagnostics?.onProtocolError, {
|
|
117
121
|
method: method.name,
|
|
118
122
|
response: raw,
|
|
119
|
-
cause,
|
|
123
|
+
cause: toDiagnosticCause(cause),
|
|
120
124
|
}));
|
|
121
125
|
}
|
|
122
126
|
return Effect.void;
|
|
@@ -126,18 +130,17 @@ export function createRpcClient(contract, options) {
|
|
|
126
130
|
safelyCall(diagnostics?.onProtocolError, {
|
|
127
131
|
method: method.name,
|
|
128
132
|
response: raw,
|
|
129
|
-
cause,
|
|
133
|
+
cause: toDiagnosticCause(cause),
|
|
130
134
|
});
|
|
131
135
|
return Effect.fail(cause);
|
|
132
136
|
}));
|
|
133
|
-
const client =
|
|
134
|
-
const clientRecord = client;
|
|
137
|
+
const client = emptyRpcClient();
|
|
135
138
|
for (const method of contract.methods) {
|
|
136
139
|
const caller = (...args) => {
|
|
137
140
|
const payload = args.length === 0 ? {} : args[0];
|
|
138
141
|
return call(method, payload);
|
|
139
142
|
};
|
|
140
|
-
|
|
143
|
+
Object.assign(client, { [method.name]: caller });
|
|
141
144
|
}
|
|
142
145
|
return client;
|
|
143
146
|
}
|
|
@@ -176,13 +179,16 @@ export function createEventSubscriber(contract, options) {
|
|
|
176
179
|
decoded = decoder(payload);
|
|
177
180
|
}
|
|
178
181
|
catch (cause) {
|
|
179
|
-
reportDecodeFailure(mode, event.name, payload, cause, options);
|
|
182
|
+
reportDecodeFailure(mode, event.name, payload, toDiagnosticCause(cause), options);
|
|
180
183
|
return;
|
|
181
184
|
}
|
|
182
185
|
handler(decoded);
|
|
183
186
|
});
|
|
184
187
|
return registerUnsubscribe(unsubscribe);
|
|
185
188
|
};
|
|
189
|
+
const streamEvent = (event) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => subscribeEvent(event, (payload) => {
|
|
190
|
+
Queue.offerUnsafe(queue, payload);
|
|
191
|
+
})), (unsubscribe) => Effect.sync(unsubscribe)));
|
|
186
192
|
const subscribeByName = (name, handler) => {
|
|
187
193
|
const event = eventMap.get(name);
|
|
188
194
|
if (!event) {
|
|
@@ -195,7 +201,7 @@ export function createEventSubscriber(contract, options) {
|
|
|
195
201
|
decoded = decoder(payload);
|
|
196
202
|
}
|
|
197
203
|
catch (cause) {
|
|
198
|
-
reportDecodeFailure(mode, name, payload, cause, options);
|
|
204
|
+
reportDecodeFailure(mode, name, payload, toDiagnosticCause(cause), options);
|
|
199
205
|
return;
|
|
200
206
|
}
|
|
201
207
|
handler(decoded);
|
|
@@ -220,6 +226,7 @@ export function createEventSubscriber(contract, options) {
|
|
|
220
226
|
return {
|
|
221
227
|
subscribe: subscribeEvent,
|
|
222
228
|
subscribeByName,
|
|
229
|
+
stream: streamEvent,
|
|
223
230
|
dispose,
|
|
224
231
|
};
|
|
225
232
|
}
|
|
@@ -237,6 +244,15 @@ export function createStreamRpcClient(contract, options) {
|
|
|
237
244
|
const diagnostics = options.diagnostics;
|
|
238
245
|
const streamBuffer = options.streamBuffer ?? { bufferSize: "unbounded" };
|
|
239
246
|
const frameDispatcher = new Map();
|
|
247
|
+
const recentlyClosedStreams = new Map();
|
|
248
|
+
const rememberRecentlyClosed = (streamId, report) => {
|
|
249
|
+
recentlyClosedStreams.set(streamId, report);
|
|
250
|
+
queueMicrotask(() => {
|
|
251
|
+
if (recentlyClosedStreams.get(streamId) === report) {
|
|
252
|
+
recentlyClosedStreams.delete(streamId);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
};
|
|
240
256
|
// Set up central frame listener
|
|
241
257
|
const centralCleanup = onStreamFrame((raw) => {
|
|
242
258
|
const frame = parseStreamFrame(raw);
|
|
@@ -258,8 +274,14 @@ export function createStreamRpcClient(contract, options) {
|
|
|
258
274
|
return;
|
|
259
275
|
}
|
|
260
276
|
const handler = frameDispatcher.get(frame.streamId);
|
|
261
|
-
if (!handler)
|
|
262
|
-
|
|
277
|
+
if (!handler) {
|
|
278
|
+
const report = recentlyClosedStreams.get(frame.streamId);
|
|
279
|
+
if (report) {
|
|
280
|
+
recentlyClosedStreams.delete(frame.streamId);
|
|
281
|
+
report(raw);
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
263
285
|
switch (frame.type) {
|
|
264
286
|
case "data":
|
|
265
287
|
handler.data(frame.payload);
|
|
@@ -273,105 +295,152 @@ export function createStreamRpcClient(contract, options) {
|
|
|
273
295
|
frameDispatcher.delete(frame.streamId);
|
|
274
296
|
break;
|
|
275
297
|
case "defect":
|
|
276
|
-
handler.defect(frame.message);
|
|
298
|
+
handler.defect(frame.message, true);
|
|
277
299
|
frameDispatcher.delete(frame.streamId);
|
|
278
300
|
break;
|
|
279
301
|
}
|
|
280
302
|
});
|
|
281
303
|
const streamMethods = contract.streamMethods ?? [];
|
|
282
|
-
const
|
|
283
|
-
|
|
304
|
+
const callbackOptions = streamBuffer.bufferSize === "unbounded"
|
|
305
|
+
? undefined
|
|
306
|
+
: { bufferSize: streamBuffer.bufferSize, strategy: streamBuffer.strategy };
|
|
307
|
+
function emptyStreamRpcClient() {
|
|
308
|
+
return Object.create(null);
|
|
309
|
+
}
|
|
310
|
+
const client = emptyStreamRpcClient();
|
|
284
311
|
for (const method of streamMethods) {
|
|
285
312
|
const decodeChunk = S.decodeUnknownSync(method.chunk);
|
|
286
313
|
const encodeInput = S.encodeSync(method.req);
|
|
287
314
|
const decodeTypedError = isNoErrorSchema(method.err) ? null : S.decodeUnknownSync(method.err);
|
|
288
315
|
const caller = (...args) => {
|
|
289
316
|
const payload = args.length === 0 ? {} : args[0];
|
|
290
|
-
return Stream.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
317
|
+
return Stream.callback((queue) => {
|
|
318
|
+
let queueClosed = false;
|
|
319
|
+
let postCloseReported = false;
|
|
320
|
+
const reportPostClose = (response) => {
|
|
321
|
+
if (postCloseReported)
|
|
322
|
+
return;
|
|
323
|
+
postCloseReported = true;
|
|
324
|
+
safelyCall(diagnostics?.onProtocolError, {
|
|
325
|
+
method: method.name,
|
|
326
|
+
response,
|
|
327
|
+
cause: new Error(`Stream ${method.name} received a post-close data frame; frame ignored`),
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
const failQueue = (error) => {
|
|
331
|
+
if (queueClosed)
|
|
332
|
+
return;
|
|
333
|
+
queueClosed = true;
|
|
334
|
+
Queue.failCauseUnsafe(queue, Cause.fail(error));
|
|
335
|
+
};
|
|
336
|
+
const endQueue = () => {
|
|
337
|
+
if (queueClosed)
|
|
338
|
+
return;
|
|
339
|
+
queueClosed = true;
|
|
340
|
+
Queue.endUnsafe(queue);
|
|
341
|
+
};
|
|
342
|
+
return Effect.gen(function* () {
|
|
343
|
+
const streamId = crypto.randomUUID();
|
|
344
|
+
// Once main has sent a terminal frame (end/error/defect) there is
|
|
345
|
+
// nothing left to cancel; the finalizer skips the cancel invoke.
|
|
346
|
+
let serverTerminated = false;
|
|
347
|
+
// 1. Register in dispatch map BEFORE calling invoke
|
|
348
|
+
frameDispatcher.set(streamId, {
|
|
349
|
+
data: (rawPayload) => {
|
|
350
|
+
let decoded;
|
|
351
|
+
try {
|
|
352
|
+
decoded = decodeChunk(rawPayload);
|
|
353
|
+
}
|
|
354
|
+
catch (cause) {
|
|
355
|
+
const context = {
|
|
356
|
+
scope: "stream-chunk",
|
|
357
|
+
name: method.name,
|
|
358
|
+
payload: rawPayload,
|
|
359
|
+
cause: toDiagnosticCause(cause),
|
|
360
|
+
};
|
|
361
|
+
safelyCall(diagnostics?.onDecodeFailure, context);
|
|
362
|
+
failQueue(rpcDefect("stream_chunk_decode_failed", `Stream ${method.name} chunk decode failed: ${formatUnknown(cause)}`, toDiagnosticCause(cause)));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const accepted = !queueClosed && Queue.offerUnsafe(queue, decoded);
|
|
366
|
+
if (!accepted && (queueClosed || queue.state._tag !== "Open")) {
|
|
367
|
+
// Stream has already finished. Remove dispatcher entry on the
|
|
368
|
+
// first post-close frame to avoid repeated diagnostics.
|
|
369
|
+
frameDispatcher.delete(streamId);
|
|
370
|
+
reportPostClose(rawPayload);
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
end: () => {
|
|
374
|
+
serverTerminated = true;
|
|
375
|
+
endQueue();
|
|
376
|
+
},
|
|
377
|
+
error: (err) => {
|
|
378
|
+
serverTerminated = true;
|
|
379
|
+
if (!decodeTypedError) {
|
|
380
|
+
failQueue(rpcDefect("stream_error_decode_failed", `Stream ${method.name} received typed error but declares NoError`, err));
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
const decoded = decodeNonEmptyError(method.err, err.data);
|
|
385
|
+
failQueue(decoded);
|
|
386
|
+
}
|
|
387
|
+
catch (cause) {
|
|
388
|
+
const errContext = {
|
|
389
|
+
scope: "stream-error",
|
|
390
|
+
name: method.name,
|
|
391
|
+
payload: err,
|
|
392
|
+
cause: toDiagnosticCause(cause),
|
|
393
|
+
};
|
|
394
|
+
safelyCall(diagnostics?.onDecodeFailure, errContext);
|
|
395
|
+
failQueue(rpcDefect("stream_error_decode_failed", `Stream ${method.name} error decode failed: ${formatUnknown(cause)}`, toDiagnosticCause(cause)));
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
defect: (message, fromServer = false) => {
|
|
399
|
+
if (fromServer) {
|
|
400
|
+
serverTerminated = true;
|
|
401
|
+
}
|
|
402
|
+
failQueue(rpcDefect("remote_defect", message, undefined));
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
// 2. Register cleanup finalizer
|
|
406
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
407
|
+
frameDispatcher.delete(streamId);
|
|
408
|
+
if (queueClosed && !postCloseReported) {
|
|
409
|
+
rememberRecentlyClosed(streamId, reportPostClose);
|
|
343
410
|
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
411
|
+
}).pipe(Effect.andThen(Effect.suspend(() => serverTerminated
|
|
412
|
+
? Effect.void
|
|
413
|
+
: Effect.tryPromise(() => invoke(`stream-cancel`, { streamId })).pipe(Effect.ignore)))));
|
|
414
|
+
// 3. Encode input
|
|
415
|
+
const encodedInput = yield* Effect.try({
|
|
416
|
+
try: () => encodeInput(payload),
|
|
417
|
+
catch: (cause) => rpcDefect("request_encoding_failed", `Stream ${method.name} request encoding failed: ${formatUnknown(cause)}`, cause),
|
|
418
|
+
});
|
|
419
|
+
// 4. Initiate the stream on main
|
|
420
|
+
const response = yield* Effect.tryPromise({
|
|
421
|
+
try: () => invoke(`stream/${method.name}`, {
|
|
422
|
+
data: encodedInput,
|
|
423
|
+
streamId,
|
|
424
|
+
}),
|
|
425
|
+
catch: (cause) => rpcDefect("stream_invoke_failed", `Stream ${method.name} invoke failed: ${formatUnknown(cause)}`, cause),
|
|
426
|
+
});
|
|
427
|
+
// 5. Validate handshake response
|
|
428
|
+
const envelope = parseRpcResponseEnvelope(response);
|
|
429
|
+
if (envelope?.type === "defect") {
|
|
430
|
+
return yield* Effect.fail(rpcDefect("remote_defect", envelope.message, envelope.cause));
|
|
431
|
+
}
|
|
432
|
+
if (!isStreamStartedResponse(response)) {
|
|
433
|
+
return yield* Effect.fail(rpcDefect("stream_handshake_invalid", `Stream ${method.name} unexpected handshake response`, response));
|
|
434
|
+
}
|
|
435
|
+
}).pipe(Effect.catchCause((cause) => Effect.sync(() => {
|
|
436
|
+
if (queueClosed)
|
|
437
|
+
return;
|
|
438
|
+
queueClosed = true;
|
|
439
|
+
Queue.failCauseUnsafe(queue, cause);
|
|
440
|
+
})));
|
|
441
|
+
}, callbackOptions);
|
|
373
442
|
};
|
|
374
|
-
|
|
443
|
+
Object.assign(client, { [method.name]: caller });
|
|
375
444
|
}
|
|
376
445
|
function dispose() {
|
|
377
446
|
// Fail all active streams so consumers don't hang
|
|
@@ -379,6 +448,7 @@ export function createStreamRpcClient(contract, options) {
|
|
|
379
448
|
handler.defect("Stream client disposed");
|
|
380
449
|
frameDispatcher.delete(streamId);
|
|
381
450
|
}
|
|
451
|
+
recentlyClosedStreams.clear();
|
|
382
452
|
centralCleanup();
|
|
383
453
|
}
|
|
384
454
|
return {
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import type { IpcEncodedValue } from "./boundary.ts";
|
|
1
2
|
import type { RpcInvoke } from "./types.ts";
|
|
2
3
|
export type Invocation = {
|
|
3
4
|
readonly method: string;
|
|
4
|
-
readonly payload:
|
|
5
|
+
readonly payload: IpcEncodedValue;
|
|
5
6
|
};
|
|
6
7
|
export type InvokeStub = RpcInvoke & {
|
|
7
8
|
readonly invocations: Invocation[];
|
|
8
9
|
};
|
|
9
10
|
export declare const createInvokeStub: (impl: RpcInvoke) => InvokeStub;
|
|
10
|
-
export declare const createDeferred: <T>() => {
|
|
11
|
+
export declare const createDeferred: <T, E = Error>() => {
|
|
11
12
|
promise: Promise<T>;
|
|
12
13
|
resolve: (value: T | PromiseLike<T>) => void;
|
|
13
|
-
reject: (reason?:
|
|
14
|
+
reject: (reason?: E) => void;
|
|
14
15
|
};
|
|
15
16
|
//# sourceMappingURL=testing.d.ts.map
|
package/dist/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,WAAW,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAE5E,eAAO,MAAM,gBAAgB,GAAI,MAAM,SAAS,KAAG,UAYlD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,CAAC,EAAE,CAAC,GAAG,KAAK;;qBACpB,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI;sBAC1B,CAAC,KAAK,IAAI;CAOjC,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,20 +1,39 @@
|
|
|
1
|
+
import type * as Context from "effect/Context";
|
|
1
2
|
import type * as Effect from "effect/Effect";
|
|
2
|
-
import type * as Runtime from "effect/Runtime";
|
|
3
3
|
import type * as Stream from "effect/Stream";
|
|
4
|
+
import type { DiagnosticCause, IpcEncodedValue } from "./boundary.ts";
|
|
4
5
|
import type { AnyEvent, AnyMethod, AnyStreamMethod, ExtractMethod, ExtractStreamMethod, RpcContract, RpcError, RpcEventPayload, RpcInput, RpcOutput, StreamChunk, StreamError, StreamInput } from "./contract.ts";
|
|
5
6
|
export type { AnyEvent, AnyMethod, AnyStreamMethod, ErrorSchema, ExtractMethod, ExtractStreamMethod, RpcContract, RpcError, RpcEvent, RpcEventPayload, RpcInput, RpcMethod, RpcOutput, SchemaNoContext, StreamChunk, StreamError, StreamInput, StreamRpcMethod, } from "./contract.ts";
|
|
7
|
+
/**
|
|
8
|
+
* Per-request context passed as the second argument to handlers.
|
|
9
|
+
* Handlers that don't need it can simply omit the parameter.
|
|
10
|
+
*/
|
|
11
|
+
export type RpcHandlerContext = {
|
|
12
|
+
/** The webContents that sent the request, when the transport exposes it. */
|
|
13
|
+
readonly sender: WebContentsLike | null;
|
|
14
|
+
};
|
|
6
15
|
export type Implementations<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[], readonly AnyStreamMethod[]>, R = never> = {
|
|
7
|
-
readonly [Name in C["methods"][number]["name"]]: (input: RpcInput<ExtractMethod<C["methods"], Name
|
|
16
|
+
readonly [Name in C["methods"][number]["name"]]: (input: RpcInput<ExtractMethod<C["methods"], Name>>, context: RpcHandlerContext) => Effect.Effect<RpcOutput<ExtractMethod<C["methods"], Name>>, RpcError<ExtractMethod<C["methods"], Name>>, R>;
|
|
8
17
|
};
|
|
9
18
|
export type StreamImplementations<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[], readonly AnyStreamMethod[]>, R = never> = {
|
|
10
|
-
readonly [Name in C["streamMethods"][number]["name"]]: (input: StreamInput<ExtractStreamMethod<C["streamMethods"], Name
|
|
19
|
+
readonly [Name in C["streamMethods"][number]["name"]]: (input: StreamInput<ExtractStreamMethod<C["streamMethods"], Name>>, context: RpcHandlerContext) => Stream.Stream<StreamChunk<ExtractStreamMethod<C["streamMethods"], Name>>, StreamError<ExtractStreamMethod<C["streamMethods"], Name>>, R>;
|
|
11
20
|
};
|
|
12
21
|
export type WebContentsLike = {
|
|
13
22
|
readonly id: number;
|
|
14
23
|
readonly isDestroyed: () => boolean;
|
|
15
|
-
readonly send: (channel: string, payload:
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
readonly send: (channel: string, payload: IpcEncodedValue) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Optional EventEmitter surface (present on real Electron WebContents).
|
|
27
|
+
* When available, active stream fibers are interrupted as soon as the
|
|
28
|
+
* renderer is destroyed; otherwise termination happens on the next chunk.
|
|
29
|
+
*/
|
|
30
|
+
readonly once?: (event: "destroyed", listener: () => void) => void;
|
|
31
|
+
readonly removeListener?: (event: "destroyed", listener: () => void) => void;
|
|
32
|
+
};
|
|
33
|
+
export type IpcInvokeEvent = {
|
|
34
|
+
readonly sender?: WebContentsLike;
|
|
35
|
+
};
|
|
36
|
+
type IsEmptyObject<T> = T extends object ? keyof T extends never ? string extends T ? true : false : false : false;
|
|
18
37
|
export type RpcDefectCode = "request_encoding_failed" | "invoke_failed" | "success_payload_decoding_failed" | "failure_payload_decoding_failed" | "noerror_contract_violation" | "invalid_response_envelope" | "legacy_decode_failed" | "remote_defect" | "stream_invoke_failed" | "stream_handshake_invalid" | "stream_chunk_decode_failed" | "stream_error_decode_failed";
|
|
19
38
|
export declare class RpcDefectError extends Error {
|
|
20
39
|
readonly code: RpcDefectCode;
|
|
@@ -37,32 +56,38 @@ export type ChannelPrefix = {
|
|
|
37
56
|
readonly event: string;
|
|
38
57
|
};
|
|
39
58
|
export declare const defaultChannelPrefix: ChannelPrefix;
|
|
59
|
+
/**
|
|
60
|
+
* RPC and event prefixes must differ: with identical prefixes an event named
|
|
61
|
+
* like a method (or named "sf"/"stream-cancel") would collide with RPC and
|
|
62
|
+
* stream transport channels.
|
|
63
|
+
*/
|
|
64
|
+
export declare function assertValidChannelPrefix(prefix: ChannelPrefix): ChannelPrefix;
|
|
40
65
|
export type DecodeFailureScope = "rpc-request" | "rpc-response" | "event-payload" | "stream-request" | "stream-chunk" | "stream-error";
|
|
41
66
|
export type DecodeFailureContext = {
|
|
42
67
|
readonly scope: DecodeFailureScope;
|
|
43
68
|
readonly name: string;
|
|
44
|
-
readonly payload:
|
|
45
|
-
readonly cause:
|
|
69
|
+
readonly payload: DiagnosticCause;
|
|
70
|
+
readonly cause: DiagnosticCause;
|
|
46
71
|
};
|
|
47
72
|
export type ProtocolErrorContext = {
|
|
48
73
|
readonly method: string;
|
|
49
|
-
readonly response:
|
|
50
|
-
readonly cause:
|
|
74
|
+
readonly response: DiagnosticCause;
|
|
75
|
+
readonly cause: DiagnosticCause;
|
|
51
76
|
};
|
|
52
77
|
export type DispatchFailureContext = {
|
|
53
78
|
readonly event: string;
|
|
54
|
-
readonly payload:
|
|
55
|
-
readonly cause:
|
|
79
|
+
readonly payload: DiagnosticCause;
|
|
80
|
+
readonly cause: DiagnosticCause;
|
|
56
81
|
};
|
|
57
82
|
export type DroppedEventReason = "queue_full" | "encoding_failed" | "window_unavailable" | "dispatch_failed";
|
|
58
83
|
export type DroppedEventContext = {
|
|
59
84
|
readonly event: string;
|
|
60
|
-
readonly payload:
|
|
85
|
+
readonly payload: DiagnosticCause;
|
|
61
86
|
readonly reason: DroppedEventReason;
|
|
62
87
|
readonly queued: number;
|
|
63
88
|
readonly dropped: number;
|
|
64
89
|
};
|
|
65
|
-
export type RpcInvoke = (method: string, payload:
|
|
90
|
+
export type RpcInvoke = (method: string, payload: IpcEncodedValue) => Promise<IpcEncodedValue>;
|
|
66
91
|
export type RpcResponseDecodeMode = "envelope" | "dual";
|
|
67
92
|
export type RpcClientDiagnostics = {
|
|
68
93
|
readonly onDecodeFailure?: (context: DecodeFailureContext) => void;
|
|
@@ -77,9 +102,10 @@ export type RpcEndpointDiagnostics = {
|
|
|
77
102
|
readonly onDecodeFailure?: (context: DecodeFailureContext) => void;
|
|
78
103
|
readonly onProtocolError?: (context: ProtocolErrorContext) => void;
|
|
79
104
|
};
|
|
105
|
+
export type IpcInvokeResult = IpcEncodedValue | Promise<IpcEncodedValue>;
|
|
80
106
|
export type IpcMainLike = {
|
|
81
|
-
readonly handle: (channel: string, listener: (event:
|
|
82
|
-
readonly removeHandler: (channel: string) =>
|
|
107
|
+
readonly handle: (channel: string, listener: (event: IpcInvokeEvent, payload: IpcEncodedValue) => IpcInvokeResult) => void;
|
|
108
|
+
readonly removeHandler: (channel: string) => void;
|
|
83
109
|
};
|
|
84
110
|
export interface RpcEndpoint {
|
|
85
111
|
readonly start: () => void;
|
|
@@ -89,7 +115,7 @@ export interface RpcEndpoint {
|
|
|
89
115
|
}
|
|
90
116
|
export type RpcEndpointOptions<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[], readonly AnyStreamMethod[]> = RpcContract<readonly AnyMethod[], readonly AnyEvent[], readonly []>, R = never> = {
|
|
91
117
|
readonly channelPrefix?: ChannelPrefix;
|
|
92
|
-
readonly
|
|
118
|
+
readonly context: Context.Context<R>;
|
|
93
119
|
readonly diagnostics?: RpcEndpointDiagnostics;
|
|
94
120
|
readonly streamHandlers?: StreamImplementations<C, R>;
|
|
95
121
|
};
|
|
@@ -101,7 +127,7 @@ export type EventPublisherDiagnostics = {
|
|
|
101
127
|
export type RendererWindowLike = {
|
|
102
128
|
readonly isDestroyed: () => boolean;
|
|
103
129
|
readonly webContents: {
|
|
104
|
-
readonly send: (channel: string, payload:
|
|
130
|
+
readonly send: (channel: string, payload: IpcEncodedValue) => void;
|
|
105
131
|
};
|
|
106
132
|
};
|
|
107
133
|
export type EventPublisherOptions = {
|
|
@@ -111,18 +137,29 @@ export type EventPublisherOptions = {
|
|
|
111
137
|
readonly diagnostics?: EventPublisherDiagnostics;
|
|
112
138
|
};
|
|
113
139
|
export interface RpcEventPublisher<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[], readonly AnyStreamMethod[]>> {
|
|
140
|
+
/**
|
|
141
|
+
* Enqueue an event for delivery. Never fails: after `dispose()` the event
|
|
142
|
+
* is silently discarded, and delivery problems surface only through the
|
|
143
|
+
* diagnostics hooks and `stats()`.
|
|
144
|
+
*/
|
|
114
145
|
readonly publish: <E extends C["events"][number]>(event: E, payload: RpcEventPayload<E>) => Effect.Effect<void, never>;
|
|
115
146
|
readonly start: () => void;
|
|
116
147
|
readonly stop: () => void;
|
|
117
148
|
readonly dispose: () => void;
|
|
118
149
|
readonly isRunning: () => boolean;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
150
|
+
/**
|
|
151
|
+
* `dropped` counts failed deliveries, not whole events: an event that
|
|
152
|
+
* reaches two of three windows increments it once (per failed window),
|
|
153
|
+
* as do queue evictions and encoding failures.
|
|
154
|
+
*/
|
|
155
|
+
readonly stats: () => EventPublisherStats;
|
|
123
156
|
}
|
|
124
157
|
export type EventDecodeMode = "safe" | "strict";
|
|
125
|
-
export type
|
|
158
|
+
export type EventPublisherStats = {
|
|
159
|
+
readonly queued: number;
|
|
160
|
+
readonly dropped: number;
|
|
161
|
+
};
|
|
162
|
+
export type EventSubscribe = (name: string, handler: (payload: IpcEncodedValue) => void) => () => void;
|
|
126
163
|
export type EventSubscriberDiagnostics = {
|
|
127
164
|
readonly onDecodeFailure?: (context: DecodeFailureContext) => void;
|
|
128
165
|
};
|
|
@@ -133,16 +170,22 @@ export type EventSubscriberOptions = {
|
|
|
133
170
|
};
|
|
134
171
|
export interface EventSubscriber<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[], readonly AnyStreamMethod[]>> {
|
|
135
172
|
readonly subscribe: <E extends C["events"][number]>(event: E, handler: (payload: RpcEventPayload<E>) => void) => () => void;
|
|
136
|
-
readonly subscribeByName: (name: string, handler: (payload:
|
|
173
|
+
readonly subscribeByName: (name: string, handler: (payload: IpcEncodedValue) => void) => () => void;
|
|
174
|
+
/**
|
|
175
|
+
* Effect-native subscription: a Stream of decoded payloads that
|
|
176
|
+
* subscribes when run and unsubscribes when its scope closes.
|
|
177
|
+
* Payloads that fail to decode are skipped (reported via diagnostics).
|
|
178
|
+
*/
|
|
179
|
+
readonly stream: <E extends C["events"][number]>(event: E) => Stream.Stream<RpcEventPayload<E>>;
|
|
137
180
|
readonly dispose: () => void;
|
|
138
181
|
}
|
|
139
|
-
export type OnStreamFrame = (listener: (frame:
|
|
182
|
+
export type OnStreamFrame = (listener: (frame: IpcEncodedValue) => void) => () => void;
|
|
140
183
|
/**
|
|
141
184
|
* Stream chunk buffering policy in the renderer.
|
|
142
185
|
* Prefer `bufferSize: "unbounded"` for lossless streams such as token deltas.
|
|
143
186
|
*
|
|
144
|
-
* Bounded buffers are lossy.
|
|
145
|
-
*
|
|
187
|
+
* Bounded buffers are lossy for chunks. Effect v4's callback queue still
|
|
188
|
+
* preserves terminal completion and failure signals.
|
|
146
189
|
*/
|
|
147
190
|
export type StreamBufferOptions = {
|
|
148
191
|
readonly bufferSize: "unbounded";
|
|
@@ -160,5 +203,6 @@ export interface StreamRpcClientHandle<C extends RpcContract<readonly AnyMethod[
|
|
|
160
203
|
readonly client: StreamRpcClient<C>;
|
|
161
204
|
readonly dispose: () => void;
|
|
162
205
|
}
|
|
206
|
+
export type { DiagnosticCause, IpcEncodedRecord, IpcEncodedValue } from "./boundary.ts";
|
|
163
207
|
export type { IpcBridge, IpcBridgeGlobal, IpcKit, IpcKitOptions, IpcMainHandle } from "./kit.ts";
|
|
164
208
|
//# sourceMappingURL=types.d.ts.map
|