electron-json-rpc 1.1.2 → 1.1.7

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](./README.md) | [简体中文](./README.zh-CN.md)
4
4
 
5
- A type-safe IPC library for Electron built on the JSON-RPC 2.0 protocol. Define your API once, get full type inference across main process, preload script, and renderer process. Supports streaming, event bus, queued requests with retry, and validation.
5
+ A type-safe IPC library for Electron built on the JSON-RPC 2.0 protocol. Define your API once, get full type inference across main process, preload script, and renderer process. Supports streaming, event bus, validation, and configurable timeout.
6
6
 
7
7
  > **Status**: This project is currently in **beta**. The API is subject to change. Feedback and contributions are welcome!
8
8
 
@@ -23,25 +23,24 @@ npm install electron-json-rpc
23
23
  - **Streaming** - Web standard `ReadableStream` support for server-sent streams
24
24
  - **Notifications** - One-way calls without response
25
25
  - **Timeout handling** - Configurable timeout for RPC calls
26
- - **Batch requests** - Support for multiple requests in a single call
27
26
 
28
27
  ## Serializable Types
29
28
 
30
29
  This library uses Electron's IPC which internally uses the **Structured Clone Algorithm** for serialization. This means you can pass many more types than just JSON:
31
30
 
32
- | Type | Supported | Notes |
33
- | ------------------- | --------- | ------------------------------------- |
34
- | Primitives | ✅ | string, number, boolean, bigint |
35
- | null / undefined | ✅ | undefined is preserved (not null) |
36
- | Plain Objects | ✅ | With serializable properties |
37
- | Arrays | ✅ | Including nested and sparse arrays |
38
- | Date | ✅ | Preserves date object |
39
- | RegExp | ✅ | Preserves pattern and flags |
40
- | Map / Set | ✅ | With serializable contents |
41
- | ArrayBuffer | ✅ | Binary data |
42
- | Typed Arrays | ✅ | Int8Array, Uint8Array, etc. |
43
- | Error objects | ✅ | Including stack trace |
44
- | Circular references | ✅ | Handled correctly |
31
+ | Type | Supported | Notes |
32
+ | ------------------- | --------- | ---------------------------------- |
33
+ | Primitives | ✅ | string, number, boolean, bigint |
34
+ | null / undefined | ✅ | undefined is preserved (not null) |
35
+ | Plain Objects | ✅ | With serializable properties |
36
+ | Arrays | ✅ | Including nested and sparse arrays |
37
+ | Date | ✅ | Preserves date object |
38
+ | RegExp | ✅ | Preserves pattern and flags |
39
+ | Map / Set | ✅ | With serializable contents |
40
+ | ArrayBuffer | ✅ | Binary data |
41
+ | Typed Arrays | ✅ | Int8Array, Uint8Array, etc. |
42
+ | Error objects | ✅ | Including stack trace |
43
+ | Circular references | ✅ | Handled correctly |
45
44
 
46
45
  **Not supported:**
47
46
 
