electron-effect-rpc 0.9.0 → 0.11.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/main.js CHANGED
@@ -1,34 +1,35 @@
1
1
  import * as S from "effect/Schema";
2
- import { Cause, Effect, Exit, FiberId, Stream } from "effect";
3
- import * as Runtime from "effect/Runtime";
2
+ import { Cause, Effect, Exit, Fiber, Result, Stream } from "effect";
3
+ import { isFunctionValue, isNumberValue, isRecord, isStringValue, toDiagnosticCause, } from "./boundary.js";
4
4
  import { isNoErrorSchema } from "./contract.js";
5
- import { extractErrorTag, formatUnknown, isRecord, safelyCall, toDefectEnvelope, } from "./protocol.js";
5
+ import { extractErrorTag, formatUnknown, safelyCall, toDefectEnvelope, } from "./protocol.js";
6
6
  import { assertValidChannelPrefix, defaultChannelPrefix, } from "./types.js";
7
+ function readNamed(record, key) {
8
+ return record[key];
9
+ }
7
10
  function resolveChannelPrefix(prefix) {
8
11
  return prefix ? assertValidChannelPrefix(prefix) : defaultChannelPrefix;
9
12
  }
10
13
  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");
14
+ return (isRecord(value) &&
15
+ isNumberValue(value.id) &&
16
+ isFunctionValue(value.isDestroyed) &&
17
+ isFunctionValue(value.send) &&
18
+ (value.once === undefined || isFunctionValue(value.once)) &&
19
+ (value.removeListener === undefined || isFunctionValue(value.removeListener)));
20
+ }
21
+ function parseWebContentsLike(value) {
22
+ return isWebContentsLike(value) ? value : null;
16
23
  }
17
24
  function extractSender(event) {
18
25
  if (!isRecord(event))
19
26
  return null;
20
- return isWebContentsLike(event.sender) ? event.sender : null;
21
- }
22
- function isImplementation(value) {
23
- return typeof value === "function";
24
- }
25
- function isStreamImplementation(value) {
26
- return typeof value === "function";
27
+ return parseWebContentsLike(event.sender);
27
28
  }
28
29
  export function createRpcEndpoint(contract, ipc, implementations, options) {
29
30
  const channelPrefix = resolveChannelPrefix(options.channelPrefix);
30
31
  const diagnostics = options.diagnostics;
31
- const runPromiseExit = Runtime.runPromiseExit(options.runtime);
32
+ const runPromiseExit = Effect.runPromiseExitWith(options.context);
32
33
  const implementationsByName = implementations;
33
34
  const methodNames = new Set(contract.methods.map((method) => method.name));
34
35
  for (const name in implementations) {
@@ -39,14 +40,14 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
39
40
  function reportProtocolError(method, response, cause) {
40
41
  safelyCall(diagnostics?.onProtocolError, {
41
42
  method,
42
- response,
43
- cause,
43
+ response: toDiagnosticCause(response),
44
+ cause: toDiagnosticCause(cause),
44
45
  });
45
46
  }
46
47
  const listeners = new Map();
47
48
  for (const method of contract.methods) {
48
- const impl = implementationsByName[method.name];
49
- if (!isImplementation(impl)) {
49
+ const impl = readNamed(implementationsByName, method.name);
50
+ if (!isFunctionValue(impl)) {
50
51
  throw new Error(`Missing implementation for RPC method: ${method.name}`);
51
52
  }
52
53
  const decodeInput = S.decodeUnknownSync(method.req);
@@ -63,7 +64,7 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
63
64
  scope: "rpc-request",
64
65
  name: method.name,
65
66
  payload: rawPayload,
66
- cause,
67
+ cause: toDiagnosticCause(cause),
67
68
  });
68
69
  return toDefectEnvelope(cause, `RPC ${method.name} request decode failed`);
69
70
  }
@@ -87,7 +88,7 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
87
88
  return toDefectEnvelope(cause, `RPC ${method.name} success encoding failed`);
88
89
  }
