electron-state-sync 1.1.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.
Files changed (97) hide show
  1. package/CLAUDE.md +254 -0
  2. package/LICENSE +21 -0
  3. package/README.md +753 -0
  4. package/README.zh-CN.md +743 -0
  5. package/bun.lock +542 -0
  6. package/bunfig.toml +7 -0
  7. package/dist/jotai.cjs +106 -0
  8. package/dist/jotai.d.cts +48 -0
  9. package/dist/jotai.d.cts.map +1 -0
  10. package/dist/jotai.d.ts +48 -0
  11. package/dist/jotai.d.ts.map +1 -0
  12. package/dist/jotai.js +105 -0
  13. package/dist/jotai.js.map +1 -0
  14. package/dist/main.cjs +177 -0
  15. package/dist/main.d.cts +26 -0
  16. package/dist/main.d.cts.map +1 -0
  17. package/dist/main.d.ts +26 -0
  18. package/dist/main.d.ts.map +1 -0
  19. package/dist/main.js +177 -0
  20. package/dist/main.js.map +1 -0
  21. package/dist/preact.cjs +46 -0
  22. package/dist/preact.d.cts +11 -0
  23. package/dist/preact.d.cts.map +1 -0
  24. package/dist/preact.d.ts +11 -0
  25. package/dist/preact.d.ts.map +1 -0
  26. package/dist/preact.js +46 -0
  27. package/dist/preact.js.map +1 -0
  28. package/dist/preload.cjs +51 -0
  29. package/dist/preload.d.cts +20 -0
  30. package/dist/preload.d.cts.map +1 -0
  31. package/dist/preload.d.ts +20 -0
  32. package/dist/preload.d.ts.map +1 -0
  33. package/dist/preload.js +51 -0
  34. package/dist/preload.js.map +1 -0
  35. package/dist/react-query.cjs +113 -0
  36. package/dist/react-query.d.cts +58 -0
  37. package/dist/react-query.d.cts.map +1 -0
  38. package/dist/react-query.d.ts +58 -0
  39. package/dist/react-query.d.ts.map +1 -0
  40. package/dist/react-query.js +112 -0
  41. package/dist/react-query.js.map +1 -0
  42. package/dist/react.cjs +46 -0
  43. package/dist/react.d.cts +11 -0
  44. package/dist/react.d.cts.map +1 -0
  45. package/dist/react.d.ts +11 -0
  46. package/dist/react.d.ts.map +1 -0
  47. package/dist/react.js +46 -0
  48. package/dist/react.js.map +1 -0
  49. package/dist/redux.cjs +148 -0
  50. package/dist/redux.d.cts +80 -0
  51. package/dist/redux.d.cts.map +1 -0
  52. package/dist/redux.d.ts +80 -0
  53. package/dist/redux.d.ts.map +1 -0
  54. package/dist/redux.js +146 -0
  55. package/dist/redux.js.map +1 -0
  56. package/dist/renderer/index.cjs +7 -0
  57. package/dist/renderer/index.d.cts +23 -0
  58. package/dist/renderer/index.d.cts.map +1 -0
  59. package/dist/renderer/index.d.ts +23 -0
  60. package/dist/renderer/index.d.ts.map +1 -0
  61. package/dist/renderer/index.js +3 -0
  62. package/dist/renderer-C7zF3UQm.js +57 -0
  63. package/dist/renderer-C7zF3UQm.js.map +1 -0
  64. package/dist/renderer-D3YziJ_U.cjs +86 -0
  65. package/dist/solid.cjs +74 -0
  66. package/dist/solid.d.cts +13 -0
  67. package/dist/solid.d.cts.map +1 -0
  68. package/dist/solid.d.ts +13 -0
  69. package/dist/solid.d.ts.map +1 -0
  70. package/dist/solid.js +74 -0
  71. package/dist/solid.js.map +1 -0
  72. package/dist/svelte.cjs +63 -0
  73. package/dist/svelte.d.cts +14 -0
  74. package/dist/svelte.d.cts.map +1 -0
  75. package/dist/svelte.d.ts +14 -0
  76. package/dist/svelte.d.ts.map +1 -0
  77. package/dist/svelte.js +63 -0
  78. package/dist/svelte.js.map +1 -0
  79. package/dist/types-7wPPX0ty.d.ts +37 -0
  80. package/dist/types-7wPPX0ty.d.ts.map +1 -0
  81. package/dist/types-C18dHgLI.d.cts +37 -0
  82. package/dist/types-C18dHgLI.d.cts.map +1 -0
  83. package/dist/vue.cjs +69 -0
  84. package/dist/vue.d.cts +15 -0
  85. package/dist/vue.d.cts.map +1 -0
  86. package/dist/vue.d.ts +15 -0
  87. package/dist/vue.d.ts.map +1 -0
  88. package/dist/vue.js +70 -0
  89. package/dist/vue.js.map +1 -0
  90. package/dist/zustand.cjs +193 -0
  91. package/dist/zustand.d.cts +61 -0
  92. package/dist/zustand.d.cts.map +1 -0
  93. package/dist/zustand.d.ts +61 -0
  94. package/dist/zustand.d.ts.map +1 -0
  95. package/dist/zustand.js +191 -0
  96. package/dist/zustand.js.map +1 -0
  97. package/package.json +162 -0