@@ -63,9 +62,9 @@ rpc.register("getData", () => ({
63
62
 
64
63
  // Renderer process - all types preserved!
65
64
  const data = await rpc.getData();
66
- console.log(data.date instanceof Date); // true
67
- console.log(data.regex instanceof RegExp); // true
68
- console.log(data.map instanceof Map); // true
65
+ console.log(data.date instanceof Date); // true
66
+ console.log(data.regex instanceof RegExp); // true
67
+ console.log(data.map instanceof Map); // true
69
68
  console.log(data.buffer instanceof ArrayBuffer); // true
70
69
  ```
71
70
 
@@ -74,7 +73,7 @@ console.log(data.buffer instanceof ArrayBuffer); // true
74
73
  You can use the `IpcSerializable` type to ensure values are serializable:
75
74
 
76
75
  ```typescript
77
- import type { IpcSerializable } from "electron-json-rpc/types";
76
+ import type { IpcSerializable } from "electron-json-rpc";
78
77
 
79
78
  function sendToRenderer(data: IpcSerializable) {
80
79
  rpc.publish("event", data); // Type-safe!
@@ -87,10 +86,14 @@ function sendToRenderer(data: IpcSerializable) {
87
86
 
88
87
  ```typescript
89
88
  import { app, BrowserWindow } from "electron";
90
- import { RpcServer } from "electron-json-rpc/main";
89
+ import { RpcServer, createRpcServer } from "electron-json-rpc/main";
91
90
 
91
+ // Option 1: Using class constructor
92
92
  const rpc = new RpcServer();
93
93
 
94
+ // Option 2: Using factory function (equivalent)
95
+ const rpc = createRpcServer();
96
+
94
97
  // Register methods
95
98
  rpc.register("add", (a: number, b: number) => a + b);
96
99
  rpc.register("greet", async (name: string) => {
@@ -159,6 +162,27 @@ const greeting = await window.rpc.greet("World");
159
162
  const result = await window.rpc.call("otherMethod", arg1, arg2);
160
163
  ```
161
164
 
165
+ #### Using Proxy Mode
166
+
167
+ If you need a typed proxy for type inference:
168
+
169
+ ```typescript
170
+ import { exposeRpcApi } from "electron-json-rpc/preload";
171
+ import { contextBridge, ipcRenderer } from "electron";
172
+
173
+ exposeRpcApi({
174
+ contextBridge,
175
+ ipcRenderer,
176
+ methods: ["add", "greet"],
177
+ });
178
+
179
+ // In renderer, you can also use proxy for type inference
180
+ const api = window.rpc?.proxy<{
181
+ add: (a: number, b: number) => number;
182
+ greet: (name: string) => string;
183
+ }>();
184
+ ```
185
+
162
186
  ### Renderer Process
163
187
 
164
188
  ```typescript
@@ -301,152 +325,6 @@ for await (const n of api.dataStream(10)) {
301
325
  }
302
326
  ```
303
327
 
304
- ## Request Queue
305
-
306
- For applications that need to handle unreliable connections or busy main processes, the queued RPC client provides automatic request queuing with retry logic.
307
-
308
- ### Basic Usage
309
-
310
- ```typescript
311
- import { createQueuedRpcClient } from "electron-json-rpc/renderer";
312
-
313
- const rpc = createQueuedRpcClient({
314
- maxSize: 50, // Maximum queue size
315
- fullBehavior: "evictOldest", // What to do when queue is full
316
- timeout: 10000, // Request timeout in ms
317
- });
318
-
319
- // Call a method - will be queued if main process is busy
320
- const result = await rpc.call("getData", id);
321
-
322
- // Send a notification (not queued - fire and forget)
323
- rpc.notify("log", "Hello from renderer!");
324
-
325
- // Get queue status
326
- console.log(rpc.getQueueStatus());
327
- // { pending: 2, active: 1, maxSize: 50, isPaused: false, isConnected: true }
328
- ```
329
-
330
- ### Queue Configuration
331
-
332
- ```typescript
333
- const rpc = createQueuedRpcClient({
334
- // Queue size settings
335
- maxSize: 100,
336
- fullBehavior: "reject", // 'reject' | 'evict' | 'evictOldest'
337
-
338
- // Retry settings
339
- retry: {
340
- maxAttempts: 3, // Maximum retry attempts
341
- backoff: "exponential", // 'fixed' | 'exponential'
342
- initialDelay: 1000, // Initial delay in ms
343
- maxDelay: 10000, // Maximum delay in ms
344
- },
345
-
346
- // Connection health settings
347
- healthCheck: true, // Enable connection health check
348
- healthCheckInterval: 5000, // Health check interval in ms
349
- });
350
- ```
351
-
352
- ### Queue Full Behavior
353
-
354
- When the queue reaches maximum size:
355
-
356
- - **`"reject"`** (default): Throw `RpcQueueFullError` for new requests
357
- - **`"evict"`**: Evict the current request being added
358
- - **`"evictOldest"`**: Remove the oldest request from the queue
359
-
360
- ```typescript
361
- // Reject mode (default)
362
- const rpc = createQueuedRpcClient({
363
- maxSize: 10,
364
- fullBehavior: "reject",
365
- });
366
-
367
- try {
368
- await rpc.call("someMethod");
369
- } catch (error) {
370
- if (error.name === "RpcQueueFullError") {
371
- console.log("Queue is full!");
372
- }
373
- }
374
- ```
375
-
376
- ### Queue Control Methods
377
-
378
- ```typescript
379
- const rpc = createQueuedRpcClient();
380
-
381
- // Check if queue is healthy (connected and not paused)
382
- if (rpc.isQueueHealthy()) {
383
- await rpc.call("someMethod");
384
- }
385
-
386
- // Get detailed queue status
387
- const status = rpc.getQueueStatus();
388
- console.log(`Pending: ${status.pending}, Active: ${status.active}`);
389
-
390
- // Pause queue processing (incoming requests will queue)
391
- rpc.pauseQueue();
392
-
393
- // Resume queue processing
394
- rpc.resumeQueue();
395
-
396
- // Clear all pending requests
397
- rpc.clearQueue();
398
- ```
399
-
400
- ### Retry Strategy
401
-
402
- The queue automatically retries failed requests based on the configured strategy:
403
-
404
- ```typescript
405
- const rpc = createQueuedRpcClient({
406
- retry: {
407
- maxAttempts: 3,
408
- backoff: "exponential", // or "fixed"
409
- initialDelay: 1000,
410
- maxDelay: 10000,
411
- },
412
- });
413
-
414
- // Exponential backoff: 1000ms, 2000ms, 4000ms, ...
415
- // Fixed backoff: 1000ms, 1000ms, 1000ms, ...
416
- ```
417
-
418
- Requests are retried on:
419
-
420
- - Timeout errors (`RpcTimeoutError`)
421
- - Connection errors (`RpcConnectionError`)
422
-
423
- ### Error Handling
424
-
425
- ```typescript
426
- import {
427
- RpcQueueFullError,
428
- RpcConnectionError,
429
- RpcQueueEvictedError,
430
- isQueueFullError,
431
- isConnectionError,
432
- isQueueEvictedError,
433
- } from "electron-json-rpc/renderer";
434
-
435
- const rpc = createQueuedRpcClient();
436
-
437
- try {
438
- await rpc.call("someMethod");
439
- } catch (error) {
440
- if (isQueueFullError(error)) {
441
- console.log(`Queue full: ${error.currentSize}/${error.maxSize}`);
442
- } else if (isConnectionError(error)) {
443
- console.log(`Connection lost: ${error.message}`);
444
- } else if (isQueueEvictedError(error)) {
445
- console.log(`Request evicted: ${error.reason}`);
446
- }
447
- }
448
- ```
449
-
450
328
  ## Debug Logging
451
329
 
452
330
  The renderer client provides built-in debug logging for monitoring RPC requests and responses. This is useful for development and troubleshooting.
@@ -683,7 +561,7 @@ while (true) {
683
561
  if (done) break;
684
562
  console.log(value);
685
563
  }
686
- reader.release();
564
+ reader.releaseLock();
687
565
 
688
566
  // Pipe to Response
689
567
  const response = new Response(rpc.stream("fetchData", "https://api.example.com/data"));
@@ -693,7 +571,7 @@ const blob = await response.blob();
693
571
  ### Stream Utilities
694
572
 
695
573
  ```typescript
696
- import { asyncGeneratorToStream, iterableToStream } from "electron-json-rpc/stream";
574
+ import { asyncGeneratorToStream, iterableToStream, readStream } from "electron-json-rpc/stream";
697
575
 
698
576
  // Convert async generator to stream
699
577
  rpc.registerStream("numbers", () => {
@@ -709,6 +587,10 @@ rpc.registerStream("numbers", () => {
709
587
  rpc.registerStream("items", () => {
710
588
  return iterableToStream([1, 2, 3, 4, 5]);
711
589
  });
590
+
591
+ // Read entire stream into array (utility function)
592
+ const allChunks = await readStream<number>(stream);
593
+ console.log(allChunks); // [1, 2, 3, 4, 5]
712
594
  ```
713
595
 
714
596
  ## Validation
@@ -796,40 +678,6 @@ rpc.register(
796
678
  );
797
679
  ```
798
680
 
799
- ### With Ajv
800
-
801
- ```typescript
802
- import Ajv from "ajv";
803
- import { RpcServer } from "electron-json-rpc/main";
804
-
805
- const rpc = new RpcServer();
806
- const ajv = new Ajv();
807
-
808
- const validateUser = ajv.compile({
809
- type: "object",
810
- properties: {
811
- name: { type: "string", minLength: 1 },
812
- age: { type: "number", minimum: 0, maximum: 150 },
813
- },
814
- required: ["name", "age"],
815
- additionalProperties: false,
816
- });
817
-
818
- rpc.register(
819
- "createUser",
820
- async (user) => {
821
- return db.users.create(user);
822
- },
823
- {
824
- validate: (params) => {
825
- if (!validateUser(params[0])) {
826
- throw new Error(ajv.errorsText(validateUser.errors));
827
- }
828
- },
829
- },
830
- );
831
- ```
832
-
833
681
  ## API Reference
834
682
 
835
683
  ### Main Process (`electron-json-rpc/main`)
@@ -938,34 +786,6 @@ api.log("hello");
938
786
 
939
787
  Use the proxy exposed by preload (if methods whitelist was provided).
940
788
 
941
- #### `createQueuedRpcClient(options?)`
942
-
943
- Create a queued RPC client with automatic retry and connection health checking.
944
-
945
- ```typescript
946
- const rpc = createQueuedRpcClient({
947
- maxSize: 100, // Maximum queue size (default: 100)
948
- fullBehavior: "reject", // 'reject' | 'evict' | 'evictOldest'
949
- timeout: 30000, // Request timeout in ms (default: 30000)
950
- retry: {
951
- maxAttempts: 3, // Maximum retry attempts (default: 3)
952
- backoff: "exponential", // 'fixed' | 'exponential'
953
- initialDelay: 1000, // Initial delay in ms (default: 1000)
954
- maxDelay: 10000, // Maximum delay in ms (default: 10000)
955
- },
956
- healthCheck: true, // Enable health check (default: true)
957
- healthCheckInterval: 5000, // Health check interval in ms (default: 5000)
958
- apiName: "rpc", // API name (default: 'rpc')
959
- });
960
-
961
- // Queue control methods
962
- rpc.getQueueStatus(); // Returns QueueStatus
963
- rpc.clearQueue(); // Clear all pending requests
964
- rpc.pauseQueue(); // Pause queue processing
965
- rpc.resumeQueue(); // Resume queue processing
966
- rpc.isQueueHealthy(); // Returns true if connected and not paused
967
- ```
968
-
969
789
  #### `createEventBus<T>(options?)`
970
790
 
971
791
  Create a typed event bus for real-time events from main process.
@@ -992,6 +812,24 @@ events.once("data-changed", (data) => {
992
812
  });
993
813
  ```
994
814
 
815
+ ### Stream Utilities (`electron-json-rpc/stream`)
816
+
817
+ - `isReadableStream(value: unknown): boolean` - Check if a value is a ReadableStream
818
+ - `readStream<T>(stream: ReadableStream<T>): Promise<T[]>` - Read entire stream into an array
819
+ - `asyncGeneratorToStream<T>(generator: () => AsyncGenerator<T>): ReadableStream<T>` - Convert async generator to stream
820
+ - `iterableToStream<T>(iterable: Iterable<T> | AsyncIterable<T>): ReadableStream<T>` - Convert iterable to stream
821
+
822
+ ### Error Handling (`electron-json-rpc/error`)
823
+
824
+ - `createJsonRpcError(code, message, data?): JsonRpcError` - Create a JSON-RPC error object
825
+ - `errors` - Predefined error creators (parseError, invalidRequest, methodNotFound, invalidParams, internalError)
826
+ - `isJsonRpcError(error: unknown): boolean` - Check if an error is a JSON-RPC error
827
+ - `errorToJsonRpc(error: unknown): JsonRpcError` - Convert an Error object to JSON-RPC error
828
+ - `RpcTimeoutError` - Timeout error class
829
+ - `isTimeoutError(error: unknown): boolean` - Check if error is a timeout error
830
+ - `RpcConnectionError` - Connection error class
831
+ - `isConnectionError(error: unknown): boolean` - Check if error is a connection error
832
+
995
833
  ## Error Handling
996
834
 
997
835
  JSON-RPC errors are returned with standard error codes:
@@ -1004,7 +842,29 @@ JSON-RPC errors are returned with standard error codes:
1004
842
  | -32602 | Invalid params |
1005
843
  | -32603 | Internal error |
1006
844
 
1007
- Timeout errors use a custom `RpcTimeoutError` class.
845
+ **Custom Error Classes:**
846
+
847
+ - **`RpcTimeoutError`** - Thrown when an RPC call exceeds timeout duration
848
+ - **`RpcConnectionError`** - Thrown when connection to main process is lost
849
+
850
+ ```typescript
851
+ import {
852
+ RpcTimeoutError,
853
+ RpcConnectionError,
854
+ isTimeoutError,
855
+ isConnectionError,
856
+ } from "electron-json-rpc/error";
857
+
858
+ try {
859
+ await rpc.call("someMethod");
860
+ } catch (error) {
861
+ if (isTimeoutError(error)) {
862
+ console.log(`Request timed out after ${error.timeout}ms`);
863
+ } else if (isConnectionError(error)) {
864
+ console.log("Connection lost:", error.message);
865
+ }
866
+ }
867
+ ```
1008
868
 
1009
869
  ## Bundle Size
1010
870
 
@@ -1014,12 +874,10 @@ ESM bundles:
1014
874
  | ---------------- | ------- |
1015
875
  | Preload | 3.95 kB |
1016
876
  | Main | 2.97 kB |
1017
- | Queue | 1.99 kB |
1018
877
  | Debug | 1.50 kB |
1019
878
  | Renderer/client | 1.14 kB |
1020
879
  | Renderer/builder | 1.21 kB |
1021
880
  | Renderer/event | 1.15 kB |
1022
- | Renderer/queue | 0.93 kB |
1023
881
  | Stream | 0.72 kB |
1024
882
  | Event | 0.43 kB |
1025
883