electron-effect-rpc 0.4.0 → 0.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/README.md +69 -20
- package/dist/contract.d.ts +24 -2
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +35 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/kit.d.ts +12 -8
- package/dist/kit.d.ts.map +1 -1
- package/dist/kit.js +20 -2
- package/dist/main.d.ts +3 -3
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +230 -24
- package/dist/preload.d.ts +1 -0
- package/dist/preload.d.ts.map +1 -1
- package/dist/preload.js +29 -7
- package/dist/protocol.d.ts +26 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +43 -1
- package/dist/renderer.d.ts +5 -4
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +161 -9
- package/dist/types.d.ts +37 -15
- package/dist/types.d.ts.map +1 -1
- package/package.json +29 -23
package/dist/main.js
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import * as S from "@effect/schema/Schema";
|
|
2
|
-
import { Cause, Effect, Exit } from "effect";
|
|
2
|
+
import { Cause, Effect, Exit, FiberId, Stream } from "effect";
|
|
3
3
|
import * as Runtime from "effect/Runtime";
|
|
4
4
|
import { isNoErrorSchema } from "./contract.js";
|
|
5
|
-
import { extractErrorTag, safelyCall, toDefectEnvelope, } from "./protocol.js";
|
|
5
|
+
import { extractErrorTag, formatUnknown, isRecord, safelyCall, toDefectEnvelope, } from "./protocol.js";
|
|
6
6
|
import { defaultChannelPrefix, } from "./types.js";
|
|
7
7
|
function resolveChannelPrefix(prefix) {
|
|
8
8
|
return prefix ?? defaultChannelPrefix;
|
|
9
9
|
}
|
|
10
|
+
function isWebContentsLike(value) {
|
|
11
|
+
if (!isRecord(value))
|
|
12
|
+
return false;
|
|
13
|
+
return (typeof value.id === "number" &&
|
|
14
|
+
typeof value.isDestroyed === "function" &&
|
|
15
|
+
typeof value.send === "function");
|
|
16
|
+
}
|
|
17
|
+
function extractSender(event) {
|
|
18
|
+
if (!isRecord(event))
|
|
19
|
+
return null;
|
|
20
|
+
return isWebContentsLike(event.sender) ? event.sender : null;
|
|
21
|
+
}
|
|
10
22
|
function isImplementation(value) {
|
|
11
23
|
return typeof value === "function";
|
|
12
24
|
}
|
|
25
|
+
function isStreamImplementation(value) {
|
|
26
|
+
return typeof value === "function";
|
|
27
|
+
}
|
|
13
28
|
export function createRpcEndpoint(contract, ipc, implementations, options) {
|
|
14
29
|
const channelPrefix = resolveChannelPrefix(options.channelPrefix);
|
|
15
30
|
const diagnostics = options.diagnostics;
|
|
@@ -36,9 +51,7 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
|
|
|
36
51
|
}
|
|
37
52
|
const decodeInput = S.decodeUnknownSync(method.req);
|
|
38
53
|
const encodeSuccess = S.encodeSync(method.res);
|
|
39
|
-
const encodeFailure = isNoErrorSchema(method.err)
|
|
40
|
-
? null
|
|
41
|
-
: S.encodeSync(method.err);
|
|
54
|
+
const encodeFailure = isNoErrorSchema(method.err) ? null : S.encodeSync(method.err);
|
|
42
55
|
const channel = `${channelPrefix.rpc}${method.name}`;
|
|
43
56
|
listeners.set(channel, async function handleRpcRequest(_event, rawPayload) {
|
|
44
57
|
let input;
|
|
@@ -100,6 +113,133 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
|
|
|
100
113
|
return toDefectEnvelope(exit.cause, `RPC ${method.name} interrupted`);
|
|
101
114
|
});
|
|
102
115
|
}
|
|
116
|
+
const activeStreams = new Map();
|
|
117
|
+
const streamListeners = new Map();
|
|
118
|
+
const streamMethods = contract.streamMethods ?? [];
|
|
119
|
+
const streamHandlerImpls = options.streamHandlers;
|
|
120
|
+
const cancelChannel = `${channelPrefix.rpc}stream-cancel`;
|
|
121
|
+
if (streamMethods.length > 0 && !streamHandlerImpls) {
|
|
122
|
+
throw new Error("Contract defines stream methods but no streamHandlers were provided.");
|
|
123
|
+
}
|
|
124
|
+
if (streamMethods.length > 0 && streamHandlerImpls) {
|
|
125
|
+
const streamMethodNames = new Set(streamMethods.map((m) => m.name));
|
|
126
|
+
for (const name in streamHandlerImpls) {
|
|
127
|
+
if (!streamMethodNames.has(name)) {
|
|
128
|
+
throw new Error(`Stream implementation provided for unknown stream method: ${name}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const runFork = Runtime.runFork(options.runtime);
|
|
132
|
+
for (const method of streamMethods) {
|
|
133
|
+
const impl = streamHandlerImpls[method.name];
|
|
134
|
+
if (!isStreamImplementation(impl)) {
|
|
135
|
+
throw new Error(`Missing implementation for stream method: ${method.name}`);
|
|
136
|
+
}
|
|
137
|
+
const decodeInput = S.decodeUnknownSync(method.req);
|
|
138
|
+
const encodeChunk = S.encodeSync(method.chunk);
|
|
139
|
+
const encodeFailure = isNoErrorSchema(method.err) ? null : S.encodeSync(method.err);
|
|
140
|
+
const channel = `${channelPrefix.rpc}stream/${method.name}`;
|
|
141
|
+
streamListeners.set(channel, async function handleStreamRequest(event, rawPayload) {
|
|
142
|
+
if (!isRecord(rawPayload)) {
|
|
143
|
+
return toDefectEnvelope("Stream request payload must be an object");
|
|
144
|
+
}
|
|
145
|
+
const streamId = rawPayload.streamId;
|
|
146
|
+
const rawData = rawPayload.data;
|
|
147
|
+
if (typeof streamId !== "string" || streamId.length === 0) {
|
|
148
|
+
return toDefectEnvelope("Invalid streamId");
|
|
149
|
+
}
|
|
150
|
+
if (activeStreams.has(streamId)) {
|
|
151
|
+
return toDefectEnvelope("Duplicate streamId");
|
|
152
|
+
}
|
|
153
|
+
const sender = extractSender(event);
|
|
154
|
+
if (!sender) {
|
|
155
|
+
return toDefectEnvelope("Stream handler requires sender with webContents");
|
|
156
|
+
}
|
|
157
|
+
let input;
|
|
158
|
+
try {
|
|
159
|
+
input = decodeInput(rawData);
|
|
160
|
+
}
|
|
161
|
+
catch (cause) {
|
|
162
|
+
const decodeCtx = {
|
|
163
|
+
scope: "stream-request",
|
|
164
|
+
name: method.name,
|
|
165
|
+
payload: rawData,
|
|
166
|
+
cause,
|
|
167
|
+
};
|
|
168
|
+
safelyCall(diagnostics?.onDecodeFailure, decodeCtx);
|
|
169
|
+
return toDefectEnvelope(cause, `Stream ${method.name} request decode failed`);
|
|
170
|
+
}
|
|
171
|
+
const sfChannel = `${channelPrefix.rpc}sf`;
|
|
172
|
+
const trySend = (frame) => Effect.try({
|
|
173
|
+
try: () => {
|
|
174
|
+
if (!sender.isDestroyed()) {
|
|
175
|
+
sender.send(sfChannel, frame);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
catch: () => undefined,
|
|
179
|
+
}).pipe(Effect.ignore);
|
|
180
|
+
let handlerStream;
|
|
181
|
+
try {
|
|
182
|
+
handlerStream = impl(input);
|
|
183
|
+
}
|
|
184
|
+
catch (cause) {
|
|
185
|
+
return toDefectEnvelope(cause, `Stream ${method.name} implementation threw`);
|
|
186
|
+
}
|
|
187
|
+
const buildTerminalFrame = (cause) => {
|
|
188
|
+
const failure = Cause.failureOption(cause);
|
|
189
|
+
if (failure._tag === "Some") {
|
|
190
|
+
if (encodeFailure) {
|
|
191
|
+
try {
|
|
192
|
+
return {
|
|
193
|
+
type: "error",
|
|
194
|
+
streamId,
|
|
195
|
+
error: {
|
|
196
|
+
tag: extractErrorTag(failure.value),
|
|
197
|
+
data: encodeFailure(failure.value),
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// encoding failed, fall through to defect
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { type: "defect", streamId, message: formatUnknown(failure.value) };
|
|
206
|
+
}
|
|
207
|
+
if (Cause.isInterruptedOnly(cause)) {
|
|
208
|
+
return { type: "end", streamId };
|
|
209
|
+
}
|
|
210
|
+
const defect = Cause.dieOption(cause);
|
|
211
|
+
return {
|
|
212
|
+
type: "defect",
|
|
213
|
+
streamId,
|
|
214
|
+
message: defect._tag === "Some" ? formatUnknown(defect.value) : "Stream failed unexpectedly",
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
const streamEffect = handlerStream.pipe(Stream.mapEffect((chunk) => Effect.try({
|
|
218
|
+
try: () => {
|
|
219
|
+
if (sender.isDestroyed())
|
|
220
|
+
return;
|
|
221
|
+
sender.send(sfChannel, { type: "data", streamId, payload: encodeChunk(chunk) });
|
|
222
|
+
},
|
|
223
|
+
catch: () => undefined,
|
|
224
|
+
}).pipe(Effect.ignore)), Stream.runDrain, Effect.andThen(() => trySend({ type: "end", streamId })), Effect.catchAllCause((cause) => sender.isDestroyed() ? Effect.void : trySend(buildTerminalFrame(cause))), Effect.ensuring(Effect.sync(() => {
|
|
225
|
+
activeStreams.delete(streamId);
|
|
226
|
+
})));
|
|
227
|
+
// Reserve entry BEFORE forking
|
|
228
|
+
const entry = {
|
|
229
|
+
fiber: null,
|
|
230
|
+
senderId: sender.id,
|
|
231
|
+
};
|
|
232
|
+
activeStreams.set(streamId, entry);
|
|
233
|
+
const fiber = runFork(streamEffect);
|
|
234
|
+
entry.fiber = fiber;
|
|
235
|
+
const response = {
|
|
236
|
+
type: "success",
|
|
237
|
+
data: { type: "stream_started" },
|
|
238
|
+
};
|
|
239
|
+
return response;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
103
243
|
let running = false;
|
|
104
244
|
let disposed = false;
|
|
105
245
|
function start() {
|
|
@@ -115,6 +255,35 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
|
|
|
115
255
|
ipc.handle(channel, listener);
|
|
116
256
|
registeredChannels.push(channel);
|
|
117
257
|
}
|
|
258
|
+
// Register stream handlers
|
|
259
|
+
for (const [channel, listener] of streamListeners) {
|
|
260
|
+
ipc.handle(channel, listener);
|
|
261
|
+
registeredChannels.push(channel);
|
|
262
|
+
}
|
|
263
|
+
// Register cancel handler if we have stream methods
|
|
264
|
+
if (streamMethods.length > 0) {
|
|
265
|
+
ipc.handle(cancelChannel, (event, rawPayload) => {
|
|
266
|
+
if (!isRecord(rawPayload)) {
|
|
267
|
+
return { cancelled: false };
|
|
268
|
+
}
|
|
269
|
+
const streamId = rawPayload.streamId;
|
|
270
|
+
if (typeof streamId !== "string")
|
|
271
|
+
return { cancelled: false };
|
|
272
|
+
const entry = activeStreams.get(streamId);
|
|
273
|
+
if (!entry)
|
|
274
|
+
return { cancelled: false };
|
|
275
|
+
// Validate sender identity
|
|
276
|
+
const sender = extractSender(event);
|
|
277
|
+
if (!sender || sender.id !== entry.senderId)
|
|
278
|
+
return { cancelled: false };
|
|
279
|
+
// Interrupt the fiber
|
|
280
|
+
if (entry.fiber) {
|
|
281
|
+
entry.fiber.unsafeInterruptAsFork(FiberId.none);
|
|
282
|
+
}
|
|
283
|
+
return { cancelled: true };
|
|
284
|
+
});
|
|
285
|
+
registeredChannels.push(cancelChannel);
|
|
286
|
+
}
|
|
118
287
|
}
|
|
119
288
|
catch (cause) {
|
|
120
289
|
for (const channel of registeredChannels) {
|
|
@@ -133,6 +302,13 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
|
|
|
133
302
|
if (!running) {
|
|
134
303
|
return;
|
|
135
304
|
}
|
|
305
|
+
// Interrupt all active stream fibers first
|
|
306
|
+
for (const entry of activeStreams.values()) {
|
|
307
|
+
if (entry.fiber) {
|
|
308
|
+
entry.fiber.unsafeInterruptAsFork(FiberId.none);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
activeStreams.clear();
|
|
136
312
|
let firstError;
|
|
137
313
|
for (const channel of listeners.keys()) {
|
|
138
314
|
try {
|
|
@@ -142,6 +318,22 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
|
|
|
142
318
|
firstError ??= cause;
|
|
143
319
|
}
|
|
144
320
|
}
|
|
321
|
+
for (const channel of streamListeners.keys()) {
|
|
322
|
+
try {
|
|
323
|
+
ipc.removeHandler(channel);
|
|
324
|
+
}
|
|
325
|
+
catch (cause) {
|
|
326
|
+
firstError ??= cause;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (streamMethods.length > 0) {
|
|
330
|
+
try {
|
|
331
|
+
ipc.removeHandler(cancelChannel);
|
|
332
|
+
}
|
|
333
|
+
catch (cause) {
|
|
334
|
+
firstError ??= cause;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
145
337
|
running = false;
|
|
146
338
|
if (firstError !== undefined) {
|
|
147
339
|
throw firstError;
|
|
@@ -224,8 +416,8 @@ export function createEventPublisher(_contract, options) {
|
|
|
224
416
|
});
|
|
225
417
|
return;
|
|
226
418
|
}
|
|
227
|
-
const
|
|
228
|
-
if (
|
|
419
|
+
const windows = options.getWindows();
|
|
420
|
+
if (windows.length === 0) {
|
|
229
421
|
dropped += 1;
|
|
230
422
|
safelyCall(diagnostics?.onDroppedEvent, {
|
|
231
423
|
event: item.event.name,
|
|
@@ -236,23 +428,37 @@ export function createEventPublisher(_contract, options) {
|
|
|
236
428
|
});
|
|
237
429
|
return;
|
|
238
430
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
431
|
+
const channel = `${channelPrefix.event}${item.event.name}`;
|
|
432
|
+
for (const window of windows) {
|
|
433
|
+
if (window.isDestroyed()) {
|
|
434
|
+
dropped += 1;
|
|
435
|
+
safelyCall(diagnostics?.onDroppedEvent, {
|
|
436
|
+
event: item.event.name,
|
|
437
|
+
payload: item.payload,
|
|
438
|
+
reason: "window_unavailable",
|
|
439
|
+
queued: queue.length,
|
|
440
|
+
dropped,
|
|
441
|
+
});
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
window.webContents.send(channel, encoded);
|
|
446
|
+
}
|
|
447
|
+
catch (cause) {
|
|
448
|
+
dropped += 1;
|
|
449
|
+
safelyCall(diagnostics?.onDispatchFailure, {
|
|
450
|
+
event: item.event.name,
|
|
451
|
+
payload: item.payload,
|
|
452
|
+
cause,
|
|
453
|
+
});
|
|
454
|
+
safelyCall(diagnostics?.onDroppedEvent, {
|
|
455
|
+
event: item.event.name,
|
|
456
|
+
payload: item.payload,
|
|
457
|
+
reason: "dispatch_failed",
|
|
458
|
+
queued: queue.length,
|
|
459
|
+
dropped,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
256
462
|
}
|
|
257
463
|
}
|
|
258
464
|
function drain() {
|
package/dist/preload.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type ChannelPrefix, type EventSubscribe, type RpcInvoke } from "./types
|
|
|
2
2
|
export type BridgeAdapters = {
|
|
3
3
|
readonly invoke: RpcInvoke;
|
|
4
4
|
readonly subscribe: EventSubscribe;
|
|
5
|
+
readonly onStreamFrame: (listener: (frame: unknown) => void) => () => void;
|
|
5
6
|
};
|
|
6
7
|
export type BridgeAdaptersOptions = {
|
|
7
8
|
readonly channelPrefix?: ChannelPrefix;
|
package/dist/preload.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../src/preload.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../src/preload.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;CAC5E,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,qBAAqB,GAAG;IAC1D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,qBAAqB,GAAG;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAoDF,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,cAAc,CAgCpF;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAiBrE;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAaxE"}
|
package/dist/preload.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import * as electronModule from "electron";
|
|
2
|
+
import { isRecord } from "./protocol.js";
|
|
2
3
|
import { defaultChannelPrefix, } from "./types.js";
|
|
4
|
+
function isContextBridgeLike(value) {
|
|
5
|
+
return isRecord(value) && typeof value.exposeInMainWorld === "function";
|
|
6
|
+
}
|
|
7
|
+
function isIpcRendererLike(value) {
|
|
8
|
+
return (isRecord(value) &&
|
|
9
|
+
typeof value.invoke === "function" &&
|
|
10
|
+
typeof value.on === "function" &&
|
|
11
|
+
typeof value.removeListener === "function");
|
|
12
|
+
}
|
|
3
13
|
function resolveElectronRendererBindings() {
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
const mod = electronModule;
|
|
15
|
+
const moduleDefault = isRecord(mod) ? mod.default : undefined;
|
|
16
|
+
const source = isRecord(moduleDefault) ? moduleDefault : isRecord(mod) ? mod : undefined;
|
|
17
|
+
if (!source) {
|
|
18
|
+
throw new Error("electron-effect-rpc/preload requires Electron preload runtime bindings.");
|
|
19
|
+
}
|
|
20
|
+
const { contextBridge, ipcRenderer } = source;
|
|
21
|
+
if (!isContextBridgeLike(contextBridge) || !isIpcRendererLike(ipcRenderer)) {
|
|
11
22
|
throw new Error("electron-effect-rpc/preload requires Electron preload runtime bindings.");
|
|
12
23
|
}
|
|
13
24
|
return { contextBridge, ipcRenderer };
|
|
@@ -24,9 +35,18 @@ export function createBridgeAdapters(options) {
|
|
|
24
35
|
ipcRenderer.removeListener(channel, wrapped);
|
|
25
36
|
};
|
|
26
37
|
};
|
|
38
|
+
const onStreamFrame = (listener) => {
|
|
39
|
+
const channel = `${channelPrefix.rpc}sf`;
|
|
40
|
+
const wrapped = (_event, frame) => listener(frame);
|
|
41
|
+
ipcRenderer.on(channel, wrapped);
|
|
42
|
+
return () => {
|
|
43
|
+
ipcRenderer.removeListener(channel, wrapped);
|
|
44
|
+
};
|
|
45
|
+
};
|
|
27
46
|
return {
|
|
28
47
|
invoke,
|
|
29
48
|
subscribe,
|
|
49
|
+
onStreamFrame,
|
|
30
50
|
};
|
|
31
51
|
}
|
|
32
52
|
export function exposeRpcBridge(options) {
|
|
@@ -38,6 +58,7 @@ export function exposeRpcBridge(options) {
|
|
|
38
58
|
});
|
|
39
59
|
contextBridge.exposeInMainWorld(rpcGlobal, {
|
|
40
60
|
invoke: adapters.invoke,
|
|
61
|
+
onStreamFrame: adapters.onStreamFrame,
|
|
41
62
|
});
|
|
42
63
|
contextBridge.exposeInMainWorld(eventsGlobal, {
|
|
43
64
|
subscribe: adapters.subscribe,
|
|
@@ -52,5 +73,6 @@ export function exposeIpcBridge(options) {
|
|
|
52
73
|
contextBridge.exposeInMainWorld(global, {
|
|
53
74
|
invoke: adapters.invoke,
|
|
54
75
|
subscribe: adapters.subscribe,
|
|
76
|
+
onStreamFrame: adapters.onStreamFrame,
|
|
55
77
|
});
|
|
56
78
|
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -15,9 +15,35 @@ export type RpcDefectEnvelope = {
|
|
|
15
15
|
readonly cause?: unknown;
|
|
16
16
|
};
|
|
17
17
|
export type RpcResponseEnvelope = RpcSuccessEnvelope | RpcFailureEnvelope | RpcDefectEnvelope;
|
|
18
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
18
19
|
export declare function formatUnknown(value: unknown): string;
|
|
19
20
|
export declare function extractErrorTag(error: unknown): string;
|
|
20
21
|
export declare function toDefectEnvelope(cause: unknown, prefix?: string): RpcDefectEnvelope;
|
|
21
22
|
export declare function safelyCall<T>(callback: ((context: T) => void) | undefined, context: T): void;
|
|
23
|
+
export type StreamDataFrame = {
|
|
24
|
+
readonly type: "data";
|
|
25
|
+
readonly streamId: string;
|
|
26
|
+
readonly payload: unknown;
|
|
27
|
+
};
|
|
28
|
+
export type StreamEndFrame = {
|
|
29
|
+
readonly type: "end";
|
|
30
|
+
readonly streamId: string;
|
|
31
|
+
};
|
|
32
|
+
export type StreamErrorFrame = {
|
|
33
|
+
readonly type: "error";
|
|
34
|
+
readonly streamId: string;
|
|
35
|
+
readonly error: {
|
|
36
|
+
readonly tag: string;
|
|
37
|
+
readonly data: unknown;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export type StreamDefectFrame = {
|
|
41
|
+
readonly type: "defect";
|
|
42
|
+
readonly streamId: string;
|
|
43
|
+
readonly message: string;
|
|
44
|
+
};
|
|
45
|
+
export type StreamFrame = StreamDataFrame | StreamEndFrame | StreamErrorFrame | StreamDefectFrame;
|
|
46
|
+
export declare function extractStreamIdFromRaw(value: unknown): string | null;
|
|
47
|
+
export declare function parseStreamFrame(value: unknown): StreamFrame | null;
|
|
22
48
|
export declare function parseRpcResponseEnvelope(value: unknown): RpcResponseEnvelope | null;
|
|
23
49
|
//# sourceMappingURL=protocol.d.ts.map
|
package/dist/protocol.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,mBAAmB,
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG,kBAAkB,GAAG,iBAAiB,CAAC;AAM9F,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAUtD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAOnF;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAU5F;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,cAAc,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;AAElG,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKpE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CA4CnE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CA+CnF"}
|
package/dist/protocol.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
function hasOwn(record, key) {
|
|
2
2
|
return Object.prototype.hasOwnProperty.call(record, key);
|
|
3
3
|
}
|
|
4
|
-
function isRecord(value) {
|
|
4
|
+
export function isRecord(value) {
|
|
5
5
|
return typeof value === "object" && value !== null;
|
|
6
6
|
}
|
|
7
7
|
export function formatUnknown(value) {
|
|
@@ -35,6 +35,48 @@ export function safelyCall(callback, context) {
|
|
|
35
35
|
// Diagnostics hooks must never crash transport internals.
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
export function extractStreamIdFromRaw(value) {
|
|
39
|
+
if (!isRecord(value) || typeof value.streamId !== "string") {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return value.streamId;
|
|
43
|
+
}
|
|
44
|
+
export function parseStreamFrame(value) {
|
|
45
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
if (typeof value.streamId !== "string") {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const streamId = value.streamId;
|
|
52
|
+
switch (value.type) {
|
|
53
|
+
case "data":
|
|
54
|
+
if (!hasOwn(value, "payload")) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return { type: "data", streamId, payload: value.payload };
|
|
58
|
+
case "end":
|
|
59
|
+
return { type: "end", streamId };
|
|
60
|
+
case "error":
|
|
61
|
+
if (!isRecord(value.error) ||
|
|
62
|
+
typeof value.error.tag !== "string" ||
|
|
63
|
+
!hasOwn(value.error, "data")) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
type: "error",
|
|
68
|
+
streamId,
|
|
69
|
+
error: { tag: value.error.tag, data: value.error.data },
|
|
70
|
+
};
|
|
71
|
+
case "defect":
|
|
72
|
+
if (typeof value.message !== "string") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return { type: "defect", streamId, message: value.message };
|
|
76
|
+
default:
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
38
80
|
export function parseRpcResponseEnvelope(value) {
|
|
39
81
|
if (!isRecord(value) || typeof value.type !== "string") {
|
|
40
82
|
return null;
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type RpcContract } from "./contract.ts";
|
|
2
|
-
import { type AnyEvent, type AnyMethod, type EventSubscriber, type EventSubscriberOptions, type RpcClient, type RpcClientOptions } from "./types.ts";
|
|
3
|
-
export declare function createRpcClient<const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent
|
|
4
|
-
export declare function createEventSubscriber<const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent
|
|
1
|
+
import { type AnyStreamMethod, type RpcContract } from "./contract.ts";
|
|
2
|
+
import { type AnyEvent, type AnyMethod, type EventSubscriber, type EventSubscriberOptions, type RpcClient, type RpcClientOptions, type StreamRpcClientHandle, type StreamRpcClientOptions } from "./types.ts";
|
|
3
|
+
export declare function createRpcClient<const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>, const StreamMethods extends ReadonlyArray<AnyStreamMethod> = readonly []>(contract: RpcContract<Methods, Events, StreamMethods>, options: RpcClientOptions): RpcClient<RpcContract<Methods, Events, StreamMethods>>;
|
|
4
|
+
export declare function createEventSubscriber<const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>, const StreamMethods extends ReadonlyArray<AnyStreamMethod> = readonly []>(contract: RpcContract<Methods, Events, StreamMethods>, options: EventSubscriberOptions): EventSubscriber<RpcContract<Methods, Events, StreamMethods>>;
|
|
5
|
+
export declare function createStreamRpcClient<const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>, const StreamMethods extends ReadonlyArray<AnyStreamMethod>>(contract: RpcContract<Methods, Events, StreamMethods>, options: StreamRpcClientOptions): StreamRpcClientHandle<RpcContract<Methods, Events, StreamMethods>>;
|
|
5
6
|
export { RpcDefectError } from "./types.ts";
|
|
6
7
|
//# sourceMappingURL=renderer.d.ts.map
|
package/dist/renderer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,WAAW,EAMjB,MAAM,eAAe,CAAC;AAUvB,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,SAAS,EAGd,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAE3B,KAAK,SAAS,EACd,KAAK,gBAAgB,EAMrB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,YAAY,CAAC;AAsEpB,wBAAgB,eAAe,CAC7B,KAAK,CAAC,OAAO,SAAS,aAAa,CAAC,SAAS,CAAC,EAC9C,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,QAAQ,CAAC,EAC5C,KAAK,CAAC,aAAa,SAAS,aAAa,CAAC,eAAe,CAAC,GAAG,SAAS,EAAE,EAExE,QAAQ,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,EACrD,OAAO,EAAE,gBAAgB,GACxB,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CA0JxD;AAqBD,wBAAgB,qBAAqB,CACnC,KAAK,CAAC,OAAO,SAAS,aAAa,CAAC,SAAS,CAAC,EAC9C,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,QAAQ,CAAC,EAC5C,KAAK,CAAC,aAAa,SAAS,aAAa,CAAC,eAAe,CAAC,GAAG,SAAS,EAAE,EAExE,QAAQ,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,EACrD,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAsF9D;AAUD,wBAAgB,qBAAqB,CACnC,KAAK,CAAC,OAAO,SAAS,aAAa,CAAC,SAAS,CAAC,EAC9C,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,QAAQ,CAAC,EAC5C,KAAK,CAAC,aAAa,SAAS,aAAa,CAAC,eAAe,CAAC,EAE1D,QAAQ,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,EACrD,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CA2NpE;AAED,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|