89
90
  }
90
- const failure = Cause.failureOption(exit.cause);
91
+ const failure = Cause.findErrorOption(exit.cause);
91
92
  if (failure._tag === "Some") {
92
93
  if (!encodeFailure) {
93
94
  return toDefectEnvelope(failure.value, `RPC ${method.name} returned a typed failure, but method declares NoError`);
@@ -106,9 +107,9 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
106
107
  return toDefectEnvelope(cause, `RPC ${method.name} failure encoding failed`);
107
108
  }
108
109
  }
109
- const defect = Cause.dieOption(exit.cause);
110
- if (defect._tag === "Some") {
111
- return toDefectEnvelope(defect.value, `RPC ${method.name} defect`);
110
+ const defect = Cause.findDefect(exit.cause);
111
+ if (Result.isSuccess(defect)) {
112
+ return toDefectEnvelope(defect.success, `RPC ${method.name} defect`);
112
113
  }
113
114
  return toDefectEnvelope(exit.cause, `RPC ${method.name} interrupted`);
114
115
  });
@@ -133,10 +134,10 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
133
134
  throw new Error(`Stream implementation provided for unknown stream method: ${name}`);
134
135
  }
135
136
  }
136
- const runFork = Runtime.runFork(options.runtime);
137
+ const runFork = Effect.runForkWith(options.context);
137
138
  for (const method of streamMethods) {
138
- const impl = streamHandlerImpls[method.name];
139
- if (!isStreamImplementation(impl)) {
139
+ const impl = readNamed(streamHandlerImpls, method.name);
140
+ if (!isFunctionValue(impl)) {
140
141
  throw new Error(`Missing implementation for stream method: ${method.name}`);
141
142
  }
142
143
  const decodeInput = S.decodeUnknownSync(method.req);
@@ -149,7 +150,7 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
149
150
  }
150
151
  const streamId = rawPayload.streamId;
151
152
  const rawData = rawPayload.data;
152
- if (typeof streamId !== "string" || streamId.length === 0) {
153
+ if (!isStringValue(streamId) || streamId.length === 0) {
153
154
  return toDefectEnvelope("Invalid streamId");
154
155
  }
155
156
  if (activeStreams.has(streamId)) {
@@ -167,8 +168,8 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
167
168
  const decodeCtx = {
168
169
  scope: "stream-request",
169
170
  name: method.name,
170
- payload: rawData,
171
- cause,
171
+ payload: toDiagnosticCause(rawData),
172
+ cause: toDiagnosticCause(cause),
172
173
  };
173
174
  safelyCall(diagnostics?.onDecodeFailure, decodeCtx);
174
175
  return toDefectEnvelope(cause, `Stream ${method.name} request decode failed`);
@@ -180,8 +181,8 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
180
181
  sender.send(sfChannel, frame);
181
182
  }
182
183
  },
183
- catch: () => undefined,
184
- }).pipe(Effect.ignore);
184
+ catch: toDiagnosticCause,
185
+ }).pipe(Effect.tapError((cause) => Effect.sync(() => reportProtocolError(method.name, frame, cause))), Effect.ignore);
185
186
  let handlerStream;
186
187
  try {
187
188
  handlerStream = impl(input, { sender });
@@ -190,7 +191,7 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
190
191
  return toDefectEnvelope(cause, `Stream ${method.name} implementation threw`);
191
192
  }
