electron-effect-rpc 0.2.0 → 0.4.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
@@ -1,24 +1,26 @@
1
1
  # electron-effect-rpc
2
2
 
3
- Typed IPC RPC for Electron, built on Effect and @effect/schema. This library
4
- lets you define a shared contract, generate a typed RPC client in the renderer,
5
- register handlers in the main process, and stream typed events across processes.
3
+ Typed IPC RPC for Electron, built on Effect and @effect/schema.
6
4
 
7
- This package is ESM-only. It targets modern Electron runtimes (current project
8
- uses Electron 38) and assumes ESM-capable bundling.
5
+ The ergonomic default is now a single shared `createIpcKit` configuration that
6
+ you reuse in main, preload, and renderer code. Low-level subpath APIs still
7
+ exist and remain fully supported.
8
+
9
+ This package is ESM-only. It targets modern Electron runtimes and assumes an
10
+ ESM-capable build pipeline.
9
11
 
10
12
  ## Features
11
13
  - Single shared contract for methods and events.
12
- - End-to-end type safety using @effect/schema.
13
- - Promise-based renderer client with typed errors.
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.
14
+ - Single shared kit config to eliminate cross-process prefix drift.
15
+ - End-to-end schema validation at IPC boundaries.
16
+ - Effect-first renderer RPC with typed domain and defect channels.
17
+ - Effect-native main handlers with explicit runtime injection.
18
+ - Explicit lifecycle handles and bounded event queue backpressure.
17
19
  - Structured diagnostics hooks for decode/protocol/dispatch failures.
18
20
 
19
21
  ## Requirements
20
22
  - Electron with context isolation enabled.
21
- - ESM-capable build pipeline.
23
+ - ESM-capable bundling.
22
24
  - Peer dependencies: `effect`, `@effect/schema`, `electron`.
23
25
 
24
26
  ## Installation
@@ -26,31 +28,12 @@ uses Electron 38) and assumes ESM-capable bundling.
26
28
  bun add electron-effect-rpc effect @effect/schema
27
29
  ```
28
30
 
29
- If you are in a monorepo workspace, add the dependency to the target package
30
- and let the workspace resolver handle the rest.
31
-
32
- ## Core Concepts
33
-
34
- ### Communication directions
35
-
36
- This library provides two communication patterns for Electron IPC:
37
-
38
- | Direction | Mechanism | Pattern | Use case |
39
- |-----------|-----------|---------|----------|
40
- | **Renderer → Main** | RPC methods | Request/response | Fetching data, triggering actions, calling main process APIs |
41
- | **Main → Renderer** | Event bus | Push/broadcast | Progress updates, state changes, background task notifications |
42
-
43
- **RPC methods** are for when the renderer needs something from the main process. The renderer calls a method and awaits a typed response.
44
-
45
- **Events** are for when the main process needs to notify the renderer. The main process emits events whenever it wants, and the renderer subscribes to receive them.
46
-
47
- ### Defining methods and events
48
-
49
- Define methods and events using schema-based helpers:
31
+ ## Quickstart (Kit-First)
50
32
 
33
+ ### 1) Define contract and kit once
51
34
  ```ts
52
35
  import * as S from "@effect/schema/Schema";
53
- import { defineContract, event, rpc } from "electron-effect-rpc/contract";
36
+ import { createIpcKit, defineContract, event, rpc } from "electron-effect-rpc";
54
37
 
55
38
  export const GetAppVersion = rpc(
56
39
  "GetAppVersion",
@@ -67,218 +50,143 @@ export const WorkUnitProgress = event(
67
50
  })
68
51
  );
69
52
 
70
- const methods = [GetAppVersion] as const;
71
- const events = [WorkUnitProgress] as const;
53
+ const contract = defineContract({
54
+ methods: [GetAppVersion] as const,
55
+ events: [WorkUnitProgress] as const,
56
+ });
72
57
 
73
- export const contract = defineContract({ methods, events });
58
+ export const ipc = createIpcKit({
59
+ contract,
60
+ channelPrefix: { rpc: "rpc/", event: "event/" },
61
+ bridge: { global: "api" },
62
+ decode: { rpc: "envelope", events: "safe" },
63
+ });
74
64
  ```
75
65
 
76
- ### Errors
77
- Error schemas should be `Schema.TaggedError` classes. If a method does not
78
- declare an error schema, it uses `NoError` and the error channel is `never`.
79
-
80
- ```ts
81
- import * as S from "@effect/schema/Schema";
82
- import { rpc } from "electron-effect-rpc/contract";
83
-
84
- export class FileReadError extends S.TaggedError<FileReadError>()("FileReadError", {
85
- message: S.String,
86
- path: S.String,
87
- }) {}
88
-
89
- export const ReadTextFile = rpc(
90
- "ReadTextFile",
91
- S.Struct({ path: S.String }),
92
- S.Struct({ content: S.String }),
93
- FileReadError
94
- );
95
- ```
96
-
97
- ## Usage
98
-
99
- ### Main process: register handlers
66
+ ### 2) Main process
100
67
  ```ts
101
68
  import { app, ipcMain } from "electron";
102
69
  import { Effect } from "effect";
103
70
  import * as Runtime from "effect/Runtime";
104
- import { createRpcEndpoint, createEventPublisher } from "electron-effect-rpc/main";
105
- import { contract, WorkUnitProgress } from "./contract.ts";
106
-
107
- const implementations = {
108
- GetAppVersion: () => Effect.succeed({ version: app.getVersion() }),
109
- };
71
+ import { ipc, WorkUnitProgress } from "./shared-ipc.ts";
110
72
 
