electron-effect-rpc 0.1.1 → 0.2.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 CHANGED
@@ -11,9 +11,10 @@ uses Electron 38) and assumes ESM-capable bundling.
11
11
  - Single shared contract for methods and events.
12
12
  - End-to-end type safety using @effect/schema.
13
13
  - Promise-based renderer client with typed errors.
14
- - Effect-based main handlers with optional runtime injection.
15
- - Event bus and subscriber for typed renderer events.
16
- - No protocol handshake or versioning; schema decoding is the source of truth.
14
+ - Effect-based main handlers with explicit runtime injection.
15
+ - Explicit lifecycle handles for main-process RPC and event publishing.
16
+ - Bounded event queue with drop-oldest backpressure.
17
+ - Structured diagnostics hooks for decode/protocol/dispatch failures.
17
18
 
18
19
  ## Requirements
19
20
  - Electron with context isolation enabled.
@@ -99,36 +100,43 @@ export const ReadTextFile = rpc(
99
100
  ```ts
100
101
  import { app, ipcMain } from "electron";
101
102
  import { Effect } from "effect";
102
- import { createRpcServer, createEventBus } from "electron-effect-rpc/main";
103
+ import * as Runtime from "effect/Runtime";
104
+ import { createRpcEndpoint, createEventPublisher } from "electron-effect-rpc/main";
103
105
  import { contract, WorkUnitProgress } from "./contract.ts";
104
106
 
105
107
  const implementations = {
106
108
  GetAppVersion: () => Effect.succeed({ version: app.getVersion() }),
107
109
  };
108
110
 
109
- createRpcServer(contract, ipcMain, implementations);
111
+ const endpoint = createRpcEndpoint(contract, ipcMain, implementations, {
112
+ runtime: Runtime.defaultRuntime,
113
+ });
114
+ endpoint.start();
110
115
 
111
- const eventBus = createEventBus(contract, {
116
+ const publisher = createEventPublisher(contract, {
112
117
  getWindow: () => mainWindow,
113
118
  });
119
+ publisher.start();
114
120
 
115
- eventBus.emit(WorkUnitProgress, {
121
+ publisher.publish(WorkUnitProgress, {
116
122
  requestId: "req-1",
117
123
  chunk: "working...",
118
124
  done: false,
119
125
  });
120
126
  ```
121
127
 
122
- If your handlers require services in the Effect environment, provide a runtime:
128
+ Pass the runtime used to execute handler effects:
123
129
 
124
130
  ```ts
125
131
  import * as Runtime from "effect/Runtime";
126
- import { createRpcServer } from "electron-effect-rpc/main";
132
+ import { createRpcEndpoint } from "electron-effect-rpc/main";
127
133
  import { contract } from "./contract.ts";
128
134
 
129
- createRpcServer(contract, ipcMain, implementations, {
135
+ const endpoint = createRpcEndpoint(contract, ipcMain, implementations, {
130
136
  runtime: Runtime.defaultRuntime,
131
137
  });
138
+
139
+ endpoint.start();
132
140
  ```
133
141
 
134
142
  ### Preload: expose bridge globals
@@ -152,13 +160,25 @@ exposeRpcBridge({
152
160
  });
153
161
  ```
154
162
 
163
+ Or use low-level bridge adapters for custom exposure:
164
+ ```ts
165
+ import { createBridgeAdapters } from "electron-effect-rpc/preload";
166
+
167
+ const bridge = createBridgeAdapters();
168
+ // bridge.invoke(method, payload)
169
+ // bridge.subscribe(name, handler)
170
+ ```
171
+
155
172
  ### Renderer: create client and subscriber
156
173
  ```ts
157
174
  import { createRpcClient, createEventSubscriber } from "electron-effect-rpc/renderer";
158
175
  import { contract, WorkUnitProgress } from "./contract.ts";
159
176
 
160
177
  const client = createRpcClient(contract, { invoke: window.rpc.invoke });
161
- const events = createEventSubscriber(contract, { subscribe: window.events.subscribe });
178
+ const events = createEventSubscriber(contract, {
179
+ subscribe: window.events.subscribe,
180
+ decodeMode: "safe", // default
181
+ });
162
182
 
163
183
  const { version } = await client.GetAppVersion();
164
184
 
@@ -194,8 +214,10 @@ import { createInvokeStub } from "electron-effect-rpc/testing";
194
214
  import { contract } from "./contract.ts";
195
215
 
196
216
  const invoke = createInvokeStub(async (method, payload) => {
197
- // return encoded Exit values from your handler logic
198
- return payload;
217
+ return {
218
+ type: "success",
219
+ data: { version: "1.0.0" },
220
+ };
199
221
  });
200
222
 
201
223
  const client = createRpcClient(contract, { invoke });
@@ -210,7 +232,8 @@ expect(invoke.invocations).toEqual([
210
232
  You can stub `IpcMainLike` and collect registered handlers:
211
233
 
212
234
  ```ts
213
- import { createRpcServer } from "electron-effect-rpc/main";
235
+ import * as Runtime from "effect/Runtime";
236
+ import { createRpcEndpoint } from "electron-effect-rpc/main";
214
237
  import type { IpcMainLike } from "electron-effect-rpc/types";
215
238
  import { contract } from "./contract.ts";
216
239
 
@@ -219,9 +242,15 @@ const ipcMainStub: IpcMainLike = {
219
242
  handle: (channel, handler) => {
220
243
  handlers.set(channel, handler);
221
244
  },
245
+ removeHandler: (channel) => {
246
+ handlers.delete(channel);
247
+ },
222
248
  };
223
249
 
224
- createRpcServer(contract, ipcMainStub, implementations);
250
+ const endpoint = createRpcEndpoint(contract, ipcMainStub, implementations, {
251
+ runtime: Runtime.defaultRuntime,
252
+ });
253
+ endpoint.start();
225
254
  ```
226
255
 
227
256
  ## Error Handling
@@ -236,13 +265,13 @@ Entry points:
236
265
  - `electron-effect-rpc/contract`
237
266
  - `rpc`, `event`, `defineContract`, `exitSchemaFor`, `SchemaNoContext`, `NoError`
238
267
  - `electron-effect-rpc/types`
239
- - Type aliases such as `Implementations`, `RpcClient`, `RpcEventBus`, `IpcMainLike`
268
+ - Type aliases such as `Implementations`, `RpcClient`, `RpcEventPublisher`, `IpcMainLike`
240
269
  - `electron-effect-rpc/main`
241
- - `createRpcServer`, `createEventBus`
270
+ - `createRpcEndpoint`, `createEventPublisher`
242
271
  - `electron-effect-rpc/renderer`
243
272
  - `createRpcClient`, `createEventSubscriber`, `RpcDefectError`
244
273
  - `electron-effect-rpc/preload`
245
- - `exposeRpcBridge`
274
+ - `exposeRpcBridge`, `createBridgeAdapters`
246
275
  - `electron-effect-rpc/testing`
247
276
  - `createInvokeStub`, `createDeferred`
248
277
 
@@ -2,7 +2,8 @@ import * as S from "@effect/schema/Schema";
2
2
  export type SchemaNoContext = S.Schema.AnyNoContext;
3
3
  export declare const NoError: typeof S.Never;
4
4
  export type NoError = typeof NoError;
5
- export type ErrorSchema = SchemaNoContext | S.Schema<never, never, never>;
5
+ export type ErrorSchema = SchemaNoContext | NoError;
6
+ export declare function isNoErrorSchema(schema: ErrorSchema): schema is NoError;
6
7
  export interface RpcMethod<Name extends string, Req extends SchemaNoContext, Res extends SchemaNoContext, Err extends ErrorSchema = NoError> {
7
8
  readonly name: Name;
8
9
  readonly req: Req;
@@ -33,8 +34,8 @@ export interface RpcContract<Methods extends ReadonlyArray<AnyMethod>, Events ex
33
34
  readonly methods: Methods;
34
35
  readonly events: Events;
35
36
  }
36
- export declare const defineContract: <const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>>(input: {
37
+ export declare function defineContract<const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>>(input: {
37
38
  readonly methods: Methods;
38
39
  readonly events: Events;
39
- }) => RpcContract<Methods, Events>;
40
+ }): RpcContract<Methods, Events>;
40
41
  //# sourceMappingURL=contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
package/dist/contract.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import * as S from "@effect/schema/Schema";
2
2
  export const NoError = S.Never;
3
+ export function isNoErrorSchema(schema) {
4
+ return schema === NoError;
5
+ }
3
6
  export function rpc(name, req, res, err = NoError) {
4
7
  return { name, req, res, err };
5
8
  }
@@ -11,7 +14,7 @@ export const exitSchemaFor = (method) => S.Exit({
11
14
  failure: method.err,
12
15
  defect: S.Defect,
13
16
  });
14
- const collectDuplicates = (names) => {
17
+ function collectDuplicates(names) {
15
18
  const counts = new Map();
16
19
  const duplicates = [];
17
20
  for (const name of names) {
@@ -22,8 +25,8 @@ const collectDuplicates = (names) => {
22
25
  }
23
26
  }
24
27
  return duplicates;
25
- };
26
- export const defineContract = (input) => {
28
+ }
29
+ export function defineContract(input) {
27
30
  const { methods, events } = input;
28
31
  if (!Array.isArray(methods)) {
29
32
  throw new Error("RPC contract methods must be an array.");
@@ -40,4 +43,4 @@ export const defineContract = (input) => {
40
43
  throw new Error(`Duplicate RPC event name(s): ${duplicateEvents.join(", ")}`);
41
44
  }
42
45
  return input;
43
- };
46
+ }
package/dist/main.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type RpcContract } from "./contract.ts";
2
- import { type AnyEvent, type AnyMethod, type EventBusOptions, type Implementations, type IpcMainLike, type RpcEventBus, type RpcServerOptions } from "./types.ts";
3
- export declare const createRpcServer: <const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>, R = never>(contract: RpcContract<Methods, Events>, ipc: IpcMainLike, implementations: Implementations<RpcContract<Methods, Events>, R>, options?: RpcServerOptions<R>) => void;
4
- export declare const createEventBus: <const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>>(_contract: RpcContract<Methods, Events>, options: EventBusOptions) => RpcEventBus<RpcContract<Methods, Events>>;
1
+ import type { RpcContract } from "./contract.ts";
2
+ import { type AnyEvent, type AnyMethod, type EventPublisherOptions, type Implementations, type IpcMainLike, type RpcEndpoint, type RpcEndpointOptions, type RpcEventPublisher } from "./types.ts";
3
+ export declare function createRpcEndpoint<const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>, R = never>(contract: RpcContract<Methods, Events>, ipc: IpcMainLike, implementations: Implementations<RpcContract<Methods, Events>, R>, options: RpcEndpointOptions<R>): RpcEndpoint;
4
+ export declare function createEventPublisher<const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>>(_contract: RpcContract<Methods, Events>, options: EventPublisherOptions): RpcEventPublisher<RpcContract<Methods, Events>>;
5
5
  //# sourceMappingURL=main.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,WAAW,EAKjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACtB,MAAM,YAAY,CAAC;AAKpB,eAAO,MAAM,eAAe,GAC1B,KAAK,CAAC,OAAO,SAAS,aAAa,CAAC,SAAS,CAAC,EAC9C,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,QAAQ,CAAC,EAC5C,CAAC,GAAG,KAAK,EAET,UAAU,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EACtC,KAAK,WAAW,EAChB,iBAAiB,eAAe,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,EACjE,UAAU,gBAAgB,CAAC,CAAC,CAAC,KAC5B,IA6CF,CAAC;AAqCF,eAAO,MAAM,cAAc,GACzB,KAAK,CAAC,OAAO,SAAS,aAAa,CAAC,SAAS,CAAC,EAC9C,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,QAAQ,CAAC,EAE5C,WAAW,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EACvC,SAAS,eAAe,KACvB,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CA8C1C,CAAC"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,WAAW,EAKZ,MAAM,eAAe,CAAC;AAQvB,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,SAAS,EAEd,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,YAAY,CAAC;AAmBpB,wBAAgB,iBAAiB,CAC/B,KAAK,CAAC,OAAO,SAAS,aAAa,CAAC,SAAS,CAAC,EAC9C,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,QAAQ,CAAC,EAC5C,CAAC,GAAG,KAAK,EAET,QAAQ,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EACtC,GAAG,EAAE,WAAW,EAChB,eAAe,EAAE,eAAe,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,EACjE,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC7B,WAAW,CA4Kb;AAmBD,wBAAgB,oBAAoB,CAClC,KAAK,CAAC,OAAO,SAAS,aAAa,CAAC,SAAS,CAAC,EAC9C,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,QAAQ,CAAC,EAE5C,SAAS,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EACvC,OAAO,EAAE,qBAAqB,GAC7B,iBAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CA8KjD"}
package/dist/main.js CHANGED
@@ -1,18 +1,19 @@
1
1
  import * as S from "@effect/schema/Schema";
2
- import { Effect, PubSub, Stream } from "effect";
2
+ import { Cause, Effect, Exit } from "effect";
3
3
  import * as Runtime from "effect/Runtime";
4
- import { exitSchemaFor, } from "./contract.js";
4
+ import { isNoErrorSchema } from "./contract.js";
5
+ import { extractErrorTag, safelyCall, toDefectEnvelope, } from "./protocol.js";
5
6
  import { defaultChannelPrefix, } from "./types.js";
6
- const resolveChannelPrefix = (prefix) => prefix ?? defaultChannelPrefix;
7
- export const createRpcServer = (contract, ipc, implementations, options) => {
8
- const channelPrefix = resolveChannelPrefix(options?.channelPrefix);
9
- const runPromiseExit = (effect) => {
10
- if (options?.runtime) {
11
- return Runtime.runPromiseExit(options.runtime)(effect);
12
- }
13
- // @ts-expect-error -- default runtime only supports R=never when no runtime is provided
14
- return Effect.runPromiseExit(effect);
15
- };
7
+ function resolveChannelPrefix(prefix) {
8
+ return prefix ?? defaultChannelPrefix;
9
+ }
10
+ function isImplementation(value) {
11
+ return typeof value === "function";
12
+ }
13
+ export function createRpcEndpoint(contract, ipc, implementations, options) {
14
+ const channelPrefix = resolveChannelPrefix(options.channelPrefix);
15
+ const diagnostics = options.diagnostics;
16
+ const runPromiseExit = Runtime.runPromiseExit(options.runtime);
16
17
  const implementationsByName = implementations;
17
18
  const methodNames = new Set(contract.methods.map((method) => method.name));
18
19
  for (const name in implementations) {
@@ -20,53 +21,276 @@ export const createRpcServer = (contract, ipc, implementations, options) => {
20
21
  throw new Error(`Implementation provided for unknown RPC method: ${name}`);
21
22
  }
22
23
  }
23
- contract.methods.forEach((method) => {
24
+ function reportProtocolError(method, response, cause) {
25
+ safelyCall(diagnostics?.onProtocolError, {
26
+ method,
27
+ response,
28
+ cause,
29
+ });
30
+ }
31
+ const listeners = new Map();
32
+ for (const method of contract.methods) {
24
33
  const impl = implementationsByName[method.name];
25
34
  if (!isImplementation(impl)) {
26
35
  throw new Error(`Missing implementation for RPC method: ${method.name}`);
27
36
  }
28
- const exitSchema = exitSchemaFor(method);
29
- const encodeExit = S.encodeUnknownSync(exitSchema);
30
37
  const decodeInput = S.decodeUnknownSync(method.req);
38
+ const encodeSuccess = S.encodeSync(method.res);
39
+ const encodeFailure = isNoErrorSchema(method.err)
40
+ ? null
41
+ : S.encodeSync(method.err);
31
42
  const channel = `${channelPrefix.rpc}${method.name}`;
32
- ipc.handle(channel, async (_event, rawPayload) => {
43
+ listeners.set(channel, async function handleRpcRequest(_event, rawPayload) {
33
44
  let input;
34
45
  try {
35
46
  input = decodeInput(rawPayload);
36
47
  }
37
48
  catch (cause) {
38
- const defectExit = await runPromiseExit(Effect.die(cause));
39
- return encodeExit(defectExit);
49
+ safelyCall(diagnostics?.onDecodeFailure, {
50
+ scope: "rpc-request",
51
+ name: method.name,
52
+ payload: rawPayload,
53
+ cause,
54
+ });
55
+ return toDefectEnvelope(cause, `RPC ${method.name} request decode failed`);
56
+ }
57
+ let effect;
58
+ try {
59
+ effect = impl(input);
60
+ }
61
+ catch (cause) {
62
+ return toDefectEnvelope(cause, `RPC ${method.name} implementation threw`);
40
63
  }
41
- const exit = await runPromiseExit(impl(input));
42
- return encodeExit(exit);
64
+ const exit = await runPromiseExit(effect);
65
+ if (Exit.isSuccess(exit)) {
66
+ try {
67
+ return {
68
+ type: "success",
69
+ data: encodeSuccess(exit.value),
70
+ };
71
+ }
72
+ catch (cause) {
73
+ reportProtocolError(method.name, exit.value, cause);
74
+ return toDefectEnvelope(cause, `RPC ${method.name} success encoding failed`);
75
+ }
76
+ }
77
+ const failure = Cause.failureOption(exit.cause);
78
+ if (failure._tag === "Some") {
79
+ if (!encodeFailure) {
80
+ return toDefectEnvelope(failure.value, `RPC ${method.name} returned a typed failure, but method declares NoError`);
81
+ }
82
+ try {
83
+ return {
84
+ type: "failure",
85
+ error: {
86
+ tag: extractErrorTag(failure.value),
87
+ data: encodeFailure(failure.value),
88
+ },
89
+ };
90
+ }
91
+ catch (cause) {
92
+ reportProtocolError(method.name, failure.value, cause);
93
+ return toDefectEnvelope(cause, `RPC ${method.name} failure encoding failed`);
94
+ }
95
+ }
96
+ const defect = Cause.dieOption(exit.cause);
97
+ if (defect._tag === "Some") {
98
+ return toDefectEnvelope(defect.value, `RPC ${method.name} defect`);
99
+ }
100
+ return toDefectEnvelope(exit.cause, `RPC ${method.name} interrupted`);
43
101
  });
44
- });
45
- };
46
- const isImplementation = (value) => typeof value === "function";
47
- const encodePayload = (event, payload) => Effect.try({
48
- try: () => S.encodeSync(event.payload)(payload),
49
- catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)),
50
- });
51
- const dispatchToRenderer = (getWindow, channelPrefix, event, encoded) => Effect.sync(() => {
52
- const window = getWindow();
53
- if (window && !window.isDestroyed()) {
54
- const prefix = resolveChannelPrefix(channelPrefix);
55
- window.webContents.send(`${prefix.event}${event.name}`, encoded);
56
- }
57
- });
58
- export const createEventBus = (_contract, options) => {
59
- const pubsub = Effect.runSync(PubSub.unbounded());
60
- Effect.runFork(Effect.scoped(Effect.gen(function* () {
61
- const dequeue = yield* PubSub.subscribe(pubsub);
62
- yield* Stream.fromQueue(dequeue, { shutdown: true }).pipe(Stream.runForEach(({ event, payload }) => Effect.gen(function* () {
63
- const encodeResult = yield* Effect.either(encodePayload(event, payload));
64
- if (encodeResult._tag === "Left") {
102
+ }
103
+ let running = false;
104
+ let disposed = false;
105
+ function start() {
106
+ if (disposed) {
107
+ throw new Error("RPC endpoint has already been disposed.");
108
+ }
109
+ if (running) {
110
+ return;
111
+ }
112
+ for (const [channel, listener] of listeners) {
113
+ ipc.handle(channel, listener);
114
+ }
115
+ running = true;
116
+ }
117
+ function stop() {
118
+ if (!running) {
119
+ return;
120
+ }
121
+ for (const channel of listeners.keys()) {
122
+ ipc.removeHandler(channel);
123
+ }
124
+ running = false;
125
+ }
126
+ function dispose() {
127
+ if (disposed) {
128
+ return;
129
+ }
130
+ stop();
131
+ disposed = true;
132
+ }
133
+ function isRunning() {
134
+ return running;
135
+ }
136
+ return {
137
+ start,
138
+ stop,
139
+ dispose,
140
+ isRunning,
141
+ };
142
+ }
143
+ function clampQueueSize(maxQueueSize) {
144
+ if (maxQueueSize === undefined) {
145
+ return 1000;
146
+ }
147
+ if (!Number.isFinite(maxQueueSize) || maxQueueSize < 1) {
148
+ throw new Error("Event publisher maxQueueSize must be a positive finite number.");
149
+ }
150
+ return Math.floor(maxQueueSize);
151
+ }
152
+ export function createEventPublisher(_contract, options) {
153
+ const channelPrefix = resolveChannelPrefix(options.channelPrefix);
154
+ const diagnostics = options.diagnostics;
155
+ const maxQueueSize = clampQueueSize(options.maxQueueSize);
156
+ const queue = [];
157
+ let dropped = 0;
158
+ let running = false;
159
+ let disposed = false;
160
+ let draining = false;
161
+ let drainScheduled = false;
162
+ function scheduleDrain() {
163
+ if (!running || disposed || draining || drainScheduled) {
164
+ return;
165
+ }
166
+ drainScheduled = true;
167
+ queueMicrotask(() => {
168
+ drainScheduled = false;
169
+ drain();
170
+ });
171
+ }
172
+ function dispatch(item) {
173
+ let encoded;
174
+ try {
175
+ encoded = S.encodeSync(item.event.payload)(item.payload);
176
+ }
177
+ catch (cause) {
178
+ dropped += 1;
179
+ safelyCall(diagnostics?.onDecodeFailure, {
180
+ scope: "event-payload",
181
+ name: item.event.name,
182
+ payload: item.payload,
183
+ cause,
184
+ });
185
+ safelyCall(diagnostics?.onDroppedEvent, {
186
+ event: item.event.name,
187
+ payload: item.payload,
188
+ reason: "encoding_failed",
189
+ queued: queue.length,
190
+ dropped,
191
+ });
192
+ return;
193
+ }
194
+ const window = options.getWindow();
195
+ if (!window || window.isDestroyed()) {
196
+ return;
197
+ }
198
+ try {
199
+ window.webContents.send(`${channelPrefix.event}${item.event.name}`, encoded);
200
+ }
201
+ catch (cause) {
202
+ safelyCall(diagnostics?.onDispatchFailure, {
203
+ event: item.event.name,
204
+ payload: item.payload,
205
+ cause,
206
+ });
207
+ }
208
+ }
209
+ function drain() {
210
+ if (!running || disposed || draining) {
211
+ return;
212
+ }
213
+ draining = true;
214
+ try {
215
+ while (running && !disposed && queue.length > 0) {
216
+ const next = queue.shift();
217
+ if (!next) {
218
+ continue;
219
+ }
220
+ dispatch(next);
221
+ }
222
+ }
223
+ finally {
224
+ draining = false;
225
+ if (running && !disposed && queue.length > 0) {
226
+ scheduleDrain();
227
+ }
228
+ }
229
+ }
230
+ function enqueue(item) {
231
+ if (queue.length >= maxQueueSize) {
232
+ const evicted = queue.shift();
233
+ dropped += 1;
234
+ if (evicted) {
235
+ safelyCall(diagnostics?.onDroppedEvent, {
236
+ event: evicted.event.name,
237
+ payload: evicted.payload,
238
+ reason: "queue_full",
239
+ queued: queue.length,
240
+ dropped,
241
+ });
242
+ }
243
+ }
244
+ queue.push(item);
245
+ scheduleDrain();
246
+ }
247
+ function publish(event, payload) {
248
+ return Effect.sync(() => {
249
+ if (disposed) {
65
250
  return;
66
251
  }
67
- yield* dispatchToRenderer(options.getWindow, options.channelPrefix, event, encodeResult.right);
68
- })));
69
- })).pipe(Effect.catchAllCause(() => Effect.void), Effect.retry({ times: 3 }), Effect.catchAll(() => Effect.void)));
70
- const emit = (event, payload) => Effect.flatMap(PubSub.publish(pubsub, { event, payload }), () => Effect.void);
71
- return { emit };
72
- };
252
+ enqueue({ event, payload });
253
+ });
254
+ }
255
+ function start() {
256
+ if (disposed) {
257
+ throw new Error("Event publisher has already been disposed.");
258
+ }
259
+ if (running) {
260
+ return;
261
+ }
262
+ running = true;
263
+ scheduleDrain();
264
+ }
265
+ function stop() {
266
+ if (!running) {
267
+ return;
268
+ }
269
+ running = false;
270
+ }
271
+ function dispose() {
272
+ if (disposed) {
273
+ return;
274
+ }
275
+ stop();
276
+ queue.length = 0;
277
+ disposed = true;
278
+ }
279
+ function isRunning() {
280
+ return running;
281
+ }
282
+ function stats() {
283
+ return {
284
+ queued: queue.length,
285
+ dropped,
286
+ };
287
+ }
288
+ return {
289
+ publish,
290
+ start,
291
+ stop,
292
+ dispose,
293
+ isRunning,
294
+ stats,
295
+ };
296
+ }
package/dist/preload.d.ts CHANGED
@@ -1,9 +1,15 @@
1
- import { type ChannelPrefix } from "./types.ts";
2
- type BridgeOptions = {
1
+ import { type ChannelPrefix, type EventSubscribe, type RpcInvoke } from "./types.ts";
2
+ export type BridgeAdapters = {
3
+ readonly invoke: RpcInvoke;
4
+ readonly subscribe: EventSubscribe;
5
+ };
6
+ export type BridgeAdaptersOptions = {
7
+ readonly channelPrefix?: ChannelPrefix;
8
+ };
9
+ export type BridgeExposureOptions = BridgeAdaptersOptions & {
3
10
  readonly rpcGlobal?: string;
4
11
  readonly eventsGlobal?: string;
5
- readonly channelPrefix?: ChannelPrefix;
6
12
  };
7
- export declare const exposeRpcBridge: (options?: BridgeOptions) => void;
8
- export {};
13
+ export declare function createBridgeAdapters(options?: BridgeAdaptersOptions): BridgeAdapters;
14
+ export declare function exposeRpcBridge(options?: BridgeExposureOptions): void;
9
15
  //# sourceMappingURL=preload.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../src/preload.ts"],"names":[],"mappings":"AACA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAItE,KAAK,aAAa,GAAG;IACnB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;CACxC,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,UAAU,aAAa,KAAG,IAqBzD,CAAC"}
1
+ {"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../src/preload.ts"],"names":[],"mappings":"AACA,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;CACpC,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,wBAAgB,oBAAoB,CAClC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,cAAc,CAsBhB;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAerE"}
package/dist/preload.js CHANGED
@@ -1,17 +1,31 @@
1
1
  import { contextBridge, ipcRenderer } from "electron";
2
- import { defaultChannelPrefix } from "./types.js";
3
- export const exposeRpcBridge = (options) => {
4
- const rpcGlobal = options?.rpcGlobal ?? "rpc";
5
- const eventsGlobal = options?.eventsGlobal ?? "events";
2
+ import { defaultChannelPrefix, } from "./types.js";
3
+ export function createBridgeAdapters(options) {
6
4
  const channelPrefix = options?.channelPrefix ?? defaultChannelPrefix;
7
5
  const invoke = (method, payload) => ipcRenderer.invoke(`${channelPrefix.rpc}${method}`, payload);
8
6
  const subscribe = (event, listener) => {
9
7
  const wrapped = (_event, payload) => listener(payload);
10
- ipcRenderer.on(`${channelPrefix.event}${event}`, wrapped);
8
+ const channel = `${channelPrefix.event}${event}`;
9
+ ipcRenderer.on(channel, wrapped);
11
10
  return () => {
12
- ipcRenderer.removeListener(`${channelPrefix.event}${event}`, wrapped);
11
+ ipcRenderer.removeListener(channel, wrapped);
13
12
  };
14
13
  };
15
- contextBridge.exposeInMainWorld(rpcGlobal, { invoke });
16
- contextBridge.exposeInMainWorld(eventsGlobal, { subscribe });
17
- };
14
+ return {
15
+ invoke,
16
+ subscribe,
17
+ };
18
+ }
19
+ export function exposeRpcBridge(options) {
20
+ const rpcGlobal = options?.rpcGlobal ?? "rpc";
21
+ const eventsGlobal = options?.eventsGlobal ?? "events";
22
+ const adapters = createBridgeAdapters({
23
+ channelPrefix: options?.channelPrefix,
24
+ });
25
+ contextBridge.exposeInMainWorld(rpcGlobal, {
26
+ invoke: adapters.invoke,
27
+ });
28
+ contextBridge.exposeInMainWorld(eventsGlobal, {
29
+ subscribe: adapters.subscribe,
30
+ });
31
+ }
@@ -0,0 +1,23 @@
1
+ export type RpcSuccessEnvelope = {
2
+ readonly type: "success";
3
+ readonly data: unknown;
4
+ };
5
+ export type RpcFailureEnvelope = {
6
+ readonly type: "failure";
7
+ readonly error: {
8
+ readonly tag: string;
9
+ readonly data: unknown;
10
+ };
11
+ };
12
+ export type RpcDefectEnvelope = {
13
+ readonly type: "defect";
14
+ readonly message: string;
15
+ readonly cause?: unknown;
16
+ };
17
+ export type RpcResponseEnvelope = RpcSuccessEnvelope | RpcFailureEnvelope | RpcDefectEnvelope;
18
+ export declare function formatUnknown(value: unknown): string;
19
+ export declare function extractErrorTag(error: unknown): string;
20
+ export declare function toDefectEnvelope(cause: unknown, prefix?: string): RpcDefectEnvelope;
21
+ export declare function safelyCall<T>(callback: ((context: T) => void) | undefined, context: T): void;
22
+ export declare function parseRpcResponseEnvelope(value: unknown): RpcResponseEnvelope | null;
23
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +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,GAC3B,kBAAkB,GAClB,kBAAkB,GAClB,iBAAiB,CAAC;AAUtB,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAUtD;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,MAAM,GACd,iBAAiB,CAOnB;AAED,wBAAgB,UAAU,CAAC,CAAC,EAC1B,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,EAC5C,OAAO,EAAE,CAAC,GACT,IAAI,CAUN;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,GACb,mBAAmB,GAAG,IAAI,CA+C5B"}
@@ -0,0 +1,77 @@
1
+ function hasOwn(record, key) {
2
+ return Object.prototype.hasOwnProperty.call(record, key);
3
+ }
4
+ 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
+ }
10
+ export function extractErrorTag(error) {
11
+ if (isRecord(error) && typeof error._tag === "string") {
12
+ return error._tag;
13
+ }
14
+ if (error instanceof Error && error.name.length > 0) {
15
+ return error.name;
16
+ }
17
+ return "RpcError";
18
+ }
19
+ export function toDefectEnvelope(cause, prefix) {
20
+ const causeText = formatUnknown(cause);
21
+ return {
22
+ type: "defect",
23
+ message: prefix ? `${prefix}: ${causeText}` : causeText,
24
+ cause: causeText,
25
+ };
26
+ }
27
+ export function safelyCall(callback, context) {
28
+ if (!callback) {
29
+ return;
30
+ }
31
+ try {
32
+ callback(context);
33
+ }
34
+ catch {
35
+ // Diagnostics hooks must never crash transport internals.
36
+ }
37
+ }
38
+ export function parseRpcResponseEnvelope(value) {
39
+ if (!isRecord(value) || typeof value.type !== "string") {
40
+ return null;
41
+ }
42
+ switch (value.type) {
43
+ case "success":
44
+ if (!hasOwn(value, "data")) {
45
+ return null;
46
+ }
47
+ return {
48
+ type: "success",
49
+ data: value.data,
50
+ };
51
+ case "failure":
52
+ if (!isRecord(value.error) || typeof value.error.tag !== "string") {
53
+ return null;
54
+ }
55
+ if (!hasOwn(value.error, "data")) {
56
+ return null;
57
+ }
58
+ return {
59
+ type: "failure",
60
+ error: {
61
+ tag: value.error.tag,
62
+ data: value.error.data,
63
+ },
64
+ };
65
+ case "defect":
66
+ if (typeof value.message !== "string") {
67
+ return null;
68
+ }
69
+ return {
70
+ type: "defect",
71
+ message: value.message,
72
+ cause: value.cause,
73
+ };
74
+ default:
75
+ return null;
76
+ }
77
+ }
@@ -1,6 +1,6 @@
1
1
  import { type RpcContract } from "./contract.ts";
2
2
  import { type AnyEvent, type AnyMethod, type EventSubscriber, type EventSubscriberOptions, type RpcClient, type RpcClientOptions } from "./types.ts";
3
- export declare const createRpcClient: <const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>>(contract: RpcContract<Methods, Events>, options?: RpcClientOptions) => RpcClient<RpcContract<Methods, Events>>;
4
- export declare const createEventSubscriber: <const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>>(contract: RpcContract<Methods, Events>, options?: EventSubscriberOptions) => EventSubscriber<RpcContract<Methods, Events>>;
3
+ export declare function createRpcClient<const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>>(contract: RpcContract<Methods, Events>, options?: RpcClientOptions): RpcClient<RpcContract<Methods, Events>>;
4
+ export declare function createEventSubscriber<const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>>(contract: RpcContract<Methods, Events>, options?: EventSubscriberOptions): EventSubscriber<RpcContract<Methods, Events>>;
5
5
  export { RpcDefectError } from "./types.ts";
6
6
  //# sourceMappingURL=renderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,WAAW,EAIjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAE3B,KAAK,SAAS,EACd,KAAK,gBAAgB,EACtB,MAAM,YAAY,CAAC;AAyBpB,eAAO,MAAM,eAAe,GAC1B,KAAK,CAAC,OAAO,SAAS,aAAa,CAAC,SAAS,CAAC,EAC9C,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,QAAQ,CAAC,EAE5C,UAAU,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EACtC,UAAU,gBAAgB,KACzB,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAuExC,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,KAAK,CAAC,OAAO,SAAS,aAAa,CAAC,SAAS,CAAC,EAC9C,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,QAAQ,CAAC,EAE5C,UAAU,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EACtC,UAAU,sBAAsB,KAC/B,eAAe,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAoC9C,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,WAAW,EAIjB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,SAAS,EAGd,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAE3B,KAAK,SAAS,EACd,KAAK,gBAAgB,EAEtB,MAAM,YAAY,CAAC;AAuDpB,wBAAgB,eAAe,CAC7B,KAAK,CAAC,OAAO,SAAS,aAAa,CAAC,SAAS,CAAC,EAC9C,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,QAAQ,CAAC,EAE5C,QAAQ,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EACtC,OAAO,CAAC,EAAE,gBAAgB,GACzB,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAqHzC;AAqBD,wBAAgB,qBAAqB,CACnC,KAAK,CAAC,OAAO,SAAS,aAAa,CAAC,SAAS,CAAC,EAC9C,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,QAAQ,CAAC,EAE5C,QAAQ,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,EACtC,OAAO,CAAC,EAAE,sBAAsB,GAC/B,eAAe,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CA+E/C;AAED,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
package/dist/renderer.js CHANGED
@@ -1,81 +1,171 @@
1
1
  import * as S from "@effect/schema/Schema";
2
2
  import { Cause, Exit } from "effect";
3
- import { exitSchemaFor, } from "./contract.js";
3
+ import { exitSchemaFor, isNoErrorSchema, } from "./contract.js";
4
+ import { formatUnknown, parseRpcResponseEnvelope, safelyCall } from "./protocol.js";
4
5
  import { RpcDefectError, } from "./types.js";
5
- const formatCause = (cause) => cause instanceof Error ? cause.message : String(cause);
6
- const requireInvoke = (options) => {
6
+ function requireInvoke(options) {
7
7
  if (!options?.invoke) {
8
8
  throw new Error("RpcClientOptions.invoke is required.");
9
9
  }
10
10
  return options.invoke;
11
- };
12
- const requireSubscribe = (options) => {
11
+ }
12
+ function requireSubscribe(options) {
13
13
  if (!options?.subscribe) {
14
14
  throw new Error("EventSubscriberOptions.subscribe is required.");
15
15
  }
16
16
  return options.subscribe;
17
- };
18
- export const createRpcClient = (contract, options) => {
17
+ }
18
+ function decodeLegacyExit(method, raw) {
19
+ const exitSchema = exitSchemaFor(method);
20
+ const decodeExit = S.decodeUnknownSync(exitSchema);
21
+ const exit = decodeExit(raw);
22
+ if (Exit.isSuccess(exit)) {
23
+ return exit.value;
24
+ }
25
+ const failureOption = Cause.failureOption(exit.cause);
26
+ if (failureOption._tag === "Some") {
27
+ throw failureOption.value;
28
+ }
29
+ const defectOption = Cause.dieOption(exit.cause);
30
+ if (defectOption._tag === "Some") {
31
+ const defect = defectOption.value;
32
+ if (defect instanceof Error) {
33
+ throw new RpcDefectError(defect.message, defect);
34
+ }
35
+ throw new RpcDefectError(String(defect), defect);
36
+ }
37
+ throw new RpcDefectError("RPC call was interrupted or failed unexpectedly", exit.cause);
38
+ }
39
+ export function createRpcClient(contract, options) {
19
40
  const invoke = requireInvoke(options);
41
+ const diagnostics = options?.diagnostics;
42
+ const decodeMode = options?.rpcDecodeMode ?? "envelope";
20
43
  const call = async (method, input) => {
21
44
  let encoded;
22
45
  try {
23
46
  encoded = S.encodeSync(method.req)(input);
24
47
  }
25
48
  catch (cause) {
26
- throw new Error(`RPC ${method.name} request encoding failed: ${formatCause(cause)}`);
49
+ safelyCall(diagnostics?.onDecodeFailure, {
50
+ scope: "rpc-request",
51
+ name: method.name,
52
+ payload: input,
53
+ cause,
54
+ });
55
+ throw new Error(`RPC ${method.name} request encoding failed: ${formatUnknown(cause)}`);
27
56
  }
28
57
  const raw = await invoke(method.name, encoded);
29
- const exitSchema = exitSchemaFor(method);
30
- const decodeExit = S.decodeUnknownSync(exitSchema);
31
- let exit;
32
- try {
33
- exit = decodeExit(raw);
34
- }
35
- catch (cause) {
36
- throw new Error(`RPC ${method.name} response decoding failed: ${formatCause(cause)}`);
37
- }
38
- if (Exit.isSuccess(exit)) {
39
- return exit.value;
40
- }
41
- const cause = exit.cause;
42
- const failureOption = Cause.failureOption(cause);
43
- if (failureOption._tag === "Some") {
44
- throw failureOption.value;
58
+ const envelope = parseRpcResponseEnvelope(raw);
59
+ if (envelope) {
60
+ switch (envelope.type) {
61
+ case "success":
62
+ try {
63
+ return S.decodeUnknownSync(method.res)(envelope.data);
64
+ }
65
+ catch (cause) {
66
+ safelyCall(diagnostics?.onDecodeFailure, {
67
+ scope: "rpc-response",
68
+ name: method.name,
69
+ payload: envelope.data,
70
+ cause,
71
+ });
72
+ throw new Error(`RPC ${method.name} success payload decoding failed: ${formatUnknown(cause)}`);
73
+ }
74
+ case "failure":
75
+ if (isNoErrorSchema(method.err)) {
76
+ throw new RpcDefectError(`RPC ${method.name} received a failure for a method that declares NoError`, envelope.error);
77
+ }
78
+ let decodedError;
79
+ try {
80
+ decodedError = S.decodeUnknownSync(method.err)(envelope.error.data);
81
+ }
82
+ catch (cause) {
83
+ safelyCall(diagnostics?.onDecodeFailure, {
84
+ scope: "rpc-response",
85
+ name: method.name,
86
+ payload: envelope.error,
87
+ cause,
88
+ });
89
+ throw new Error(`RPC ${method.name} failure payload decoding failed: ${formatUnknown(cause)}`);
90
+ }
91
+ throw decodedError;
92
+ case "defect":
93
+ throw new RpcDefectError(envelope.message, envelope.cause);
94
+ }
45
95
  }
46
- const defectOption = Cause.dieOption(cause);
47
- if (defectOption._tag === "Some") {
48
- const defect = defectOption.value;
49
- if (defect instanceof Error) {
50
- throw new RpcDefectError(defect.message, defect);
96
+ if (decodeMode === "dual") {
97
+ try {
98
+ return decodeLegacyExit(method, raw);
99
+ }
100
+ catch (cause) {
101
+ safelyCall(diagnostics?.onProtocolError, {
102
+ method: method.name,
103
+ response: raw,
104
+ cause,
105
+ });
106
+ throw cause;
51
107
  }
52
- throw new RpcDefectError(String(defect), defect);
53
108
  }
54
- throw new RpcDefectError("RPC call was interrupted or failed unexpectedly", cause);
109
+ const cause = new Error(`RPC ${method.name} response was not a valid envelope.`);
110
+ safelyCall(diagnostics?.onProtocolError, {
111
+ method: method.name,
112
+ response: raw,
113
+ cause,
114
+ });
115
+ throw cause;
55
116
  };
56
117
  const client = Object.create(null);
57
118
  const clientRecord = client;
58
- contract.methods.forEach((method) => {
119
+ for (const method of contract.methods) {
59
120
  const caller = (input) => {
60
121
  const payload = input ?? S.decodeUnknownSync(method.req)({});
61
122
  return call(method, payload);
62
123
  };
63
124
  clientRecord[method.name] = caller;
64
- });
125
+ }
65
126
  return client;
66
- };
67
- export const createEventSubscriber = (contract, options) => {
127
+ }
128
+ function reportDecodeFailure(mode, eventName, payload, cause, options) {
129
+ safelyCall(options?.diagnostics?.onDecodeFailure, {
130
+ scope: "event-payload",
131
+ name: eventName,
132
+ payload,
133
+ cause,
134
+ });
135
+ if (mode === "strict") {
136
+ throw cause;
137
+ }
138
+ }
139
+ export function createEventSubscriber(contract, options) {
68
140
  const subscribe = requireSubscribe(options);
141
+ const mode = options?.decodeMode ?? "safe";
69
142
  const eventMap = new Map();
143
+ const subscriptions = new Set();
70
144
  for (const event of contract.events) {
71
145
  eventMap.set(event.name, event);
72
146
  }
147
+ function registerUnsubscribe(unsubscribe) {
148
+ subscriptions.add(unsubscribe);
149
+ return () => {
150
+ if (subscriptions.delete(unsubscribe)) {
151
+ unsubscribe();
152
+ }
153
+ };
154
+ }
73
155
  const subscribeEvent = (event, handler) => {
74
156
  const decoder = S.decodeUnknownSync(event.payload);
75
- return subscribe(event.name, (payload) => {
76
- const decoded = decoder(payload);
157
+ const unsubscribe = subscribe(event.name, (payload) => {
158
+ let decoded;
159
+ try {
160
+ decoded = decoder(payload);
161
+ }
162
+ catch (cause) {
163
+ reportDecodeFailure(mode, event.name, payload, cause, options);
164
+ return;
165
+ }
77
166
  handler(decoded);
78
167
  });
168
+ return registerUnsubscribe(unsubscribe);
79
169
  };
80
170
  const subscribeByName = (name, handler) => {
81
171
  const event = eventMap.get(name);
@@ -83,11 +173,29 @@ export const createEventSubscriber = (contract, options) => {
83
173
  throw new Error(`Unknown event: ${name}`);
84
174
  }
85
175
  const decoder = S.decodeUnknownSync(event.payload);
86
- return subscribe(name, (payload) => handler(decoder(payload)));
176
+ const unsubscribe = subscribe(name, (payload) => {
177
+ let decoded;
178
+ try {
179
+ decoded = decoder(payload);
180
+ }
181
+ catch (cause) {
182
+ reportDecodeFailure(mode, name, payload, cause, options);
183
+ return;
184
+ }
185
+ handler(decoded);
186
+ });
187
+ return registerUnsubscribe(unsubscribe);
87
188
  };
189
+ function dispose() {
190
+ for (const unsubscribe of subscriptions) {
191
+ unsubscribe();
192
+ }
193
+ subscriptions.clear();
194
+ }
88
195
  return {
89
196
  subscribe: subscribeEvent,
90
197
  subscribeByName,
198
+ dispose,
91
199
  };
92
- };
200
+ }
93
201
  export { RpcDefectError } from "./types.js";
package/dist/types.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type * as Effect from "effect/Effect";
2
2
  import type * as Runtime from "effect/Runtime";
3
- import type { BrowserWindow } from "electron";
4
3
  import type { AnyEvent, AnyMethod, ExtractMethod, RpcContract, RpcError, RpcEventPayload, RpcInput, RpcOutput } from "./contract.ts";
5
4
  export type { AnyEvent, AnyMethod, ErrorSchema, ExtractMethod, RpcContract, RpcError, RpcEvent, RpcEventPayload, RpcInput, RpcMethod, RpcOutput, SchemaNoContext, } from "./contract.ts";
6
5
  export type Implementations<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>, R = never> = {
@@ -21,32 +20,105 @@ export type ChannelPrefix = {
21
20
  readonly event: string;
22
21
  };
23
22
  export declare const defaultChannelPrefix: ChannelPrefix;
24
- export type IpcMainLike = {
25
- readonly handle: (channel: string, listener: (event: unknown, payload: unknown) => unknown) => unknown;
23
+ export type DecodeFailureScope = "rpc-request" | "rpc-response" | "event-payload";
24
+ export type DecodeFailureContext = {
25
+ readonly scope: DecodeFailureScope;
26
+ readonly name: string;
27
+ readonly payload: unknown;
28
+ readonly cause: unknown;
29
+ };
30
+ export type ProtocolErrorContext = {
31
+ readonly method: string;
32
+ readonly response: unknown;
33
+ readonly cause: unknown;
34
+ };
35
+ export type DispatchFailureContext = {
36
+ readonly event: string;
37
+ readonly payload: unknown;
38
+ readonly cause: unknown;
39
+ };
40
+ export type DroppedEventReason = "queue_full" | "encoding_failed";
41
+ export type DroppedEventContext = {
42
+ readonly event: string;
43
+ readonly payload: unknown;
44
+ readonly reason: DroppedEventReason;
45
+ readonly queued: number;
46
+ readonly dropped: number;
26
47
  };
27
48
  export type RpcInvoke = (method: string, payload: unknown) => Promise<unknown>;
28
- /** Provide a Runtime when handlers require services (R). */
29
- export type RpcServerOptions<R = never> = {
30
- readonly channelPrefix?: ChannelPrefix;
31
- readonly runtime?: Runtime.Runtime<R>;
49
+ export type RpcResponseDecodeMode = "envelope" | "dual";
50
+ export type RpcClientDiagnostics = {
51
+ readonly onDecodeFailure?: (context: DecodeFailureContext) => void;
52
+ readonly onProtocolError?: (context: ProtocolErrorContext) => void;
32
53
  };
33
54
  export type RpcClientOptions = {
34
55
  readonly invoke?: RpcInvoke;
35
- readonly channelPrefix?: ChannelPrefix;
56
+ readonly diagnostics?: RpcClientDiagnostics;
57
+ readonly rpcDecodeMode?: RpcResponseDecodeMode;
36
58
  };
37
- export type EventBusOptions = {
59
+ export type RpcEndpointDiagnostics = {
60
+ readonly onDecodeFailure?: (context: DecodeFailureContext) => void;
61
+ readonly onProtocolError?: (context: ProtocolErrorContext) => void;
62
+ };
63
+ export type IpcMainLike = {
64
+ readonly handle: (channel: string, listener: (event: unknown, payload: unknown) => unknown) => unknown;
65
+ readonly removeHandler: (channel: string) => unknown;
66
+ };
67
+ export interface RpcEndpoint {
68
+ readonly start: () => void;
69
+ readonly stop: () => void;
70
+ readonly dispose: () => void;
71
+ readonly isRunning: () => boolean;
72
+ }
73
+ /**
74
+ * Runtime used to execute handler effects.
75
+ */
76
+ export type RpcEndpointOptions<R = never> = {
38
77
  readonly channelPrefix?: ChannelPrefix;
39
- readonly getWindow: () => BrowserWindow | null;
78
+ readonly runtime: Runtime.Runtime<R>;
79
+ readonly diagnostics?: RpcEndpointDiagnostics;
40
80
  };
41
- export type EventSubscriberOptions = {
81
+ export type EventPublisherDiagnostics = {
82
+ readonly onDecodeFailure?: (context: DecodeFailureContext) => void;
83
+ readonly onDispatchFailure?: (context: DispatchFailureContext) => void;
84
+ readonly onDroppedEvent?: (context: DroppedEventContext) => void;
85
+ };
86
+ export type RendererWindowLike = {
87
+ readonly isDestroyed: () => boolean;
88
+ readonly webContents: {
89
+ readonly send: (channel: string, payload: unknown) => void;
90
+ };
91
+ };
92
+ export type EventPublisherOptions = {
42
93
  readonly channelPrefix?: ChannelPrefix;
43
- readonly subscribe?: (name: string, handler: (payload: unknown) => void) => () => void;
94
+ readonly getWindow: () => RendererWindowLike | null;
95
+ readonly maxQueueSize?: number;
96
+ readonly diagnostics?: EventPublisherDiagnostics;
44
97
  };
45
- export interface RpcEventBus<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>> {
46
- readonly emit: <E extends C["events"][number]>(event: E, payload: RpcEventPayload<E>) => Effect.Effect<void, never>;
98
+ export interface RpcEventPublisher<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>> {
99
+ readonly publish: <E extends C["events"][number]>(event: E, payload: RpcEventPayload<E>) => Effect.Effect<void, never>;
100
+ readonly start: () => void;
101
+ readonly stop: () => void;
102
+ readonly dispose: () => void;
103
+ readonly isRunning: () => boolean;
104
+ readonly stats: () => {
105
+ readonly queued: number;
106
+ readonly dropped: number;
107
+ };
47
108
  }
109
+ export type EventDecodeMode = "safe" | "strict";
110
+ export type EventSubscribe = (name: string, handler: (payload: unknown) => void) => () => void;
111
+ export type EventSubscriberDiagnostics = {
112
+ readonly onDecodeFailure?: (context: DecodeFailureContext) => void;
113
+ };
114
+ export type EventSubscriberOptions = {
115
+ readonly subscribe?: EventSubscribe;
116
+ readonly decodeMode?: EventDecodeMode;
117
+ readonly diagnostics?: EventSubscriberDiagnostics;
118
+ };
48
119
  export interface EventSubscriber<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>> {
49
120
  readonly subscribe: <E extends C["events"][number]>(event: E, handler: (payload: RpcEventPayload<E>) => void) => () => void;
50
- readonly subscribeByName: (name: C["events"][number]["name"], handler: (payload: unknown) => void) => () => void;
121
+ readonly subscribeByName: (name: string, handler: (payload: unknown) => void) => () => void;
122
+ readonly dispose: () => void;
51
123
  }
52
124
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electron-effect-rpc",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Typed IPC RPC for Electron, built on Effect and @effect/schema",
5
5
  "type": "module",
6
6
  "exports": {