192
193
  const buildTerminalFrame = (cause) => {
193
- const failure = Cause.failureOption(cause);
194
+ const failure = Cause.findErrorOption(cause);
194
195
  if (failure._tag === "Some") {
195
196
  if (encodeFailure) {
196
197
  try {
@@ -217,14 +218,16 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
217
218
  message: `Stream ${method.name} returned a typed failure, but method declares NoError: ${formatUnknown(failure.value)}`,
218
219
  };
219
220
  }
220
- if (Cause.isInterruptedOnly(cause)) {
221
+ if (Cause.hasInterruptsOnly(cause)) {
221
222
  return { type: "end", streamId };
222
223
  }
223
- const defect = Cause.dieOption(cause);
224
+ const defect = Cause.findDefect(cause);
224
225
  return {
225
226
  type: "defect",
226
227
  streamId,
227
- message: defect._tag === "Some" ? formatUnknown(defect.value) : "Stream failed unexpectedly",
228
+ message: Result.isSuccess(defect)
229
+ ? formatUnknown(defect.success)
230
+ : "Stream failed unexpectedly",
228
231
  };
229
232
  };
230
233
  // Reserve entry BEFORE forking
@@ -233,7 +236,7 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
233
236
  senderId: sender.id,
234
237
  };
235
238
  const onSenderDestroyed = () => {
236
- entry.fiber?.unsafeInterruptAsFork(FiberId.none);
239
+ entry.fiber?.interruptUnsafe();
237
240
  };
238
241
  const streamEffect = handlerStream.pipe(Stream.mapEffect((chunk) => Effect.suspend(() => {
239
242
  // A destroyed renderer can never consume more chunks:
@@ -241,18 +244,17 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
241
244
  if (sender.isDestroyed()) {
242
245
  return Effect.interrupt;
243
246
  }
244
- return Effect.try({
245
- try: () => sender.send(sfChannel, { type: "data", streamId, payload: encodeChunk(chunk) }),
246
- catch: () => undefined,
247
- }).pipe(Effect.ignore);
248
- })), Stream.runDrain, Effect.andThen(() => trySend({ type: "end", streamId })), Effect.catchAllCause((cause) => sender.isDestroyed() ? Effect.void : trySend(buildTerminalFrame(cause))), Effect.ensuring(Effect.sync(() => {
247
+ // A lost chunk invalidates the stream. Let the transport boundary
248
+ // send a defect frame instead of reporting successful completion.
249
+ return Effect.sync(() => sender.send(sfChannel, { type: "data", streamId, payload: encodeChunk(chunk) }));
250
+ })), Stream.runDrain, Effect.andThen(() => trySend({ type: "end", streamId })), Effect.catchCause((cause) => sender.isDestroyed() ? Effect.void : trySend(buildTerminalFrame(cause))), Effect.ensuring(Effect.sync(() => {
249
251
  activeStreams.delete(streamId);
250
- if (typeof sender.removeListener === "function") {
252
+ if (sender.removeListener) {
251
253
  sender.removeListener("destroyed", onSenderDestroyed);
252
254
  }
253
255
  })));
254
256
  activeStreams.set(streamId, entry);
255
- if (typeof sender.once === "function") {
257
+ if (sender.once) {
256
258
  sender.once("destroyed", onSenderDestroyed);
257
259
  }
258
260
  let fiber;
@@ -261,7 +263,7 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
261
263
  }
262
264
  catch (cause) {
263
265
  activeStreams.delete(streamId);
264
- if (typeof sender.removeListener === "function") {
266
+ if (sender.removeListener) {
265
267
  sender.removeListener("destroyed", onSenderDestroyed);
266
268
  }
267
269
  return toDefectEnvelope(cause, `Stream ${method.name} failed to start`);
@@ -302,7 +304,7 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
302
304
  return { cancelled: false };
303
305
  }
304
306
  const streamId = rawPayload.streamId;
305
- if (typeof streamId !== "string")
307
+ if (!isStringValue(streamId))
306
308
  return { cancelled: false };
307
309
  const entry = activeStreams.get(streamId);
308
310
  if (!entry)
@@ -313,7 +315,7 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
313
315
  return { cancelled: false };
314
316
  // Interrupt the fiber
315
317
  if (entry.fiber) {
316
- entry.fiber.unsafeInterruptAsFork(FiberId.none);
318
+ entry.fiber.interruptUnsafe();
317
319
  }
318
320
  return { cancelled: true };
319
321
  });
@@ -340,7 +342,7 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
340
342
  // Interrupt all active stream fibers first
341
343
  for (const entry of activeStreams.values()) {
342
344
  if (entry.fiber) {
343
- entry.fiber.unsafeInterruptAsFork(FiberId.none);
345
+ entry.fiber.interruptUnsafe();
344
346
  }
345
347
  }
346
348
  activeStreams.clear();
@@ -439,12 +441,12 @@ export function createEventPublisher(_contract, options) {
439
441
  safelyCall(diagnostics?.onDecodeFailure, {
440
442
  scope: "event-payload",
441
443
  name: item.event.name,
442
- payload: item.payload,
443
- cause,
444
+ payload: toDiagnosticCause(item.payload),
445
+ cause: toDiagnosticCause(cause),
444
446
  });
445
447
  safelyCall(diagnostics?.onDroppedEvent, {
446
448
  event: item.event.name,
447
- payload: item.payload,
449
+ payload: toDiagnosticCause(item.payload),
448
450
  reason: "encoding_failed",
449
451
  queued: queue.length,
450
452
  dropped,
@@ -456,7 +458,7 @@ export function createEventPublisher(_contract, options) {
456
458
  dropped += 1;
457
459
  safelyCall(diagnostics?.onDroppedEvent, {
458
460
  event: item.event.name,
459
- payload: item.payload,
461
+ payload: toDiagnosticCause(item.payload),
460
462
  reason: "window_unavailable",
461
463
  queued: queue.length,
462
464
  dropped,
@@ -469,7 +471,7 @@ export function createEventPublisher(_contract, options) {
469
471
  dropped += 1;
470
472
  safelyCall(diagnostics?.onDroppedEvent, {
471
473
  event: item.event.name,
472
- payload: item.payload,
474
+ payload: toDiagnosticCause(item.payload),
473
475
  reason: "window_unavailable",
474
476
  queued: queue.length,
475
477
  dropped,
@@ -483,12 +485,12 @@ export function createEventPublisher(_contract, options) {
483
485
  dropped += 1;
484
486
  safelyCall(diagnostics?.onDispatchFailure, {
485
487
  event: item.event.name,
486
- payload: item.payload,
487
- cause,
488
+ payload: toDiagnosticCause(item.payload),
489
+ cause: toDiagnosticCause(cause),
488
490
  });
489
491
  safelyCall(diagnostics?.onDroppedEvent, {
490
492
  event: item.event.name,
491
- payload: item.payload,
493
+ payload: toDiagnosticCause(item.payload),
492
494
  reason: "dispatch_failed",
493
495
  queued: queue.length,
494
496
  dropped,
@@ -502,7 +504,10 @@ export function createEventPublisher(_contract, options) {
502
504
  }
503
505
  draining = true;
504
506
  try {
505
- while (running && !disposed && queue.length > 0) {
507
+ while (queue.length > 0) {
508
+ if (!running || disposed) {
509
+ break;
510
+ }
506
511
  const next = queue.shift();
507
512
  if (!next) {
508
513
  continue;
@@ -524,7 +529,7 @@ export function createEventPublisher(_contract, options) {
524
529
  if (evicted) {
525
530
  safelyCall(diagnostics?.onDroppedEvent, {
526
531
  event: evicted.event.name,
527
- payload: evicted.payload,
532
+ payload: toDiagnosticCause(evicted.payload),
528
533
  reason: "queue_full",
529
534
  queued: queue.length,
530
535
  dropped,
@@ -1,8 +1,22 @@
1
- import { type ChannelPrefix, type EventSubscribe, type RpcInvoke } from "./types.ts";
1
+ import { type IpcEncodedValue } from "./boundary.ts";
2
+ import { type ChannelPrefix, type EventSubscribe, type IpcInvokeEvent, type OnStreamFrame, type RpcInvoke } from "./types.ts";
3
+ export type ExposedRpcApi = {
4
+ readonly invoke: RpcInvoke;
5
+ readonly onStreamFrame: OnStreamFrame;
6
+ };
7
+ export type ExposedEventsApi = {
8
+ readonly subscribe: EventSubscribe;
9
+ };
10
+ export type ExposedIpcApi = {
11
+ readonly invoke: RpcInvoke;
12
+ readonly subscribe: EventSubscribe;
13
+ readonly onStreamFrame: OnStreamFrame;
14
+ };
15
+ export type ExposedBridgeValue = ExposedRpcApi | ExposedEventsApi | ExposedIpcApi;
2
16
  export type BridgeAdapters = {
3
17
  readonly invoke: RpcInvoke;
4
18
  readonly subscribe: EventSubscribe;
5
- readonly onStreamFrame: (listener: (frame: unknown) => void) => () => void;
19
+ readonly onStreamFrame: OnStreamFrame;
6
20
  };
7
21
  export type BridgeAdaptersOptions = {
8
22
  readonly channelPrefix?: ChannelPrefix;
@@ -15,18 +29,32 @@ export type IpcBridgeExposureOptions = BridgeAdaptersOptions & {
15
29
  readonly global?: string;
16
30
  };
17
31
  type ContextBridgeLike = {
18
- readonly exposeInMainWorld: (name: string, value: Record<string, unknown>) => void;
32
+ readonly exposeInMainWorld: (name: string, value: ExposedBridgeValue) => void;
19
33
  };
20
34
  type IpcRendererLike = {
21
- readonly invoke: (channel: string, payload: unknown) => Promise<unknown>;
22
- readonly on: (channel: string, handler: (event: unknown, payload: unknown) => void) => void;
23
- readonly removeListener: (channel: string, handler: (event: unknown, payload: unknown) => void) => void;
35
+ readonly invoke: (channel: string, payload: IpcEncodedValue) => Promise<IpcEncodedValue>;
36
+ readonly on: (channel: string, handler: (event: IpcInvokeEvent, payload: IpcEncodedValue) => void) => void;
37
+ readonly removeListener: (channel: string, handler: (event: IpcInvokeEvent, payload: IpcEncodedValue) => void) => void;
24
38
  };
25
- type ElectronRendererBindings = {
39
+ export type ElectronRendererBindings = {
26
40
  readonly contextBridge: ContextBridgeLike;
27
41
  readonly ipcRenderer: IpcRendererLike;
28
42
  };
29
- export declare function resolveElectronRendererBindings(moduleCandidate: unknown): ElectronRendererBindings;
43
+ type ElectronRuntimeFunction = (...arguments_: never[]) => void;
44
+ type ElectronDirectModuleCandidate = {
45
+ readonly contextBridge: {
46
+ readonly exposeInMainWorld: ElectronRuntimeFunction;
47
+ };
48
+ readonly ipcRenderer: {
49
+ readonly invoke: ElectronRuntimeFunction;
50
+ readonly on: ElectronRuntimeFunction;
51
+ readonly removeListener: ElectronRuntimeFunction;
52
+ };
53
+ };
54
+ export type ElectronModuleCandidate = ElectronDirectModuleCandidate | {
55
+ readonly default: ElectronModuleCandidate;
56
+ };
57
+ export declare function resolveElectronRendererBindings<T>(moduleCandidate: T): ElectronRendererBindings;
30
58
  export declare function createBridgeAdaptersFromBindings(bindings: ElectronRendererBindings, options?: BridgeAdaptersOptions): BridgeAdapters;
31
59
  export declare function exposeRpcBridgeFromBindings(bindings: ElectronRendererBindings, options?: BridgeExposureOptions): void;
32
60
  export declare function exposeIpcBridgeFromBindings(bindings: ElectronRendererBindings, options?: IpcBridgeExposureOptions): void;
@@ -1 +1 @@
1
- {"version":3,"file":"preload-bridge.d.ts","sourceRoot":"","sources":["../src/preload-bridge.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,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;AAEF,KAAK,iBAAiB,GAAG;IACvB,QAAQ,CAAC,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACpF,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzE,QAAQ,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,CAAC;IAC5F,QAAQ,CAAC,cAAc,EAAE,CACvB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,KAChD,IAAI,CAAC;CACX,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC9B,QAAQ,CAAC,aAAa,EAAE,iBAAiB,CAAC;IAC1C,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC;CACvC,CAAC;AAeF,wBAAgB,+BAA+B,CAC7C,eAAe,EAAE,OAAO,GACvB,wBAAwB,CAqB1B;AAED,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,wBAAwB,EAClC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,cAAc,CAkChB;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,wBAAwB,EAClC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAiBN;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,wBAAwB,EAClC,OAAO,CAAC,EAAE,wBAAwB,GACjC,IAAI,CAaN"}
1
+ {"version":3,"file":"preload-bridge.d.ts","sourceRoot":"","sources":["../src/preload-bridge.ts"],"names":[],"mappings":"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"}
@@ -1,32 +1,34 @@
1
- import { isRecord } from "./protocol.js";
1
+ import { isFunctionValue, isRecord } from "./boundary.js";
2
2
  import { assertValidChannelPrefix, defaultChannelPrefix, } from "./types.js";
3
3
  function isContextBridgeLike(value) {
4
- return isRecord(value) && typeof value.exposeInMainWorld === "function";
4
+ return isRecord(value) && isFunctionValue(value.exposeInMainWorld);
5
5
  }
6
6
  function isIpcRendererLike(value) {
7
7
  return (isRecord(value) &&
8
- typeof value.invoke === "function" &&
9
- typeof value.on === "function" &&
10
- typeof value.removeListener === "function");
8
+ isFunctionValue(value.invoke) &&
9
+ isFunctionValue(value.on) &&
10
+ isFunctionValue(value.removeListener));
11
+ }
12
+ function isElectronRendererBindings(value) {
13
+ return (isRecord(value) &&
14
+ isContextBridgeLike(value.contextBridge) &&
15
+ isIpcRendererLike(value.ipcRenderer));
16
+ }
17
+ function parseElectronRendererBindings(value) {
18
+ return isElectronRendererBindings(value) ? value : null;
11
19
  }
12
20
  export function resolveElectronRendererBindings(moduleCandidate) {
13
- const moduleDefault = isRecord(moduleCandidate) ? moduleCandidate.default : undefined;
14
- const source = isRecord(moduleCandidate)
15
- ? moduleCandidate
16
- : isRecord(moduleDefault)
17
- ? moduleDefault
18
- : undefined;
19
- if (!source) {
20
- throw new Error("electron-effect-rpc/preload requires Electron preload runtime bindings.");
21
+ const direct = parseElectronRendererBindings(moduleCandidate);
22
+ if (direct) {
23
+ return direct;
21
24
  }
22
- const { contextBridge, ipcRenderer } = source;
23
- if (!isContextBridgeLike(contextBridge) || !isIpcRendererLike(ipcRenderer)) {
24
- throw new Error("electron-effect-rpc/preload requires Electron preload runtime bindings.");
25
+ if (isRecord(moduleCandidate)) {
26
+ const fromDefault = parseElectronRendererBindings(moduleCandidate.default);
27
+ if (fromDefault) {
28
+ return fromDefault;
29
+ }
25
30
  }
26
- return {
27
- contextBridge,
28
- ipcRenderer,
29
- };
31
+ throw new Error("electron-effect-rpc/preload requires Electron preload runtime bindings.");
30
32
  }
31
33
  export function createBridgeAdaptersFromBindings(bindings, options) {
32
34
  const channelPrefix = options?.channelPrefix
@@ -1,29 +1,29 @@
1
+ import { type IpcEncodedValue } from "./boundary.ts";
1
2
  export type RpcSuccessEnvelope = {
2
3
  readonly type: "success";
3
- readonly data: unknown;
4
+ readonly data: IpcEncodedValue;
4
5
  };
5
6
  export type RpcFailureEnvelope = {
6
7
  readonly type: "failure";
7
8
  readonly error: {
8
9
  readonly tag: string;
9
- readonly data: unknown;
10
+ readonly data: IpcEncodedValue;
10
11
  };
11
12
  };
12
13
  export type RpcDefectEnvelope = {
13
14
  readonly type: "defect";
14
15
  readonly message: string;
15
- readonly cause?: unknown;
16
+ readonly cause?: IpcEncodedValue;
16
17
  };
17
18
  export type RpcResponseEnvelope = RpcSuccessEnvelope | RpcFailureEnvelope | RpcDefectEnvelope;
18
- export declare function isRecord(value: unknown): value is Record<string, unknown>;
19
- export declare function formatUnknown(value: unknown): string;
20
- export declare function extractErrorTag(error: unknown): string;
21
- export declare function toDefectEnvelope(cause: unknown, prefix?: string): RpcDefectEnvelope;
19
+ export { formatUnknown, isRecord, isStringValue } from "./boundary.ts";
20
+ export declare function extractErrorTag<T>(error: T): string;
21
+ export declare function toDefectEnvelope<T>(cause: T, prefix?: string): RpcDefectEnvelope;
22
22
  export declare function safelyCall<T>(callback: ((context: T) => void) | undefined, context: T): void;
23
23
  export type StreamDataFrame = {
24
24
  readonly type: "data";
25
25
  readonly streamId: string;
26
- readonly payload: unknown;
26
+ readonly payload: IpcEncodedValue;
27
27
  };
28
28
  export type StreamEndFrame = {
29
29
  readonly type: "end";
@@ -34,7 +34,7 @@ export type StreamErrorFrame = {
34
34
  readonly streamId: string;
35
35
  readonly error: {
36
36
  readonly tag: string;
37
- readonly data: unknown;
37
+ readonly data: IpcEncodedValue;
38
38
  };
39
39
  };
40
40
  export type StreamDefectFrame = {
@@ -43,7 +43,7 @@ export type StreamDefectFrame = {
43
43
  readonly message: string;
44
44
  };
45
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;
48
- export declare function parseRpcResponseEnvelope(value: unknown): RpcResponseEnvelope | null;
46
+ export declare function extractStreamIdFromRaw<T>(value: T): string | null;
47
+ export declare function parseStreamFrame<T>(value: T): StreamFrame | null;
48
+ export declare function parseRpcResponseEnvelope<T>(value: T): RpcResponseEnvelope | null;
49
49
  //# sourceMappingURL=protocol.d.ts.map
@@ -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,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"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AAEvB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;CAChC,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,eAAe,CAAC;KAChC,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,eAAe,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG,kBAAkB,GAAG,iBAAiB,CAAC;AAM9F,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEvE,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,CAUnD;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAOhF;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,eAAe,CAAC;CACnC,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,eAAe,CAAC;KAChC,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;AAMlG,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,GAAG,IAAI,CAMjE;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,WAAW,GAAG,IAAI,CAiChE;AAED,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,mBAAmB,GAAG,IAAI,CAyBhF"}
package/dist/protocol.js CHANGED
@@ -1,14 +1,11 @@
1
- function hasOwn(record, key) {
2
- return Object.prototype.hasOwnProperty.call(record, key);
3
- }
4
- export function isRecord(value) {
5
- return typeof value === "object" && value !== null;
6
- }
7
- export function formatUnknown(value) {
8
- return value instanceof Error ? value.message : String(value);
9
- }
1
+ import * as S from "effect/Schema";
2
+ import { formatUnknown, isRecord, isStringValue, } from "./boundary.js";
3
+ const TaggedValue = S.Struct({
4
+ _tag: S.String,
5
+ });
6
+ export { formatUnknown, isRecord, isStringValue } from "./boundary.js";
10
7
  export function extractErrorTag(error) {
11
- if (isRecord(error) && typeof error._tag === "string") {
8
+ if (S.is(TaggedValue)(error)) {
12
9
  return error._tag;
13
10
  }
14
11
  if (error instanceof Error && error.name.length > 0) {
@@ -35,84 +32,69 @@ export function safelyCall(callback, context) {
35
32
  // Diagnostics hooks must never crash transport internals.
36
33
  }
37
34
  }
35
+ function hasOwn(record, key) {
36
+ return Object.prototype.hasOwnProperty.call(record, key);
37
+ }
38
38
  export function extractStreamIdFromRaw(value) {
39
- if (!isRecord(value) || typeof value.streamId !== "string") {
39
+ if (!isRecord(value)) {
40
40
  return null;
41
41
  }
42
- return value.streamId;
42
+ const streamId = value.streamId;
43
+ return isStringValue(streamId) ? streamId : null;
43
44
  }
44
45
  export function parseStreamFrame(value) {
45
- if (!isRecord(value) || typeof value.type !== "string") {
46
+ if (!isRecord(value)) {
46
47
  return null;
47
48
  }
48
- if (typeof value.streamId !== "string") {
49
+ const streamId = extractStreamIdFromRaw(value);
50
+ if (streamId === null) {
49
51
  return null;
50
52
  }
51
- const streamId = value.streamId;
52
53
  switch (value.type) {
53
54
  case "data":
54
- if (!hasOwn(value, "payload")) {
55
- return null;
56
- }
57
- return { type: "data", streamId, payload: value.payload };
55
+ return hasOwn(value, "payload") ? { type: "data", streamId, payload: value.payload } : null;
58
56
  case "end":
59
57
  return { type: "end", streamId };
60
- case "error":
61
- if (!isRecord(value.error) ||
62
- typeof value.error.tag !== "string" ||
63
- !hasOwn(value.error, "data")) {
58
+ case "error": {
59
+ const error = value.error;
60
+ if (!isRecord(error) || !isStringValue(error.tag) || !hasOwn(error, "data")) {
64
61
  return null;
65
62
  }
66
63
  return {
67
64
  type: "error",
68
65
  streamId,
69
- error: { tag: value.error.tag, data: value.error.data },
66
+ error: { tag: error.tag, data: error.data },
70
67
  };
68
+ }
71
69
  case "defect":
72
- if (typeof value.message !== "string") {
73
- return null;
74
- }
75
- return { type: "defect", streamId, message: value.message };
70
+ return isStringValue(value.message)
71
+ ? { type: "defect", streamId, message: value.message }
72
+ : null;
76
73
  default:
77
74
  return null;
78
75
  }
79
76
  }
80
77
  export function parseRpcResponseEnvelope(value) {
81
- if (!isRecord(value) || typeof value.type !== "string") {
78
+ if (!isRecord(value)) {
82
79
  return null;
83
80
  }
84
81
  switch (value.type) {
85
82
  case "success":
86
- if (!hasOwn(value, "data")) {
87
- return null;
88
- }
89
- return {
90
- type: "success",
91
- data: value.data,
92
- };
93
- case "failure":
94
- if (!isRecord(value.error) || typeof value.error.tag !== "string") {
95
- return null;
96
- }
97
- if (!hasOwn(value.error, "data")) {
83
+ return hasOwn(value, "data") ? { type: "success", data: value.data } : null;
84
+ case "failure": {
85
+ const error = value.error;
86
+ if (!isRecord(error) || !isStringValue(error.tag) || !hasOwn(error, "data")) {
98
87
  return null;
99
88
  }
100
89
  return {
101
90
  type: "failure",
102
- error: {
103
- tag: value.error.tag,
104
- data: value.error.data,
105
- },
91
+ error: { tag: error.tag, data: error.data },
106
92
  };
93
+ }
107
94
  case "defect":
108
- if (typeof value.message !== "string") {
109
- return null;
110
- }
111
- return {
112
- type: "defect",
113
- message: value.message,
114
- cause: value.cause,
115
- };
95
+ return isStringValue(value.message)
96
+ ? { type: "defect", message: value.message, cause: value.cause }
97
+ : null;
116
98
  default:
117
99
  return null;
118
100
  }