111
- const endpoint = createRpcEndpoint(contract, ipcMain, implementations, {
73
+ const mainRpc = ipc.main({
74
+ ipcMain,
75
+ handlers: {
76
+ GetAppVersion: () => Effect.succeed({ version: app.getVersion() }),
77
+ },
112
78
  runtime: Runtime.defaultRuntime,
113
- });
114
- endpoint.start();
115
-
116
- const publisher = createEventPublisher(contract, {
117
79
  getWindow: () => mainWindow,
118
80
  });
119
- publisher.start();
120
81
 
121
- publisher.publish(WorkUnitProgress, {
82
+ mainRpc.start();
83
+
84
+ void Effect.runPromise(mainRpc.publish(WorkUnitProgress, {
122
85
  requestId: "req-1",
123
- chunk: "working...",
86
+ chunk: "starting",
124
87
  done: false,
125
- });
126
- ```
127
-
128
- Pass the runtime used to execute handler effects:
129
-
130
- ```ts
131
- import * as Runtime from "effect/Runtime";
132
- import { createRpcEndpoint } from "electron-effect-rpc/main";
133
- import { contract } from "./contract.ts";
134
-
135
- const endpoint = createRpcEndpoint(contract, ipcMain, implementations, {
136
- runtime: Runtime.defaultRuntime,
137
- });
138
-
139
- endpoint.start();
88
+ }));
140
89
  ```
141
90
 
142
- ### Preload: expose bridge globals
91
+ ### 3) Preload
143
92
  ```ts
144
- import { exposeRpcBridge } from "electron-effect-rpc/preload";
93
+ import { ipc } from "./shared-ipc.ts";
145
94
 
146
- exposeRpcBridge();
95
+ ipc.preload().expose();
147
96
  ```
148
97
 
149
- Defaults:
150
- - RPC global: `window.rpc.invoke(method, payload)`
151
- - Events global: `window.events.subscribe(name, handler)`
152
- - Channel prefix: `rpc/` and `event/`
98
+ This exposes one global by default: `window.api`.
153
99
 
154
- You can override globals and prefixes:
100
+ ### 4) Renderer
155
101
  ```ts
156
- exposeRpcBridge({
157
- rpcGlobal: "rpcApi",
158
- eventsGlobal: "rpcEvents",
159
- channelPrefix: { rpc: "rpc/", event: "events/" },
160
- });
161
- ```
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
-
172
- ### Renderer: create client and subscriber
173
- ```ts
174
- import { createRpcClient, createEventSubscriber } from "electron-effect-rpc/renderer";
175
- import { contract, WorkUnitProgress } from "./contract.ts";
176
-
177
- const client = createRpcClient(contract, { invoke: window.rpc.invoke });
178
- const events = createEventSubscriber(contract, {
179
- subscribe: window.events.subscribe,
180
- decodeMode: "safe", // default
181
- });
102
+ import { Effect } from "effect";
103
+ import { ipc, WorkUnitProgress } from "./shared-ipc.ts";
182
104
 
183
- const { version } = await client.GetAppVersion();
105
+ const { client, events } = ipc.renderer(window.api);
106
+ const { version } = await Effect.runPromise(client.GetAppVersion());
184
107
 
185
- events.subscribe(WorkUnitProgress, (payload) => {
108
+ const unsubscribe = events.subscribe(WorkUnitProgress, (payload) => {
186
109
  console.log(payload.chunk);
187
110
  });
188
- ```
189
111
 
190
- ### Window type augmentation
191
- If you expose globals in preload, add a local `globals.d.ts`:
112
+ // later
113
+ unsubscribe();
114
+ events.dispose();
115
+ ```
192
116
 
117
+ ### 5) Window typing
193
118
  ```ts
194
119
  declare global {
195
120
  interface Window {
196
- rpc: {
121
+ api: {
197
122
  invoke: (method: string, payload: unknown) => Promise<unknown>;
198
- };
199
- events: {
200
123
  subscribe: (name: string, handler: (payload: unknown) => void) => () => void;
201
124
  };
202
125
  }
203
126
  }
204
127
  ```
205
128
 
206
- ## Testing
129
+ ## Error Model
207
130
 
208
- ### Renderer client tests
209
- Use the testing helpers to stub invoke behavior:
131
+ Domain failures are modeled with tagged error schemas and are surfaced in the
132
+ Effect error channel as those same tagged values. Unexpected failures,
133
+ transport defects, and protocol mismatches are surfaced as `RpcDefectError`,
134
+ which includes a stable `code` discriminator:
135
+ `request_encoding_failed`, `invoke_failed`,
136
+ `success_payload_decoding_failed`, `failure_payload_decoding_failed`,
137
+ `noerror_contract_violation`, `invalid_response_envelope`,
138
+ `legacy_decode_failed`, and `remote_defect`.
210
139
 
211
- ```ts
212
- import { createRpcClient } from "electron-effect-rpc/renderer";
213
- import { createInvokeStub } from "electron-effect-rpc/testing";
214
- import { contract } from "./contract.ts";
215
-
216
- const invoke = createInvokeStub(async (method, payload) => {
217
- return {
218
- type: "success",
219
- data: { version: "1.0.0" },
220
- };
221
- });
140
+ ## Breaking Changes
222
141
 
223
- const client = createRpcClient(contract, { invoke });
224
- await client.GetAppVersion();
142
+ Renderer RPC methods now return `Effect.Effect` instead of `Promise`, and
143
+ `IpcMainHandle.emit` was removed in favor of `publish`.
225
144
 
226
- expect(invoke.invocations).toEqual([
227
- { method: "GetAppVersion", payload: {} },
228
- ]);
145
+ Before:
146
+
147
+ ```ts
148
+ const result = await client.GetAppVersion();
149
+ await mainRpc.emit(WorkUnitProgress, payload);
229
150
  ```
230
151
 
231
- ### Main process tests
232
- You can stub `IpcMainLike` and collect registered handlers:
152
+ After:
233
153
 
234
154
  ```ts
235
- import * as Runtime from "effect/Runtime";
236
- import { createRpcEndpoint } from "electron-effect-rpc/main";
237
- import type { IpcMainLike } from "electron-effect-rpc/types";
238
- import { contract } from "./contract.ts";
239
-
240
- const handlers = new Map<string, (event: unknown, payload: unknown) => unknown>();
241
- const ipcMainStub: IpcMainLike = {
242
- handle: (channel, handler) => {
243
- handlers.set(channel, handler);
244
- },
245
- removeHandler: (channel) => {
246
- handlers.delete(channel);
247
- },
248
- };
249
-
250
- const endpoint = createRpcEndpoint(contract, ipcMainStub, implementations, {
251
- runtime: Runtime.defaultRuntime,
252
- });
253
- endpoint.start();
155
+ const result = await Effect.runPromise(client.GetAppVersion());
156
+ await Effect.runPromise(mainRpc.publish(WorkUnitProgress, payload));
254
157
  ```
255
158
 
256
- ## Error Handling
257
- - If a handler fails with a typed domain error, the renderer client rejects
258
- with that error instance.
259
- - If a handler dies or throws a defect, the renderer client rejects with
260
- `RpcDefectError`.
159
+ ## Low-Level APIs (Still Supported)
261
160
 
262
- ## API Surface
263
-
264
- Entry points:
161
+ If you need direct control, keep using subpath entry points:
265
162
  - `electron-effect-rpc/contract`
266
- - `rpc`, `event`, `defineContract`, `exitSchemaFor`, `SchemaNoContext`, `NoError`
267
- - `electron-effect-rpc/types`
268
- - Type aliases such as `Implementations`, `RpcClient`, `RpcEventPublisher`, `IpcMainLike`
269
163
  - `electron-effect-rpc/main`
270
- - `createRpcEndpoint`, `createEventPublisher`
271
164
  - `electron-effect-rpc/renderer`
272
- - `createRpcClient`, `createEventSubscriber`, `RpcDefectError`
273
165
  - `electron-effect-rpc/preload`
274
- - `exposeRpcBridge`, `createBridgeAdapters`
166
+ - `electron-effect-rpc/types`
275
167
  - `electron-effect-rpc/testing`
276
- - `createInvokeStub`, `createDeferred`
168
+
169
+ ## Root API Surface
170
+
171
+ The root entry point exports:
172
+ - `createIpcKit`
173
+ - `rpc`, `event`, `defineContract`, `NoError`
174
+ - Types: `IpcKit`, `IpcKitOptions`, `IpcMainHandle`, `IpcBridge`, `IpcBridgeGlobal`
175
+
176
+ Low-level factories like `createRpcClient` remain subpath-only by design.
177
+
178
+ ## Tutorials
179
+
180
+ For deeper walkthroughs and production guidance:
181
+ - [Tutorial Index](./docs/tutorials/README.md)
182
+ - [First RPC: Main + Preload + Renderer](./docs/tutorials/01-first-rpc.md)
183
+ - [Typed Errors, Defects, and Diagnostics](./docs/tutorials/02-typed-errors-defects-diagnostics.md)
184
+ - [Events, Lifecycle, and Backpressure](./docs/tutorials/03-events-lifecycle-backpressure.md)
277
185
 
278
186
  ## Conventions
279
187
  - Relative imports use `.ts` extensions.
280
188
  - Package imports are extensionless.
281
- - No `index.ts` barrel files.
189
+ - No `index.ts` barrel files in subpath modules.
282
190
 
283
191
  ## License
284
192
  MIT
@@ -0,0 +1,4 @@
1
+ export { createIpcKit } from "./kit.ts";
2
+ export { defineContract, event, NoError, rpc } from "./contract.ts";
3
+ export type { IpcBridge, IpcBridgeGlobal, IpcKit, IpcKitOptions, IpcMainHandle, } from "./kit.ts";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACpE,YAAY,EACV,SAAS,EACT,eAAe,EACf,MAAM,EACN,aAAa,EACb,aAAa,GACd,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { createIpcKit } from "./kit.js";
2
+ export { defineContract, event, NoError, rpc } from "./contract.js";
package/dist/kit.d.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { Effect } from "effect";
2
+ import type * as Runtime from "effect/Runtime";
3
+ import type { AnyEvent, AnyMethod, RpcContract, RpcEventPayload } from "./contract.ts";
4
+ import { type ChannelPrefix, type EventDecodeMode, type EventPublisherDiagnostics, type EventSubscribe, type EventSubscriber, type IpcMainLike, type Implementations, type RendererWindowLike, type RpcClient, type RpcEndpoint, type RpcEndpointDiagnostics, type RpcEventPublisher, type RpcInvoke, type RpcResponseDecodeMode } from "./types.ts";
5
+ export type IpcBridge = {
6
+ readonly invoke: RpcInvoke;
7
+ readonly subscribe: EventSubscribe;
8
+ };
9
+ export type IpcBridgeGlobal<Name extends string = "api"> = {
10
+ readonly [K in Name]: IpcBridge;
11
+ };
12
+ export type IpcKitOptions<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>> = {
13
+ readonly contract: C;
14
+ readonly channelPrefix?: ChannelPrefix;
15
+ readonly bridge?: {
16
+ readonly global?: string;
17
+ };
18
+ readonly decode?: {
19
+ readonly rpc?: RpcResponseDecodeMode;
20
+ readonly events?: EventDecodeMode;
21
+ };
22
+ };
23
+ type IpcMainOptions<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>, R> = {
24
+ readonly ipcMain: IpcMainLike;
25
+ readonly handlers: Implementations<C, R>;
26
+ readonly runtime: Runtime.Runtime<R>;
27
+ readonly getWindow: () => RendererWindowLike | null;
28
+ readonly maxQueueSize?: number;
29
+ readonly diagnostics?: {
30
+ readonly rpc?: RpcEndpointDiagnostics;
31
+ readonly events?: EventPublisherDiagnostics;
32
+ };
33
+ };
34
+ export type IpcMainHandle<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>> = {
35
+ readonly endpoint: RpcEndpoint;
36
+ readonly publisher: RpcEventPublisher<C>;
37
+ readonly start: () => void;
38
+ readonly stop: () => void;
39
+ readonly dispose: () => void;
40
+ readonly isRunning: () => boolean;
41
+ readonly publish: <E extends C["events"][number]>(event: E, payload: RpcEventPayload<E>) => Effect.Effect<void, never>;
42
+ readonly stats: () => {
43
+ readonly queued: number;
44
+ readonly dropped: number;
45
+ };
46
+ };
47
+ export type IpcKit<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>> = {
48
+ readonly contract: C;
49
+ readonly config: {
50
+ readonly channelPrefix: ChannelPrefix;
51
+ readonly bridgeGlobal: string;
52
+ readonly rpcDecodeMode: RpcResponseDecodeMode;
53
+ readonly eventDecodeMode: EventDecodeMode;
54
+ };
55
+ readonly main: <R>(options: IpcMainOptions<C, R>) => IpcMainHandle<C>;
56
+ readonly preload: (options?: {
57
+ readonly global?: string;
58
+ }) => {
59
+ readonly global: string;
60
+ readonly bridge: IpcBridge;
61
+ readonly expose: () => void;
62
+ };
63
+ readonly renderer: (bridge: IpcBridge) => {
64
+ readonly client: RpcClient<C>;
65
+ readonly events: EventSubscriber<C>;
66
+ };
67
+ };
68
+ export declare function createIpcKit<const Methods extends ReadonlyArray<AnyMethod>, const Events extends ReadonlyArray<AnyEvent>>(options: IpcKitOptions<RpcContract<Methods, Events>>): IpcKit<RpcContract<Methods, Events>>;
69
+ export {};
70
+ //# sourceMappingURL=kit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kit.d.ts","sourceRoot":"","sources":["../src/kit.ts"],"names":[],"mappings":"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"}
package/dist/kit.js ADDED
@@ -0,0 +1,134 @@
1
+ import { Effect } from "effect";
2
+ import { createEventPublisher, createRpcEndpoint } from "./main.js";
3
+ import { exposeIpcBridge, createBridgeAdapters } from "./preload.js";
4
+ import { createEventSubscriber, createRpcClient } from "./renderer.js";
5
+ import { defaultChannelPrefix, } from "./types.js";
6
+ export function createIpcKit(options) {
7
+ const contract = options.contract;
8
+ const channelPrefix = options.channelPrefix
9
+ ? { ...options.channelPrefix }
10
+ : { ...defaultChannelPrefix };
11
+ const bridgeGlobal = options.bridge?.global ?? "api";
12
+ const rpcDecodeMode = options.decode?.rpc ?? "envelope";
13
+ const eventDecodeMode = options.decode?.events ?? "safe";
14
+ const main = (mainOptions) => {
15
+ const endpoint = createRpcEndpoint(contract, mainOptions.ipcMain, mainOptions.handlers, {
16
+ runtime: mainOptions.runtime,
17
+ channelPrefix,
18
+ diagnostics: mainOptions.diagnostics?.rpc,
19
+ });
20
+ const publisher = createEventPublisher(contract, {
21
+ getWindow: mainOptions.getWindow,
22
+ maxQueueSize: mainOptions.maxQueueSize,
23
+ channelPrefix,
24
+ diagnostics: mainOptions.diagnostics?.events,
25
+ });
26
+ function start() {
27
+ endpoint.start();
28
+ try {
29
+ publisher.start();
30
+ }
31
+ catch (cause) {
32
+ try {
33
+ endpoint.stop();
34
+ }
35
+ catch {
36
+ // Best effort rollback.
37
+ }
38
+ throw cause;
39
+ }
40
+ }
41
+ function stop() {
42
+ let firstError;
43
+ try {
44
+ publisher.stop();
45
+ }
46
+ catch (cause) {
47
+ firstError ??= cause;
48
+ }
49
+ try {
50
+ endpoint.stop();
51
+ }
52
+ catch (cause) {
53
+ firstError ??= cause;
54
+ }
55
+ if (firstError !== undefined) {
56
+ throw firstError;
57
+ }
58
+ }
59
+ function dispose() {
60
+ let firstError;
61
+ try {
62
+ publisher.dispose();
63
+ }
64
+ catch (cause) {
65
+ firstError ??= cause;
66
+ }
67
+ try {
68
+ endpoint.dispose();
69
+ }
70
+ catch (cause) {
71
+ firstError ??= cause;
72
+ }
73
+ if (firstError !== undefined) {
74
+ throw firstError;
75
+ }
76
+ }
77
+ function isRunning() {
78
+ return endpoint.isRunning() && publisher.isRunning();
79
+ }
80
+ function publish(event, payload) {
81
+ return publisher.publish(event, payload);
82
+ }
83
+ return {
84
+ endpoint,
85
+ publisher,
86
+ start,
87
+ stop,
88
+ dispose,
89
+ isRunning,
90
+ publish,
91
+ stats: publisher.stats,
92
+ };
93
+ };
94
+ const preload = (preloadOptions) => {
95
+ const global = preloadOptions?.global ?? bridgeGlobal;
96
+ const bridge = createBridgeAdapters({
97
+ channelPrefix,
98
+ });
99
+ return {
100
+ global,
101
+ bridge,
102
+ expose: () => {
103
+ exposeIpcBridge({
104
+ global,
105
+ channelPrefix,
106
+ });
107
+ },
108
+ };
109
+ };
110
+ const renderer = (bridge) => {
111
+ return {
112
+ client: createRpcClient(contract, {
113
+ invoke: bridge.invoke,
114
+ rpcDecodeMode,
115
+ }),
116
+ events: createEventSubscriber(contract, {
117
+ subscribe: bridge.subscribe,
118
+ decodeMode: eventDecodeMode,
119
+ }),
120
+ };
121
+ };
122
+ return {
123
+ contract,
124
+ config: {
125
+ channelPrefix,
126
+ bridgeGlobal,
127
+ rpcDecodeMode,
128
+ eventDecodeMode,
129
+ },
130
+ main,
131
+ preload,
132
+ renderer,
133
+ };
134
+ }
@@ -1 +1 @@
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"}
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,CAgNb;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,CAkMjD"}
package/dist/main.js CHANGED
@@ -109,8 +109,23 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
109
109
  if (running) {
110
110
  return;
111
111
  }
112
- for (const [channel, listener] of listeners) {
113
- ipc.handle(channel, listener);
112
+ const registeredChannels = [];
113
+ try {
114
+ for (const [channel, listener] of listeners) {
115
+ ipc.handle(channel, listener);
116
+ registeredChannels.push(channel);
117
+ }
118
+ }
119
+ catch (cause) {
120
+ for (const channel of registeredChannels) {
121
+ try {
122
+ ipc.removeHandler(channel);
123
+ }
124
+ catch {
125
+ // Best-effort rollback: avoid leaving partial registration behind.
126
+ }
127
+ }
128
+ throw cause;
114
129
  }
115
130
  running = true;
116
131
  }
@@ -118,17 +133,35 @@ export function createRpcEndpoint(contract, ipc, implementations, options) {
118
133
  if (!running) {
119
134
  return;
120
135
  }
136
+ let firstError;
121
137
  for (const channel of listeners.keys()) {
122
- ipc.removeHandler(channel);
138
+ try {
139
+ ipc.removeHandler(channel);
140
+ }
141
+ catch (cause) {
142
+ firstError ??= cause;
143
+ }
123
144
  }
124
145
  running = false;
146
+ if (firstError !== undefined) {
147
+ throw firstError;
148
+ }
125
149
  }
126
150
  function dispose() {
127
151
  if (disposed) {
128
152
  return;
129
153
  }
130
- stop();
154
+ let stopError;
155
+ try {
156
+ stop();
157
+ }
158
+ catch (cause) {
159
+ stopError = cause;
160
+ }
131
161
  disposed = true;
162
+ if (stopError !== undefined) {
163
+ throw stopError;
164
+ }
132
165
  }
133
166
  function isRunning() {
134
167
  return running;
@@ -193,17 +226,33 @@ export function createEventPublisher(_contract, options) {
193
226
  }
194
227
  const window = options.getWindow();
195
228
  if (!window || window.isDestroyed()) {
229
+ dropped += 1;
230
+ safelyCall(diagnostics?.onDroppedEvent, {
231
+ event: item.event.name,
232
+ payload: item.payload,
233
+ reason: "window_unavailable",
234
+ queued: queue.length,
235
+ dropped,
236
+ });
196
237
  return;
197
238
  }
198
239
  try {
199
240
  window.webContents.send(`${channelPrefix.event}${item.event.name}`, encoded);
200
241
  }
201
242
  catch (cause) {
243
+ dropped += 1;
202
244
  safelyCall(diagnostics?.onDispatchFailure, {
203
245
  event: item.event.name,
204
246
  payload: item.payload,
205
247
  cause,
206
248
  });
249
+ safelyCall(diagnostics?.onDroppedEvent, {
250
+ event: item.event.name,
251
+ payload: item.payload,
252
+ reason: "dispatch_failed",
253
+ queued: queue.length,
254
+ dropped,
255
+ });
207
256
  }
208
257
  }
209
258
  function drain() {
package/dist/preload.d.ts CHANGED
@@ -10,6 +10,10 @@ export type BridgeExposureOptions = BridgeAdaptersOptions & {
10
10
  readonly rpcGlobal?: string;
11
11
  readonly eventsGlobal?: string;
12
12
  };
13
+ export type IpcBridgeExposureOptions = BridgeAdaptersOptions & {
14
+ readonly global?: string;
15
+ };
13
16
  export declare function createBridgeAdapters(options?: BridgeAdaptersOptions): BridgeAdapters;
14
17
  export declare function exposeRpcBridge(options?: BridgeExposureOptions): void;
18
+ export declare function exposeIpcBridge(options?: IpcBridgeExposureOptions): void;
15
19
  //# sourceMappingURL=preload.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../src/preload.ts"],"names":[],"mappings":"AAEA,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,MAAM,MAAM,wBAAwB,GAAG,qBAAqB,GAAG;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAwCF,wBAAgB,oBAAoB,CAClC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,cAAc,CAuBhB;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAgBrE;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAYxE"}
package/dist/preload.js CHANGED
@@ -1,7 +1,20 @@
1
- import { contextBridge, ipcRenderer } from "electron";
1
+ import * as electronModule from "electron";
2
2
  import { defaultChannelPrefix, } from "./types.js";
3
+ function resolveElectronRendererBindings() {
4
+ const moduleDefault = electronModule.default;
5
+ const source = moduleDefault && typeof moduleDefault === "object"
6
+ ? moduleDefault
7
+ : electronModule;
8
+ const contextBridge = source.contextBridge;
9
+ const ipcRenderer = source.ipcRenderer;
10
+ if (!contextBridge || !ipcRenderer) {
11
+ throw new Error("electron-effect-rpc/preload requires Electron preload runtime bindings.");
12
+ }
13
+ return { contextBridge, ipcRenderer };
14
+ }
3
15
  export function createBridgeAdapters(options) {
4
16
  const channelPrefix = options?.channelPrefix ?? defaultChannelPrefix;
17
+ const { ipcRenderer } = resolveElectronRendererBindings();
5
18
  const invoke = (method, payload) => ipcRenderer.invoke(`${channelPrefix.rpc}${method}`, payload);
6
19
  const subscribe = (event, listener) => {
7
20
  const wrapped = (_event, payload) => listener(payload);
@@ -19,6 +32,7 @@ export function createBridgeAdapters(options) {
19
32
  export function exposeRpcBridge(options) {
20
33
  const rpcGlobal = options?.rpcGlobal ?? "rpc";
21
34
  const eventsGlobal = options?.eventsGlobal ?? "events";
35
+ const { contextBridge } = resolveElectronRendererBindings();
22
36
  const adapters = createBridgeAdapters({
23
37
  channelPrefix: options?.channelPrefix,
24
38
  });
@@ -29,3 +43,14 @@ export function exposeRpcBridge(options) {
29
43
  subscribe: adapters.subscribe,
30
44
  });
31
45
  }
46
+ export function exposeIpcBridge(options) {
47
+ const global = options?.global ?? "api";
48
+ const { contextBridge } = resolveElectronRendererBindings();
49
+ const adapters = createBridgeAdapters({
50
+ channelPrefix: options?.channelPrefix,
51
+ });
52
+ contextBridge.exposeInMainWorld(global, {
53
+ invoke: adapters.invoke,
54
+ subscribe: adapters.subscribe,
55
+ });
56
+ }
@@ -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 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>>;
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,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"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,WAAW,EAIjB,MAAM,eAAe,CAAC;AAOvB,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,SAAS,EAGd,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAE3B,KAAK,SAAS,EACd,KAAK,gBAAgB,EAGtB,MAAM,YAAY,CAAC;AAuEpB,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,EAAE,gBAAgB,GACxB,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CA0KzC;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,EAAE,sBAAsB,GAC9B,eAAe,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAyF/C;AAED,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
package/dist/renderer.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as S from "@effect/schema/Schema";
2
- import { Cause, Exit } from "effect";
2
+ import { Cause, Effect, Exit } from "effect";
3
3
  import { exitSchemaFor, isNoErrorSchema, } from "./contract.js";
4
- import { formatUnknown, parseRpcResponseEnvelope, safelyCall } from "./protocol.js";
4
+ import { formatUnknown, parseRpcResponseEnvelope, safelyCall, } from "./protocol.js";
5
5
  import { RpcDefectError, } from "./types.js";
6
6
  function requireInvoke(options) {
7
7
  if (!options?.invoke) {
@@ -15,110 +15,124 @@ function requireSubscribe(options) {
15
15
  }
16
16
  return options.subscribe;
17
17
  }
18
+ function rpcDefect(code, message, cause) {
19
+ return new RpcDefectError(code, message, cause);
20
+ }
18
21
  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);
22
+ return Effect.try({
23
+ try: () => S.decodeUnknownSync(exitSchemaFor(method))(raw),
24
+ catch: (cause) => rpcDefect("legacy_decode_failed", `RPC ${method.name} legacy response decoding failed: ${formatUnknown(cause)}`, cause),
25
+ }).pipe(Effect.flatMap((exit) => {
26
+ if (Exit.isSuccess(exit)) {
27
+ return Effect.succeed(exit.value);
34
28
  }
35
- throw new RpcDefectError(String(defect), defect);
36
- }
37
- throw new RpcDefectError("RPC call was interrupted or failed unexpectedly", exit.cause);
29
+ const failureOption = Cause.failureOption(exit.cause);
30
+ if (failureOption._tag === "Some") {
31
+ return Effect.fail(failureOption.value);
32
+ }
33
+ const defectOption = Cause.dieOption(exit.cause);
34
+ if (defectOption._tag === "Some") {
35
+ const defect = defectOption.value;
36
+ const message = defect instanceof Error ? defect.message : String(defect);
37
+ return Effect.fail(rpcDefect("remote_defect", message, defect));
38
+ }
39
+ return Effect.fail(rpcDefect("remote_defect", "RPC call was interrupted or failed unexpectedly", exit.cause));
40
+ }));
38
41
  }
39
42
  export function createRpcClient(contract, options) {
40
43
  const invoke = requireInvoke(options);
41
44
  const diagnostics = options?.diagnostics;
42
45
  const decodeMode = options?.rpcDecodeMode ?? "envelope";
43
- const call = async (method, input) => {
44
- let encoded;
45
- try {
46
- encoded = S.encodeSync(method.req)(input);
47
- }
48
- catch (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)}`);
56
- }
57
- const raw = await invoke(method.name, encoded);
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) {
46
+ const decodeEnvelope = (method, envelope) => {
47
+ switch (envelope.type) {
48
+ case "success":
49
+ return Effect.try({
50
+ try: () => S.decodeUnknownSync(method.res)(envelope.data),
51
+ catch: (cause) => {
66
52
  safelyCall(diagnostics?.onDecodeFailure, {
67
53
  scope: "rpc-response",
68
54
  name: method.name,
69
55
  payload: envelope.data,
70
56
  cause,
71
57
  });
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) {
58
+ return rpcDefect("success_payload_decoding_failed", `RPC ${method.name} success payload decoding failed: ${formatUnknown(cause)}`, cause);
59
+ },
60
+ });
61
+ case "failure":
62
+ if (isNoErrorSchema(method.err)) {
63
+ return Effect.fail(rpcDefect("noerror_contract_violation", `RPC ${method.name} received a failure for a method that declares NoError`, envelope.error));
64
+ }
65
+ const errorSchema = method.err;
66
+ return Effect.try({
67
+ try: () => S.decodeUnknownSync(errorSchema)(envelope.error.data),
68
+ catch: (cause) => {
83
69
  safelyCall(diagnostics?.onDecodeFailure, {
84
70
  scope: "rpc-response",
85
71
  name: method.name,
86
72
  payload: envelope.error,
87
73
  cause,
88
74
  });
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
- }
75
+ return rpcDefect("failure_payload_decoding_failed", `RPC ${method.name} failure payload decoding failed: ${formatUnknown(cause)}`, cause);
76
+ },
77
+ }).pipe(Effect.flatMap((decodedError) => Effect.fail(decodedError)));
78
+ case "defect":
79
+ return Effect.fail(rpcDefect("remote_defect", envelope.message, envelope.cause));
80
+ }
81
+ };
82
+ const call = (method, input) => Effect.try({
83
+ try: () => S.encodeSync(method.req)(input),
84
+ catch: (cause) => {
85
+ safelyCall(diagnostics?.onDecodeFailure, {
86
+ scope: "rpc-request",
87
+ name: method.name,
88
+ payload: input,
89
+ cause,
90
+ });
91
+ return rpcDefect("request_encoding_failed", `RPC ${method.name} request encoding failed: ${formatUnknown(cause)}`, cause);
92
+ },
93
+ }).pipe(Effect.flatMap((encoded) => Effect.tryPromise({
94
+ try: () => invoke(method.name, encoded),
95
+ catch: (cause) => {
96
+ safelyCall(diagnostics?.onProtocolError, {
97
+ method: method.name,
98
+ response: undefined,
99
+ cause,
100
+ });
101
+ return rpcDefect("invoke_failed", `RPC ${method.name} invoke failed: ${formatUnknown(cause)}`, cause);
102
+ },
103
+ })), Effect.flatMap((raw) => {
104
+ const envelope = parseRpcResponseEnvelope(raw);
105
+ if (envelope) {
106
+ return decodeEnvelope(method, envelope);
95
107
  }
96
108
  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;
107
- }
109
+ return decodeLegacyExit(method, raw).pipe(Effect.tapError((cause) => {
110
+ if (cause instanceof RpcDefectError &&
111
+ cause.code === "legacy_decode_failed") {
112
+ return Effect.sync(() => safelyCall(diagnostics?.onProtocolError, {
113
+ method: method.name,
114
+ response: raw,
115
+ cause,
116
+ }));
117
+ }
118
+ return Effect.void;
119
+ }));
108
120
  }
109
- const cause = new Error(`RPC ${method.name} response was not a valid envelope.`);
121
+ const cause = rpcDefect("invalid_response_envelope", `RPC ${method.name} response was not a valid envelope.`, raw);
110
122
  safelyCall(diagnostics?.onProtocolError, {
111
123
  method: method.name,
112
124
  response: raw,
113
125
  cause,
114
126
  });
115
- throw cause;
116
- };
127
+ return Effect.fail(cause);
128
+ }));
117
129
  const client = Object.create(null);