package/README.md ADDED
@@ -0,0 +1,753 @@
1
+ # Electron State Sync
2
+
3
+ [![npm version](https://badge.fury.io/js/electron-state-sync.svg)](https://www.npmjs.com/package/electron-state-sync) [![npm downloads](https://img.shields.io/npm/dm/electron-state-sync)](https://www.npmjs.com/package/electron-state-sync) [![License](https://img.shields.io/npm/l/electron-state-sync)](LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue)](https://www.typescriptlang.org/) [![Electron](https://img.shields.io/badge/Electron-18%2B-brightgreen)](https://electronjs.org/) [![CI](https://github.com/abramdev/electron-state-sync/actions/workflows/ci.yml/badge.svg)](https://github.com/abramdev/electron-state-sync/actions/workflows/ci.yml)
4
+
5
+ 🌐 [English](./README.md) | [中文](./README.zh-CN.md)
6
+
7
+ A lightweight Electron state synchronization library that enables seamless data sharing between main and renderer processes. Supports React, Vue, Svelte, SolidJS, Zustand, TanStack Query, Jotai, and Redux Toolkit with automatic multi-window sync.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install electron-state-sync
13
+ ```
14
+
15
+ ## Features
16
+
17
+ - 📦 **Lightweight**: Main 6.3KB, renderer 1.5-2.2KB
18
+ - 🧩 **Multi-Framework**: React / Vue / Svelte / Solid
19
+ - 🔄 **State Management**: Zustand / TanStack Query / Jotai / Redux Toolkit
20
+ - 🔒 **Write Control**: Support for read-only and writable modes
21
+ - ✅ **Validation**: Main process validates renderer writes with standard error codes
22
+ - 🔌 **Custom Bridge**: Support custom **SyncStateBridge** implementation
23
+
24
+ ## Usage
25
+
26
+ ### Main Process
27
+
28
+ #### Quick Setup
29
+
30
+ ```ts
31
+ // main.ts
32
+ import { state } from "electron-state-sync/main";
33
+
34
+ const counter = state({
35
+ name: "counter",
36
+ initialValue: 0,
37
+ });
38
+
39
+ counter.set(10);
40
+ const value = counter.get();
41
+ ```
42
+
43
+ #### Advanced Setup
44
+
45
+ ```ts
46
+ // main.ts
47
+ import { state } from "electron-state-sync/main";
48
+
49
+ const counter = state({
50
+ baseChannel: "state",
51
+ name: "counter",
52
+ initialValue: 0,
53
+ allowRendererSet: true,
54
+ resolveRendererValue: (value) => {
55
+ if (typeof value !== "number") {
56
+ throw new Error("counter only accepts number");
57
+ }
58
+ return value;
59
+ },
60
+ });
61
+
62
+ counter.set(100);
63
+ const current = counter.get();
64
+ ```
65
+
66
+ #### Multi-Window Sync
67
+
68
+ All windows automatically receive updates when state changes:
69
+
70
+ ```ts
71
+ // main.ts
72
+ import { state } from "electron-state-sync/main";
73
+
74
+ const theme = state({
75
+ name: "theme",
76
+ initialValue: "light",
77
+ });
78
+
79
+ // All windows using this state will receive updates
80
+ theme.set("dark"); // Broadcast to all subscribed windows
81
+ ```
82
+
83
+ #### Stopping Sync
84
+
85
+ Call `dispose()` to stop syncing and clean up IPC handlers:
86
+
87
+ ```ts
88
+ // main.ts
89
+ import { state } from "electron-state-sync/main";
90
+
91
+ const counter = state({
92
+ name: "counter",
93
+ initialValue: 0,
94
+ });
95
+
96
+ counter.set(10); // Sync and broadcast
97
+ counter.get(); // Returns 10
98
+
99
+ // Stop syncing - removes IPC handlers and clears subscribers
100
+ counter.dispose();
101
+ ```
102
+
103
+ After `dispose()` is called:
104
+ - IPC handlers for `get`/`set`/`subscribe`/`unsubscribe` are removed
105
+ - All subscribers are cleared
106
+ - Renderer calls will fail silently
107
+
108
+ Each window subscribes to state changes and receives automatic updates:
109
+
110
+ ```ts
111
+ // renderer process
112
+ import { useSyncState } from "electron-state-sync/react";
113
+
114
+ const [theme] = useSyncState("light", {
115
+ name: "theme",
116
+ });
117
+ // When any window calls theme.set(), all windows update automatically
118
+ ```
119
+
120
+ ### Preload
121
+
122
+ ```ts
123
+ // preload.ts
124
+ import { exposeSyncState } from "electron-state-sync/preload";
125
+
126
+ exposeSyncState();
127
+ ```
128
+
129
+ ### Common Interface
130
+
131
+ Browser exposes **window.syncState** with **get** / **set** / **subscribe**:
132
+
133
+ ```ts
134
+ // renderer process
135
+ const bridge = window.syncState;
136
+ if (!bridge) {
137
+ throw new Error("syncState not injected");
138
+ }
139
+
140
+ const value = await bridge.get<number>({
141
+ baseChannel: "state",
142
+ name: "counter",
143
+ });
144
+
145
+ await bridge.set(
146
+ {
147
+ baseChannel: "state",
148
+ name: "counter",
149
+ },
150
+ value + 1
151
+ );
152
+
153
+ const unsubscribe = bridge.subscribe<number>(
154
+ {
155
+ baseChannel: "state",
156
+ name: "counter",
157
+ },
158
+ (nextValue) => {
159
+ console.log(nextValue);
160
+ }
161
+ );
162
+ ```
163
+
164
+ ### Custom Bridge
165
+
166
+ You can implement **SyncStateBridge** for custom integration:
167
+
168
+ ```ts
169
+ // renderer process
170
+ import type { SyncStateBridge } from "electron-state-sync/renderer";
171
+
172
+ const customBridge: SyncStateBridge = {
173
+ get: async (options) => window.syncState!.get(options),
174
+ set: async (options, value) => window.syncState!.set(options, value),
175
+ subscribe: (options, listener) => window.syncState!.subscribe(options, listener),
176
+ };
177
+ ```
178
+
179
+ ### Vue
180
+
181
+ #### Minimal Usage
182
+
183
+ ```ts
184
+ // renderer process
185
+ import { useSyncState } from "electron-state-sync/vue";
186
+
187
+ const counter = useSyncState(0, {
188
+ name: "counter",
189
+ });
190
+ // counter.isSynced - Ref<boolean>
191
+ ```
192
+
193
+ #### Use Global Configuration
194
+
195
+ ```ts
196
+ // renderer process
197
+ import { initSyncState, useSyncState } from "electron-state-sync/vue";
198
+
199
+ initSyncState({
200
+ baseChannel: "myapp",
201
+ });
202
+
203
+ const counter = useSyncState(0, {
204
+ name: "counter",
205
+ });
206
+
207
+ const user = useSyncState({ name: "" }, {
208
+ name: "user",
209
+ });
210
+
211
+ const theme = useSyncState("light", {
212
+ baseChannel: "settings",
213
+ name: "theme",
214
+ });
215
+ ```
216
+
217
+ #### Custom Bridge
218
+
219
+ ```ts
220
+ // renderer process
221
+ import { useSyncState } from "electron-state-sync/vue";
222
+
223
+ const counter = useSyncState(0, {
224
+ name: "counter",
225
+ bridge: customBridge,
226
+ deep: false,
227
+ });
228
+ ```
229
+
230
+ ### React
231
+
232
+ #### Minimal Usage
233
+
234
+ ```ts
235
+ // renderer process
236
+ import { useSyncState } from "electron-state-sync/react";
237
+
238
+ function App() {
239
+ const [counter, setCounter, isSynced] = useSyncState(0, {
240
+ name: "counter",
241
+ });
242
+
243
+ return <div onClick={() => setCounter(5)}>{counter}</div>;
244
+ }
245
+ ```
246
+
247
+ #### Use Global Configuration
248
+
249
+ ```ts
250
+ // renderer process
251
+ import { initSyncState, useSyncState } from "electron-state-sync/react";
252
+
253
+ initSyncState({
254
+ baseChannel: "myapp",
255
+ });
256
+
257
+ const [counter, setCounter] = useSyncState(0, {
258
+ name: "counter",
259
+ });
260
+
261
+ const [user, setUser] = useSyncState({ name: "" }, {
262
+ name: "user",
263
+ });
264
+
265
+ const [theme, setTheme] = useSyncState("light", {
266
+ baseChannel: "settings",
267
+ name: "theme",
268
+ });
269
+ ```
270
+
271
+ #### Custom Bridge
272
+
273
+ ```ts
274
+ // renderer process
275
+ import { useSyncState } from "electron-state-sync/react";
276
+
277
+ const [counter, setCounter] = useSyncState(0, {
278
+ name: "counter",
279
+ bridge: customBridge,
280
+ });
281
+ ```
282
+
283
+ ### Svelte
284
+
285
+ #### Minimal Usage
286
+
287
+ ```ts
288
+ // renderer process
289
+ import { useSyncState } from "electron-state-sync/svelte";
290
+
291
+ const counter = useSyncState(0, {
292
+ name: "counter",
293
+ });
294
+ // counter.isSynced - Readable<boolean>
295
+ ```
296
+
297
+ #### Use Global Configuration
298
+
299
+ ```ts
300
+ // renderer process
301
+ import { initSyncState, useSyncState } from "electron-state-sync/svelte";
302
+
303
+ initSyncState({
304
+ baseChannel: "myapp",
305
+ });
306
+
307
+ const counter = useSyncState(0, {
308
+ name: "counter",
309
+ });
310
+
311
+ const user = useSyncState({ name: "" }, {
312
+ name: "user",
313
+ });
314
+
315
+ const theme = useSyncState("light", {
316
+ baseChannel: "settings",
317
+ name: "theme",
318
+ });
319
+ ```
320
+
321
+ #### Custom Bridge
322
+
323
+ ```ts
324
+ // renderer process
325
+ import { useSyncState } from "electron-state-sync/svelte";
326
+
327
+ const counter = useSyncState(0, {
328
+ name: "counter",
329
+ bridge: customBridge,
330
+ });
331
+ ```
332
+
333
+ ```svelte
334
+ <script lang="ts">
335
+ import { counter } from "./stores";
336
+ </script>
337
+
338
+ <div>{$counter}</div>
339
+ ```
340
+
341
+ ### SolidJS
342
+
343
+ #### Minimal Usage
344
+
345
+ ```ts
346
+ // renderer process
347
+ import { useSyncState } from "electron-state-sync/solid";
348
+
349
+ const [counter, setCounter, isSynced] = useSyncState(0, {
350
+ name: "counter",
351
+ });
352
+ ```
353
+
354
+ #### Use Global Configuration
355
+
356
+ ```ts
357
+ // renderer process
358
+ import { initSyncState, useSyncState } from "electron-state-sync/solid";
359
+
360
+ initSyncState({
361
+ baseChannel: "myapp",
362
+ });
363
+
364
+ const [counter, setCounter] = useSyncState(0, {
365
+ name: "counter",
366
+ });
367
+
368
+ const [user, setUser] = useSyncState({ name: "" }, {
369
+ name: "user",
370
+ });
371
+
372
+ const [theme, setTheme] = useSyncState("light", {
373
+ baseChannel: "settings",
374
+ name: "theme",
375
+ });
376
+ ```
377
+
378
+ #### Custom Bridge
379
+
380
+ ```ts
381
+ // renderer process
382
+ import { useSyncState } from "electron-state-sync/solid";
383
+
384
+ const [counter, setCounter] = useSyncState(0, {
385
+ name: "counter",
386
+ bridge: customBridge,
387
+ });
388
+ ```
389
+
390
+ ### Zustand
391
+
392
+ #### Minimal Usage
393
+
394
+ ```ts
395
+ // renderer process
396
+ import { create } from "zustand";
397
+ import { syncStateMiddleware } from "electron-state-sync/zustand";
398
+
399
+ const useStore = create(
400
+ syncStateMiddleware({ name: "counter" })((set) => ({
401
+ count: 0,
402
+ increment: () => set((state) => ({ count: state.count + 1 })),
403
+ }))
404
+ );
405
+
406
+ // In component
407
+ const count = useStore((state) => state.count);
408
+ ```
409
+
410
+ #### Use Global Configuration
411
+
412
+ ```ts
413
+ // renderer process
414
+ import { initSyncState } from "electron-state-sync/zustand";
415
+ import { create } from "zustand";
416
+ import { syncStateMiddleware } from "electron-state-sync/zustand";
417
+
418
+ initSyncState({
419
+ baseChannel: "myapp",
420
+ });
421
+
422
+ const useStore = create(
423
+ syncStateMiddleware({ name: "counter" })((set) => ({
424
+ count: 0,
425
+ increment: () => set((state) => ({ count: state.count + 1 })),
426
+ }))
427
+ );
428
+ ```
429
+
430
+ #### Custom Bridge
431
+
432
+ ```ts
433
+ // renderer process
434
+ import { create } from "zustand";
435
+ import { syncStateMiddleware } from "electron-state-sync/zustand";
436
+
437
+ const useStore = create(
438
+ syncStateMiddleware({
439
+ name: "counter",
440
+ bridge: customBridge,
441
+ })((set) => ({
442
+ count: 0,
443
+ increment: () => set((state) => ({ count: state.count + 1 })),
444
+ }))
445
+ );
446
+ ```
447
+
448
+ ### TanStack Query (React Query)
449
+
450
+ #### Minimal Usage
451
+
452
+ ```ts
453
+ // renderer process
454
+ import { useSyncState } from "electron-state-sync/react-query";
455
+
456
+ function App() {
457
+ const { data: count, isSynced, update } = useSyncState(0, {
458
+ name: "counter",
459
+ });
460
+
461
+ return <div onClick={() => update(5)}>{count}</div>;
462
+ }
463
+ ```
464
+
465
+ #### Use Global Configuration
466
+
467
+ ```ts
468
+ // renderer process
469
+ import { initSyncState, useSyncState } from "electron-state-sync/react-query";
470
+
471
+ initSyncState({
472
+ baseChannel: "myapp",
473
+ });
474
+
475
+ function App() {
476
+ const { data: count, isSynced, update } = useSyncState(0, {
477
+ name: "counter",
478
+ });
479
+
480
+ return <div onClick={() => update(5)}>{count}</div>;
481
+ }
482
+ ```
483
+
484
+ #### Custom Bridge
485
+
486
+ ```ts
487
+ // renderer process
488
+ import { useSyncState } from "electron-state-sync/react-query";
489
+
490
+ function App() {
491
+ const { data: count, isSynced, update } = useSyncState(0, {
492
+ name: "counter",
493
+ bridge: customBridge,
494
+ });
495
+
496
+ return <div onClick={() => update(5)}>{count}</div>;
497
+ }
498
+ ```
499
+
500
+ ### Jotai
501
+
502
+ #### Minimal Usage
503
+
504
+ ```ts
505
+ // renderer process
506
+ import { atom, useAtom } from "jotai";
507
+ import { syncStateAtom } from "electron-state-sync/jotai";
508
+
509
+ const countAtom = syncStateAtom(0, { name: "counter" });
510
+
511
+ function App() {
512
+ const [count, setCount] = useAtom(countAtom);
513
+ return <div onClick={() => setCount(5)}>{count}</div>;
514
+ }
515
+ ```
516
+
517
+ #### Use Global Configuration
518
+
519
+ ```ts
520
+ // renderer process
521
+ import { initSyncState } from "electron-state-sync/jotai";
522
+ import { atom, useAtom } from "jotai";
523
+ import { syncStateAtom } from "electron-state-sync/jotai";
524
+
525
+ initSyncState({
526
+ baseChannel: "myapp",
527
+ });
528
+
529
+ const countAtom = syncStateAtom(0, { name: "counter" });
530
+
531
+ function App() {
532
+ const [count, setCount] = useAtom(countAtom);
533
+ return <div onClick={() => setCount(5)}>{count}</div>;
534
+ }
535
+ ```
536
+
537
+ #### Custom Bridge
538
+
539
+ ```ts
540
+ // renderer process
541
+ import { atom, useAtom } from "jotai";
542
+ import { syncStateAtom } from "electron-state-sync/jotai";
543
+
544
+ const countAtom = syncStateAtom(0, {
545
+ name: "counter",
546
+ bridge: customBridge,
547
+ });
548
+
549
+ function App() {
550
+ const [count, setCount] = useAtom(countAtom);
551
+ return <div onClick={() => setCount(5)}>{count}</div>;
552
+ }
553
+ ```
554
+
555
+ ### Redux Toolkit
556
+
557
+ #### Minimal Usage
558
+
559
+ ```ts
560
+ // renderer process
561
+ import { configureStore, createSlice } from "@reduxjs/toolkit";
562
+ import { syncStateMiddleware } from "electron-state-sync/redux";
563
+ import { Provider, useDispatch, useSelector } from "react-redux";
564
+
565
+ const counterSlice = createSlice({
566
+ name: "counter",
567
+ initialState: { value: 0 },
568
+ reducers: {
569
+ setValue: (state, action) => {
570
+ state.value = action.payload;
571
+ },
572
+ },
573
+ });
574
+
575
+ const store = configureStore({
576
+ reducer: {
577
+ counter: counterSlice.reducer,
578
+ },
579
+ middleware: (getDefaultMiddleware) =>
580
+ getDefaultMiddleware().concat(
581
+ syncStateMiddleware({
582
+ name: "counter",
583
+ selector: (state) => state.counter.value,
584
+ actionType: "counter/setValue",
585
+ })
586
+ ),
587
+ });
588
+
589
+ function App() {
590
+ const count = useSelector((state) => state.counter.value);
591
+ const dispatch = useDispatch();
592
+ return <div onClick={() => dispatch(counterSlice.actions.setValue(5))}>{count}</div>;
593
+ }
594
+ ```
595
+
596
+ #### Use Global Configuration
597
+
598
+ ```ts
599
+ // renderer process
600
+ import { initSyncState } from "electron-state-sync/redux";
601
+ import { configureStore, createSlice } from "@reduxjs/toolkit";
602
+ import { syncStateMiddleware } from "electron-state-sync/redux";
603
+
604
+ initSyncState({
605
+ baseChannel: "myapp",
606
+ });
607
+
608
+ const counterSlice = createSlice({
609
+ name: "counter",
610
+ initialState: { value: 0 },
611
+ reducers: {
612
+ setValue: (state, action) => {
613
+ state.value = action.payload;
614
+ },
615
+ },
616
+ });
617
+
618
+ const store = configureStore({
619
+ reducer: {
620
+ counter: counterSlice.reducer,
621
+ },
622
+ middleware: (getDefaultMiddleware) =>
623
+ getDefaultMiddleware().concat(
624
+ syncStateMiddleware({
625
+ name: "counter",
626
+ selector: (state) => state.counter.value,
627
+ actionType: "counter/setValue",
628
+ })
629
+ ),
630
+ });
631
+ ```
632
+
633
+ #### Custom Bridge
634
+
635
+ ```ts
636
+ // renderer process
637
+ import { configureStore, createSlice } from "@reduxjs/toolkit";
638
+ import { syncStateMiddleware } from "electron-state-sync/redux";
639
+
640
+ const counterSlice = createSlice({
641
+ name: "counter",
642
+ initialState: { value: 0 },
643
+ reducers: {
644
+ setValue: (state, action) => {
645
+ state.value = action.payload;
646
+ },
647
+ },
648
+ });
649
+
650
+ const store = configureStore({
651
+ reducer: {
652
+ counter: counterSlice.reducer,
653
+ },
654
+ middleware: (getDefaultMiddleware) =>
655
+ getDefaultMiddleware().concat(
656
+ syncStateMiddleware({
657
+ name: "counter",
658
+ selector: (state) => state.counter.value,
659
+ actionType: "counter/setValue",
660
+ bridge: customBridge,
661
+ })
662
+ ),
663
+ });
664
+ ```
665
+
666
+ ### IPC Channel Naming
667
+
668
+ Channel format: **${baseChannel}:${name}:get|set|subscribe|unsubscribe|update**.
669
+
670
+ ### Write Permission & Validation
671
+
672
+ - **allowRendererSet: false** blocks renderer writes and throws error, but subscription still works.
673
+ - **resolveRendererValue** validates or transforms renderer writes, throws error rejects write.
674
+ - Main process is always the authority source, all changes broadcast to subscribers.
675
+
676
+ ### Initial Value Sync
677
+
678
+ - Renderer **initialValue** is only for initial placeholder, final value from main process.
679
+ - Renderer subscribes to main process updates first, then calls **get** to fetch current value, may trigger one overwrite update.
680
+ - If main process initial value matches renderer, usually no flash is perceived.
681
+ - Renderer can read **isSynced** to check if first sync completed.
682
+ - React/Solid Hook returns **isSynced** as third value.
683
+ - Vue Ref has **isSynced** property mounted.
684
+ - Svelte Store has **isSynced** store.
685
+
686
+ ### Error Codes
687
+
688
+ - Readonly write: **SyncStateError** code is **RENDERER_READONLY**.
689
+ - Validation failed: **SyncStateError** code is **RENDERER_INVALID_VALUE**.
690
+
691
+ ### Object Deep Watch
692
+
693
+ **Vue Only**: Deep watch is only supported in Vue integration.
694
+
695
+ Enable deep watch when value is object (Vue only):
696
+
697
+ ```ts
698
+ // Vue example
699
+ const profile = useSyncState(
700
+ { name: "Alice" },
701
+ {
702
+ name: "profile",
703
+ deep: true, // Only available in Vue
704
+ }
705
+ );
706
+ ```
707
+
708
+ **Note**:
709
+ - Vue integration converts reactive proxies to raw values before syncing to ensure IPC serialization.
710
+ - React, Svelte, and SolidJS integrations do not support deep watch. For object state changes in those frameworks, create a new object reference to trigger updates.
711
+
712
+ ## Bundle Size
713
+
714
+ Framework bundles (ESM / CJS):
715
+
716
+ | Package | ESM | CJS | gzip |
717
+ |---------|-----|-----|------|
718
+ | Main | 6.44 kB | 6.51 kB | 1.95 kB |
719
+ | Preload | 1.49 kB | 1.54 kB | 0.49 kB |
720
+ | Zustand | 5.88 kB | 6.06 kB | 1.43 kB |
721
+ | Redux | 4.37 kB | 4.54 kB | 1.34 kB |
722
+ | React Query | 3.34 kB | 3.53 kB | 1.13 kB |
723
+ | Jotai | 3.32 kB | 3.44 kB | 1.14 kB |
724
+ | Vue | 2.24 kB | 2.25 kB | 0.81 kB |
725
+ | Solid | 2.21 kB | 2.24 kB | 0.77 kB |
726
+ | Svelte | 1.77 kB | 1.82 kB | 0.64 kB |
727
+ | Preact | 1.43 kB | 1.51 kB | 0.56 kB |
728
+ | React | 1.42 kB | 1.45 kB | 0.55 kB |
729
+
730
+ ## Requirements
731
+
732
+ - **Electron**: ≥ 18.0.0 (recommended ≥ 32.0.0)
733
+ - **Node.js**: ≥ 16.9.0
734
+ - **TypeScript**: ≥ 5.0.0 (if using TypeScript)
735
+
736
+ **Framework Integration** (choose as needed):
737
+
738
+ - **React**: ≥ 18.0.0
739
+ - **Vue**: ≥ 3.0.0
740
+ - **Svelte**: ≥ 3.0.0
741
+ - **SolidJS**: ≥ 1.0.0
742
+
743
+ **State Management Integration** (choose as needed):
744
+
745
+ - **Zustand**: ≥ 4.0.0
746
+ - **TanStack Query**: ≥ 5.0.0
747
+ - **Jotai**: ≥ 2.0.0
748
+ - **Redux Toolkit**: ≥ 2.0.0
749
+ - **React Redux**: ≥ 9.0.0 (for Redux Toolkit integration)
750
+
751
+ ## License
752
+
753
+ MIT