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.
- package/CLAUDE.md +254 -0
- package/LICENSE +21 -0
- package/README.md +753 -0
- package/README.zh-CN.md +743 -0
- package/bun.lock +542 -0
- package/bunfig.toml +7 -0
- package/dist/jotai.cjs +106 -0
- package/dist/jotai.d.cts +48 -0
- package/dist/jotai.d.cts.map +1 -0
- package/dist/jotai.d.ts +48 -0
- package/dist/jotai.d.ts.map +1 -0
- package/dist/jotai.js +105 -0
- package/dist/jotai.js.map +1 -0
- package/dist/main.cjs +177 -0
- package/dist/main.d.cts +26 -0
- package/dist/main.d.cts.map +1 -0
- package/dist/main.d.ts +26 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +177 -0
- package/dist/main.js.map +1 -0
- package/dist/preact.cjs +46 -0
- package/dist/preact.d.cts +11 -0
- package/dist/preact.d.cts.map +1 -0
- package/dist/preact.d.ts +11 -0
- package/dist/preact.d.ts.map +1 -0
- package/dist/preact.js +46 -0
- package/dist/preact.js.map +1 -0
- package/dist/preload.cjs +51 -0
- package/dist/preload.d.cts +20 -0
- package/dist/preload.d.cts.map +1 -0
- package/dist/preload.d.ts +20 -0
- package/dist/preload.d.ts.map +1 -0
- package/dist/preload.js +51 -0
- package/dist/preload.js.map +1 -0
- package/dist/react-query.cjs +113 -0
- package/dist/react-query.d.cts +58 -0
- package/dist/react-query.d.cts.map +1 -0
- package/dist/react-query.d.ts +58 -0
- package/dist/react-query.d.ts.map +1 -0
- package/dist/react-query.js +112 -0
- package/dist/react-query.js.map +1 -0
- package/dist/react.cjs +46 -0
- package/dist/react.d.cts +11 -0
- package/dist/react.d.cts.map +1 -0
- package/dist/react.d.ts +11 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +46 -0
- package/dist/react.js.map +1 -0
- package/dist/redux.cjs +148 -0
- package/dist/redux.d.cts +80 -0
- package/dist/redux.d.cts.map +1 -0
- package/dist/redux.d.ts +80 -0
- package/dist/redux.d.ts.map +1 -0
- package/dist/redux.js +146 -0
- package/dist/redux.js.map +1 -0
- package/dist/renderer/index.cjs +7 -0
- package/dist/renderer/index.d.cts +23 -0
- package/dist/renderer/index.d.cts.map +1 -0
- package/dist/renderer/index.d.ts +23 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +3 -0
- package/dist/renderer-C7zF3UQm.js +57 -0
- package/dist/renderer-C7zF3UQm.js.map +1 -0
- package/dist/renderer-D3YziJ_U.cjs +86 -0
- package/dist/solid.cjs +74 -0
- package/dist/solid.d.cts +13 -0
- package/dist/solid.d.cts.map +1 -0
- package/dist/solid.d.ts +13 -0
- package/dist/solid.d.ts.map +1 -0
- package/dist/solid.js +74 -0
- package/dist/solid.js.map +1 -0
- package/dist/svelte.cjs +63 -0
- package/dist/svelte.d.cts +14 -0
- package/dist/svelte.d.cts.map +1 -0
- package/dist/svelte.d.ts +14 -0
- package/dist/svelte.d.ts.map +1 -0
- package/dist/svelte.js +63 -0
- package/dist/svelte.js.map +1 -0
- package/dist/types-7wPPX0ty.d.ts +37 -0
- package/dist/types-7wPPX0ty.d.ts.map +1 -0
- package/dist/types-C18dHgLI.d.cts +37 -0
- package/dist/types-C18dHgLI.d.cts.map +1 -0
- package/dist/vue.cjs +69 -0
- package/dist/vue.d.cts +15 -0
- package/dist/vue.d.cts.map +1 -0
- package/dist/vue.d.ts +15 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +70 -0
- package/dist/vue.js.map +1 -0
- package/dist/zustand.cjs +193 -0
- package/dist/zustand.d.cts +61 -0
- package/dist/zustand.d.cts.map +1 -0
- package/dist/zustand.d.ts +61 -0
- package/dist/zustand.d.ts.map +1 -0
- package/dist/zustand.js +191 -0
- package/dist/zustand.js.map +1 -0
- package/package.json +162 -0
package/README.md
ADDED
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
# Electron State Sync
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/electron-state-sync) [](https://www.npmjs.com/package/electron-state-sync) [](LICENSE) [](https://www.typescriptlang.org/) [](https://electronjs.org/) [](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
|