118
130
  const clientRecord = client;
119
131
  for (const method of contract.methods) {
120
- const caller = (input) => {
121
- const payload = input ?? S.decodeUnknownSync(method.req)({});
132
+ const caller = (...args) => {
133
+ const payload = args.length === 0
134
+ ? {}
135
+ : args[0];
122
136
  return call(method, payload);
123
137
  };
124
138
  clientRecord[method.name] = caller;
@@ -187,10 +201,19 @@ export function createEventSubscriber(contract, options) {
187
201
  return registerUnsubscribe(unsubscribe);
188
202
  };
189
203
  function dispose() {
204
+ let firstError;
190
205
  for (const unsubscribe of subscriptions) {
191
- unsubscribe();
206
+ try {
207
+ unsubscribe();
208
+ }
209
+ catch (cause) {
210
+ firstError ??= cause;
211
+ }
192
212
  }
193
213
  subscriptions.clear();
214
+ if (firstError !== undefined) {
215
+ throw firstError;
216
+ }
194
217
  }
195
218
  return {
196
219
  subscribe: subscribeEvent,
package/dist/types.d.ts CHANGED
@@ -5,16 +5,19 @@ export type { AnyEvent, AnyMethod, ErrorSchema, ExtractMethod, RpcContract, RpcE
5
5
  export type Implementations<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>, R = never> = {
6
6
  readonly [Name in C["methods"][number]["name"]]: (input: RpcInput<ExtractMethod<C["methods"], Name>>) => Effect.Effect<RpcOutput<ExtractMethod<C["methods"], Name>>, RpcError<ExtractMethod<C["methods"], Name>>, R>;
7
7
  };
8
- type IsEmptyObject<T> = keyof T extends never ? true : false;
9
- export type RpcCaller<M extends AnyMethod> = IsEmptyObject<RpcInput<M>> extends true ? () => Promise<RpcOutput<M>> : (input: RpcInput<M>) => Promise<RpcOutput<M>>;
10
- export type RpcClient<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>> = {
11
- readonly [Name in C["methods"][number]["name"]]: RpcCaller<ExtractMethod<C["methods"], Name>>;
12
- };
8
+ type IsEmptyObject<T> = T extends object ? keyof T extends never ? true : false : false;
9
+ export type RpcDefectCode = "request_encoding_failed" | "invoke_failed" | "success_payload_decoding_failed" | "failure_payload_decoding_failed" | "noerror_contract_violation" | "invalid_response_envelope" | "legacy_decode_failed" | "remote_defect";
13
10
  export declare class RpcDefectError extends Error {
11
+ readonly code: RpcDefectCode;
14
12
  readonly cause: unknown;
15
13
  readonly _tag = "RpcDefectError";
16
- constructor(message: string, cause: unknown);
14
+ constructor(code: RpcDefectCode, message: string, cause: unknown);
17
15
  }
16
+ export type RpcMethodError<M extends AnyMethod> = RpcError<M> | RpcDefectError;
17
+ export type RpcCaller<M extends AnyMethod> = IsEmptyObject<RpcInput<M>> extends true ? () => Effect.Effect<RpcOutput<M>, RpcMethodError<M>> : (input: RpcInput<M>) => Effect.Effect<RpcOutput<M>, RpcMethodError<M>>;
18
+ export type RpcClient<C extends RpcContract<readonly AnyMethod[], readonly AnyEvent[]>> = {
19
+ readonly [Name in C["methods"][number]["name"]]: RpcCaller<ExtractMethod<C["methods"], Name>>;
20
+ };
18
21
  export type ChannelPrefix = {
19
22
  readonly rpc: string;
20
23
  readonly event: string;
@@ -37,7 +40,7 @@ export type DispatchFailureContext = {
37
40
  readonly payload: unknown;
38
41
  readonly cause: unknown;
39
42
  };
40
- export type DroppedEventReason = "queue_full" | "encoding_failed";
43
+ export type DroppedEventReason = "queue_full" | "encoding_failed" | "window_unavailable" | "dispatch_failed";
41
44
  export type DroppedEventContext = {
42
45
  readonly event: string;
43
46
  readonly payload: unknown;
@@ -52,7 +55,7 @@ export type RpcClientDiagnostics = {
52
55
  readonly onProtocolError?: (context: ProtocolErrorContext) => void;
53
56
  };
54
57
  export type RpcClientOptions = {
55
- readonly invoke?: RpcInvoke;
58
+ readonly invoke: RpcInvoke;
56
59
  readonly diagnostics?: RpcClientDiagnostics;
57
60
  readonly rpcDecodeMode?: RpcResponseDecodeMode;
58
61
  };
@@ -112,7 +115,7 @@ export type EventSubscriberDiagnostics = {
112
115
  readonly onDecodeFailure?: (context: DecodeFailureContext) => void;
113
116
  };
114
117
  export type EventSubscriberOptions = {
115
- readonly subscribe?: EventSubscribe;
118
+ readonly subscribe: EventSubscribe;
116
119
  readonly decodeMode?: EventDecodeMode;
117
120
  readonly diagnostics?: EventSubscriberDiagnostics;
118
121
  };
@@ -121,4 +124,5 @@ export interface EventSubscriber<C extends RpcContract<readonly AnyMethod[], rea
121
124
  readonly subscribeByName: (name: string, handler: (payload: unknown) => void) => () => void;
122
125
  readonly dispose: () => void;
123
126
  }
127
+ export type { IpcBridge, IpcBridgeGlobal, IpcKit, IpcKitOptions, IpcMainHandle, } from "./kit.ts";
124
128
  //# 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":"AAAA,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EAET,aAAa,EACb,WAAW,EACX,QAAQ,EAER,eAAe,EACf,QAAQ,EAER,SAAS,EAEV,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,QAAQ,EACR,SAAS,EACT,WAAW,EACX,aAAa,EACb,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,SAAS,EACT,SAAS,EACT,eAAe,GAChB,MAAM,eAAe,CAAC;AAEvB,MAAM,MAAM,eAAe,CACzB,CAAC,SAAS,WAAW,CAAC,SAAS,SAAS,EAAE,EAAE,SAAS,QAAQ,EAAE,CAAC,EAChE,CAAC,GAAG,KAAK,IACP;IACF,QAAQ,EAAE,IAAI,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,CAC/C,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC,KAC/C,MAAM,CAAC,MAAM,CAChB,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC,EAC5C,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC,EAC3C,CAAC,CACF;CACF,CAAC;AAEF,KAAK,aAAa,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GACpC,MAAM,CAAC,SAAS,KAAK,GACnB,IAAI,GACJ,KAAK,GACP,KAAK,CAAC;AAEV,MAAM,MAAM,aAAa,GACrB,yBAAyB,GACzB,eAAe,GACf,iCAAiC,GACjC,iCAAiC,GACjC,4BAA4B,GAC5B,2BAA2B,GAC3B,sBAAsB,GACtB,eAAe,CAAC;AAEpB,qBAAa,cAAe,SAAQ,KAAK;aAIrB,IAAI,EAAE,aAAa;aAEnB,KAAK,EAAE,OAAO;IALhC,QAAQ,CAAC,IAAI,oBAAoB;gBAGf,IAAI,EAAE,aAAa,EACnC,OAAO,EAAE,MAAM,EACC,KAAK,EAAE,OAAO;CAKjC;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC;AAE/E,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,SAAS,IACvC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GACnC,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,GACpD,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AAE7E,MAAM,MAAM,SAAS,CACnB,CAAC,SAAS,WAAW,CAAC,SAAS,SAAS,EAAE,EAAE,SAAS,QAAQ,EAAE,CAAC,IAC9D;IACF,QAAQ,EAAE,IAAI,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,SAAS,CACxD,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAClC;CACF,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,aAGlC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,cAAc,GAAG,eAAe,CAAC;AAElF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,YAAY,GACZ,iBAAiB,GACjB,oBAAoB,GACpB,iBAAiB,CAAC;AAEtB,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,MAAM,CAAC;AAExD,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACnE,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACpE,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC;IAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,qBAAqB,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACnE,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACpE,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,MAAM,EAAE,CACf,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,KACpD,OAAO,CAAC;IACb,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;CACtD,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,KAAK,IAAI;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE,sBAAsB,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACnE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACvE,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;KAC5D,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,kBAAkB,GAAG,IAAI,CAAC;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,yBAAyB,CAAC;CAClD,CAAC;AAEF,MAAM,WAAW,iBAAiB,CAChC,CAAC,SAAS,WAAW,CAAC,SAAS,SAAS,EAAE,EAAE,SAAS,QAAQ,EAAE,CAAC;IAEhE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAC9C,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,KACxB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEhD,MAAM,MAAM,cAAc,GAAG,CAC3B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAChC,MAAM,IAAI,CAAC;AAEhB,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACpE,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,CAAC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,0BAA0B,CAAC;CACnD,CAAC;AAEF,MAAM,WAAW,eAAe,CAC9B,CAAC,SAAS,WAAW,CAAC,SAAS,SAAS,EAAE,EAAE,SAAS,QAAQ,EAAE,CAAC;IAEhE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAChD,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,KAC3C,MAAM,IAAI,CAAC;IAChB,QAAQ,CAAC,eAAe,EAAE,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,KAChC,MAAM,IAAI,CAAC;IAChB,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,YAAY,EACV,SAAS,EACT,eAAe,EACf,MAAM,EACN,aAAa,EACb,aAAa,GACd,MAAM,UAAU,CAAC"}
package/dist/types.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export class RpcDefectError extends Error {
2
- constructor(message, cause) {
2
+ constructor(code, message, cause) {
3
3
  super(message);
4
+ this.code = code;
4
5
  this.cause = cause;
5
6
  this._tag = "RpcDefectError";
6
7
  this.name = "RpcDefectError";
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "electron-effect-rpc",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Typed IPC RPC for Electron, built on Effect and @effect/schema",
5
5
  "type": "module",
6
6
  "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ },
7
11
  "./main": {
8
12
  "types": "./dist/main.d.ts",
9
13
  "default": "./dist/main.js"
@@ -35,6 +39,12 @@
35
39
  "scripts": {
36
40
  "build": "tsc",
37
41
  "prepublishOnly": "bun run build",
42
+ "test:unit": "bun test",
43
+ "test:types": "bun run typecheck && bunx tsc -p tsconfig.type-tests.json --noEmit",
44
+ "test:packaging": "bash ./scripts/run-packaging-checks.sh",
45
+ "test:integration": "bun test __tests__/integration.test.ts",
46
+ "test:stress": "bun test __tests__/stress.test.ts",
47
+ "test:full": "bun run test:unit && bun run test:types && bun run test:packaging && bun run test:integration && bun run test:stress",
38
48
  "test": "bun test",
39
49
  "typecheck": "tsc --noEmit"
40
50
  },