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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { a as SyncStateChannelOptions, t as SyncStateBridge } from "./types-C18dHgLI.cjs";
|
|
2
|
+
import { GetState, SetState, StateCreator, StoreApi } from "zustand";
|
|
3
|
+
|
|
4
|
+
//#region src/renderer/zustand.d.ts
|
|
5
|
+
interface SyncStateZustandOptions extends SyncStateChannelOptions {
|
|
6
|
+
bridge?: SyncStateBridge;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Zustand middleware that syncs store state with Electron main process
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { create } from 'zustand';
|
|
14
|
+
* import { syncStateMiddleware } from 'electron-state-sync/zustand';
|
|
15
|
+
*
|
|
16
|
+
* const useStore = create(
|
|
17
|
+
* syncStateMiddleware({ name: 'counter' })((set) => ({
|
|
18
|
+
* count: 0,
|
|
19
|
+
* increment: () => set((state) => ({ count: state.count + 1 }))
|
|
20
|
+
* }))
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare const syncStateMiddleware: <State extends Record<string, unknown>>(options: SyncStateZustandOptions) => (config: StateCreator<State>) => (set: SetState<State>, get: GetState<State>, api: StoreApi<State>) => State;
|
|
25
|
+
/**
|
|
26
|
+
* Hook to access sync state for a Zustand store
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { useSyncState } from 'electron-state-sync/zustand';
|
|
31
|
+
*
|
|
32
|
+
* function App() {
|
|
33
|
+
* const { state, isSynced } = useSyncState({ name: 'counter' });
|
|
34
|
+
* return <div>{state?.count}</div>;
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare const useSyncState: <State extends Record<string, unknown>>(options: SyncStateZustandOptions) => {
|
|
39
|
+
state: State | undefined;
|
|
40
|
+
isSynced: boolean;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Create a synced Zustand store (hook-less version for vanilla JS)
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* import { createSyncedStore } from 'electron-state-sync/zustand';
|
|
48
|
+
*
|
|
49
|
+
* const store = createSyncedStore(
|
|
50
|
+
* { count: 0 },
|
|
51
|
+
* { name: 'counter' },
|
|
52
|
+
* (set) => ({
|
|
53
|
+
* increment: () => set((state) => ({ count: state.count + 1 }))
|
|
54
|
+
* })
|
|
55
|
+
* );
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare const createSyncedStore: <State extends Record<string, unknown>>(initialState: State, options: SyncStateZustandOptions) => StoreApi<State>;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { SyncStateZustandOptions, createSyncedStore, syncStateMiddleware, useSyncState, useSyncState as useZustandSyncState };
|
|
61
|
+
//# sourceMappingURL=zustand.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zustand.d.cts","names":[],"sources":["../src/renderer/zustand.ts"],"sourcesContent":[],"mappings":";;;;UAUiB,uBAAA,SAAgC;EAAhC,MAAA,CAAA,EACN,eADM;AA4BjB;;;;;;;;;;;;;AA2GA;;;;AAEiB,cA7GJ,mBA6GI,EAAA,CAAA,cA5GA,MA4GA,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA,OAAA,EA5GkC,uBA4GlC,EAAA,GAAA,CAAA,MAAA,EA3GN,YA2GM,CA3GO,KA2GP,CAAA,EAAA,GAAA,CAAA,GAAA,EA1GT,QA0GS,CA1GA,KA0GA,CAAA,EAAA,GAAA,EA1Ga,QA0Gb,CA1GsB,KA0GtB,CAAA,EAAA,GAAA,EA1GmC,QA0GnC,CA1G4C,KA0G5C,CAAA,EAAA,GA1GqD,KA0GrD;AAgDjB;;;;;;;;;;;;;cAlDa,6BAA8B,kCAChC;SACC;;;;;;;;;;;;;;;;;;;cAgDC,kCAAmC,uCAChC,gBACL,4BACR,SAAS"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { a as SyncStateChannelOptions, t as SyncStateBridge } from "./types-7wPPX0ty.js";
|
|
2
|
+
import { GetState, SetState, StateCreator, StoreApi } from "zustand";
|
|
3
|
+
|
|
4
|
+
//#region src/renderer/zustand.d.ts
|
|
5
|
+
interface SyncStateZustandOptions extends SyncStateChannelOptions {
|
|
6
|
+
bridge?: SyncStateBridge;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Zustand middleware that syncs store state with Electron main process
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { create } from 'zustand';
|
|
14
|
+
* import { syncStateMiddleware } from 'electron-state-sync/zustand';
|
|
15
|
+
*
|
|
16
|
+
* const useStore = create(
|
|
17
|
+
* syncStateMiddleware({ name: 'counter' })((set) => ({
|
|
18
|
+
* count: 0,
|
|
19
|
+
* increment: () => set((state) => ({ count: state.count + 1 }))
|
|
20
|
+
* }))
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare const syncStateMiddleware: <State extends Record<string, unknown>>(options: SyncStateZustandOptions) => (config: StateCreator<State>) => (set: SetState<State>, get: GetState<State>, api: StoreApi<State>) => State;
|
|
25
|
+
/**
|
|
26
|
+
* Hook to access sync state for a Zustand store
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { useSyncState } from 'electron-state-sync/zustand';
|
|
31
|
+
*
|
|
32
|
+
* function App() {
|
|
33
|
+
* const { state, isSynced } = useSyncState({ name: 'counter' });
|
|
34
|
+
* return <div>{state?.count}</div>;
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare const useSyncState: <State extends Record<string, unknown>>(options: SyncStateZustandOptions) => {
|
|
39
|
+
state: State | undefined;
|
|
40
|
+
isSynced: boolean;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Create a synced Zustand store (hook-less version for vanilla JS)
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* import { createSyncedStore } from 'electron-state-sync/zustand';
|
|
48
|
+
*
|
|
49
|
+
* const store = createSyncedStore(
|
|
50
|
+
* { count: 0 },
|
|
51
|
+
* { name: 'counter' },
|
|
52
|
+
* (set) => ({
|
|
53
|
+
* increment: () => set((state) => ({ count: state.count + 1 }))
|
|
54
|
+
* })
|
|
55
|
+
* );
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare const createSyncedStore: <State extends Record<string, unknown>>(initialState: State, options: SyncStateZustandOptions) => StoreApi<State>;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { SyncStateZustandOptions, createSyncedStore, syncStateMiddleware, useSyncState, useSyncState as useZustandSyncState };
|
|
61
|
+
//# sourceMappingURL=zustand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zustand.d.ts","names":[],"sources":["../src/renderer/zustand.ts"],"sourcesContent":[],"mappings":";;;;UAUiB,uBAAA,SAAgC;EAAhC,MAAA,CAAA,EACN,eADM;AA4BjB;;;;;;;;;;;;;AA2GA;;;;AAEiB,cA7GJ,mBA6GI,EAAA,CAAA,cA5GA,MA4GA,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA,OAAA,EA5GkC,uBA4GlC,EAAA,GAAA,CAAA,MAAA,EA3GN,YA2GM,CA3GO,KA2GP,CAAA,EAAA,GAAA,CAAA,GAAA,EA1GT,QA0GS,CA1GA,KA0GA,CAAA,EAAA,GAAA,EA1Ga,QA0Gb,CA1GsB,KA0GtB,CAAA,EAAA,GAAA,EA1GmC,QA0GnC,CA1G4C,KA0G5C,CAAA,EAAA,GA1GqD,KA0GrD;AAgDjB;;;;;;;;;;;;;cAlDa,6BAA8B,kCAChC;SACC;;;;;;;;;;;;;;;;;;;cAgDC,kCAAmC,uCAChC,gBACL,4BACR,SAAS"}
|
package/dist/zustand.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { a as resolveSyncStateBridge, t as getGlobalConfig } from "./renderer-C7zF3UQm.js";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { createStore } from "zustand/vanilla";
|
|
4
|
+
|
|
5
|
+
//#region src/renderer/zustand.ts
|
|
6
|
+
const createChannelOptions = (options) => {
|
|
7
|
+
const globalConfig = getGlobalConfig();
|
|
8
|
+
return {
|
|
9
|
+
baseChannel: options.baseChannel ?? globalConfig.baseChannel,
|
|
10
|
+
name: options.name
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Zustand middleware that syncs store state with Electron main process
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { create } from 'zustand';
|
|
19
|
+
* import { syncStateMiddleware } from 'electron-state-sync/zustand';
|
|
20
|
+
*
|
|
21
|
+
* const useStore = create(
|
|
22
|
+
* syncStateMiddleware({ name: 'counter' })((set) => ({
|
|
23
|
+
* count: 0,
|
|
24
|
+
* increment: () => set((state) => ({ count: state.count + 1 }))
|
|
25
|
+
* }))
|
|
26
|
+
* );
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
const syncStateMiddleware = (options) => (config) => (set, get, api) => {
|
|
30
|
+
const globalConfig = getGlobalConfig();
|
|
31
|
+
const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
|
|
32
|
+
const channelOptions = createChannelOptions(options);
|
|
33
|
+
let isApplyingRemoteValue = false;
|
|
34
|
+
const originalSetState = api.setState;
|
|
35
|
+
api.setState = (partial, replace) => {
|
|
36
|
+
const currentState = get();
|
|
37
|
+
if (isApplyingRemoteValue) {
|
|
38
|
+
isApplyingRemoteValue = false;
|
|
39
|
+
return originalSetState(partial, replace);
|
|
40
|
+
}
|
|
41
|
+
let nextState;
|
|
42
|
+
if (typeof partial === "function") nextState = partial(currentState);
|
|
43
|
+
else if (replace) nextState = partial;
|
|
44
|
+
else nextState = {
|
|
45
|
+
...currentState,
|
|
46
|
+
...partial
|
|
47
|
+
};
|
|
48
|
+
const dataOnlyState = Object.fromEntries(Object.entries(nextState).filter(([_, v]) => typeof v !== "function"));
|
|
49
|
+
bridge.set(channelOptions, dataOnlyState);
|
|
50
|
+
return originalSetState(partial, replace);
|
|
51
|
+
};
|
|
52
|
+
const applyRemoteValue = (value) => {
|
|
53
|
+
isApplyingRemoteValue = true;
|
|
54
|
+
const currentState = get();
|
|
55
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) api.setState({
|
|
56
|
+
...currentState,
|
|
57
|
+
...value
|
|
58
|
+
}, true);
|
|
59
|
+
else {
|
|
60
|
+
const keys = Object.keys(currentState).filter((k) => typeof currentState[k] !== "function");
|
|
61
|
+
if (keys.length === 1) api.setState({
|
|
62
|
+
...currentState,
|
|
63
|
+
[keys[0]]: value
|
|
64
|
+
}, true);
|
|
65
|
+
else api.setState(value, true);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const unsubscribe = bridge.subscribe(channelOptions, applyRemoteValue);
|
|
69
|
+
bridge.get(channelOptions).then(applyRemoteValue);
|
|
70
|
+
const store = config(set, get, api);
|
|
71
|
+
const storeApi = api;
|
|
72
|
+
if (storeApi.destroy) {
|
|
73
|
+
const originalDestroy = storeApi.destroy;
|
|
74
|
+
storeApi.destroy = () => {
|
|
75
|
+
unsubscribe();
|
|
76
|
+
originalDestroy();
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return store;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Hook to access sync state for a Zustand store
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* import { useSyncState } from 'electron-state-sync/zustand';
|
|
87
|
+
*
|
|
88
|
+
* function App() {
|
|
89
|
+
* const { state, isSynced } = useSyncState({ name: 'counter' });
|
|
90
|
+
* return <div>{state?.count}</div>;
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
const useSyncState = (options) => {
|
|
95
|
+
const [isSynced, setIsSynced] = useState(false);
|
|
96
|
+
const [state, setState] = useState();
|
|
97
|
+
const globalConfig = getGlobalConfig();
|
|
98
|
+
const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
|
|
99
|
+
const channelOptions = useMemo(() => createChannelOptions(options), [options.baseChannel, options.name]);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const unsubscribe = bridge.subscribe(channelOptions, (value) => {
|
|
102
|
+
setState(value);
|
|
103
|
+
setIsSynced(true);
|
|
104
|
+
});
|
|
105
|
+
bridge.get(channelOptions).then((value) => {
|
|
106
|
+
setState(value);
|
|
107
|
+
setIsSynced(true);
|
|
108
|
+
});
|
|
109
|
+
return () => {
|
|
110
|
+
unsubscribe();
|
|
111
|
+
};
|
|
112
|
+
}, [bridge, channelOptions]);
|
|
113
|
+
return {
|
|
114
|
+
isSynced,
|
|
115
|
+
state
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Create a synced Zustand store (hook-less version for vanilla JS)
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* import { createSyncedStore } from 'electron-state-sync/zustand';
|
|
124
|
+
*
|
|
125
|
+
* const store = createSyncedStore(
|
|
126
|
+
* { count: 0 },
|
|
127
|
+
* { name: 'counter' },
|
|
128
|
+
* (set) => ({
|
|
129
|
+
* increment: () => set((state) => ({ count: state.count + 1 }))
|
|
130
|
+
* })
|
|
131
|
+
* );
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
const createSyncedStore = (initialState, options) => {
|
|
135
|
+
const globalConfig = getGlobalConfig();
|
|
136
|
+
const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
|
|
137
|
+
const channelOptions = createChannelOptions(options);
|
|
138
|
+
let isApplyingRemoteValue = false;
|
|
139
|
+
const store = createStore()(() => initialState);
|
|
140
|
+
const originalSetState = store.setState.bind(store);
|
|
141
|
+
store.setState = (partial, replace) => {
|
|
142
|
+
const currentState = store.getState();
|
|
143
|
+
if (isApplyingRemoteValue) {
|
|
144
|
+
isApplyingRemoteValue = false;
|
|
145
|
+
return originalSetState(partial, replace);
|
|
146
|
+
}
|
|
147
|
+
let nextState;
|
|
148
|
+
if (typeof partial === "function") nextState = partial(currentState);
|
|
149
|
+
else if (replace) nextState = partial;
|
|
150
|
+
else nextState = {
|
|
151
|
+
...currentState,
|
|
152
|
+
...partial
|
|
153
|
+
};
|
|
154
|
+
const dataOnlyState = Object.fromEntries(Object.entries(nextState).filter(([_, v]) => typeof v !== "function"));
|
|
155
|
+
bridge.set(channelOptions, dataOnlyState);
|
|
156
|
+
return originalSetState(partial, replace);
|
|
157
|
+
};
|
|
158
|
+
const applyRemoteValue = (value) => {
|
|
159
|
+
isApplyingRemoteValue = true;
|
|
160
|
+
const currentState = store.getState();
|
|
161
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) store.setState({
|
|
162
|
+
...currentState,
|
|
163
|
+
...value
|
|
164
|
+
});
|
|
165
|
+
else {
|
|
166
|
+
const keys = Object.keys(currentState).filter((k) => typeof currentState[k] !== "function");
|
|
167
|
+
if (keys.length === 1) store.setState({
|
|
168
|
+
...currentState,
|
|
169
|
+
[keys[0]]: value
|
|
170
|
+
});
|
|
171
|
+
else store.setState(value);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const unsubscribe = bridge.subscribe(channelOptions, applyRemoteValue);
|
|
175
|
+
bridge.get(channelOptions).then(applyRemoteValue);
|
|
176
|
+
const storeWithCleanup = store;
|
|
177
|
+
if (storeWithCleanup.destroy) {
|
|
178
|
+
const originalDestroy = storeWithCleanup.destroy;
|
|
179
|
+
storeWithCleanup.destroy = () => {
|
|
180
|
+
unsubscribe();
|
|
181
|
+
originalDestroy();
|
|
182
|
+
};
|
|
183
|
+
} else storeWithCleanup.destroy = () => {
|
|
184
|
+
unsubscribe();
|
|
185
|
+
};
|
|
186
|
+
return store;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
//#endregion
|
|
190
|
+
export { createSyncedStore, syncStateMiddleware, useSyncState, useSyncState as useZustandSyncState };
|
|
191
|
+
//# sourceMappingURL=zustand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zustand.js","names":[],"sources":["../src/renderer/zustand.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\n\nimport type { GetState, SetState, StateCreator, StoreApi } from \"zustand\";\nimport { createStore } from \"zustand/vanilla\";\n\nimport type { SyncStateChannelOptions } from \"../channels\";\nimport type { SyncStateBridge } from \"../types\";\nimport { resolveSyncStateBridge } from \"./bridge\";\nimport { getGlobalConfig } from \"./index\";\n\nexport interface SyncStateZustandOptions extends SyncStateChannelOptions {\n bridge?: SyncStateBridge;\n}\n\nconst createChannelOptions = (options: SyncStateZustandOptions): SyncStateChannelOptions => {\n const globalConfig = getGlobalConfig();\n return {\n baseChannel: options.baseChannel ?? globalConfig.baseChannel,\n name: options.name,\n };\n};\n\n/**\n * Zustand middleware that syncs store state with Electron main process\n *\n * @example\n * ```typescript\n * import { create } from 'zustand';\n * import { syncStateMiddleware } from 'electron-state-sync/zustand';\n *\n * const useStore = create(\n * syncStateMiddleware({ name: 'counter' })((set) => ({\n * count: 0,\n * increment: () => set((state) => ({ count: state.count + 1 }))\n * }))\n * );\n * ```\n */\nexport const syncStateMiddleware =\n <State extends Record<string, unknown>>(options: SyncStateZustandOptions) =>\n (config: StateCreator<State>) =>\n (set: SetState<State>, get: GetState<State>, api: StoreApi<State>): State => {\n const globalConfig = getGlobalConfig();\n const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);\n const channelOptions = createChannelOptions(options);\n\n // Track remote updates to prevent infinite loops\n let isApplyingRemoteValue = false;\n\n // Wrap setState to intercept local updates\n const originalSetState = api.setState;\n api.setState = (\n partial: Partial<State> | ((state: State) => Partial<State>),\n replace?: boolean,\n ) => {\n const currentState = get();\n\n // Check if this is a remote update\n if (isApplyingRemoteValue) {\n isApplyingRemoteValue = false;\n return originalSetState(partial, replace);\n }\n\n // Local update - sync to main process\n let nextState: State;\n if (typeof partial === \"function\") {\n nextState = (partial as (state: State) => State)(currentState);\n } else if (replace) {\n nextState = partial as State;\n } else {\n nextState = { ...currentState, ...partial } as State;\n }\n\n // Extract only data properties (exclude functions) for syncing to main process\n const dataOnlyState = Object.fromEntries(\n Object.entries(nextState as Record<string, unknown>).filter(\n ([_, v]) => typeof v !== \"function\",\n ),\n ) as State;\n\n void bridge.set(channelOptions, dataOnlyState);\n return originalSetState(partial, replace);\n };\n\n // 使用包装后的 setState 应用远程更新\n const applyRemoteValue = (value: State): void => {\n isApplyingRemoteValue = true;\n // If value is a primitive or the store state has nested structure,\n // merge it appropriately to preserve actions\n const currentState = get();\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n // Value is an object, merge it\n api.setState({ ...currentState, ...value } as State, true);\n } else {\n // Value is a primitive, assume it should be nested under a property\n // Try to infer the property name from current state structure\n const keys = Object.keys(currentState as Record<string, unknown>).filter(\n (k) => typeof (currentState as Record<string, unknown>)[k] !== \"function\",\n );\n if (keys.length === 1) {\n api.setState({ ...currentState, [keys[0]]: value } as State, true);\n } else {\n // Fallback: replace with value\n api.setState(value as Partial<State>, true);\n }\n }\n };\n\n // Subscribe to remote updates from main process\n const unsubscribe = bridge.subscribe<State>(channelOptions, applyRemoteValue);\n\n // Initialize from main process\n void bridge.get<State>(channelOptions).then(applyRemoteValue);\n\n // Create the store with the enhanced setState\n const store = config(set, get, api);\n\n // Cleanup on store destruction (if supported)\n const storeApi = api as unknown as StoreApi<State> & {\n destroy?: () => void;\n };\n if (storeApi.destroy) {\n const originalDestroy = storeApi.destroy;\n storeApi.destroy = () => {\n unsubscribe();\n originalDestroy();\n };\n }\n\n return store;\n };\n\n/**\n * Hook to access sync state for a Zustand store\n *\n * @example\n * ```typescript\n * import { useSyncState } from 'electron-state-sync/zustand';\n *\n * function App() {\n * const { state, isSynced } = useSyncState({ name: 'counter' });\n * return <div>{state?.count}</div>;\n * }\n * ```\n */\nexport const useSyncState = <State extends Record<string, unknown>>(\n options: SyncStateZustandOptions,\n): { state: State | undefined; isSynced: boolean } => {\n const [isSynced, setIsSynced] = useState(false);\n const [state, setState] = useState<State | undefined>();\n\n const globalConfig = getGlobalConfig();\n const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);\n const channelOptions = useMemo(\n () => createChannelOptions(options),\n [options.baseChannel, options.name],\n );\n\n useEffect(() => {\n const unsubscribe = bridge.subscribe<State>(channelOptions, (value) => {\n setState(value);\n setIsSynced(true);\n });\n\n void bridge.get<State>(channelOptions).then((value) => {\n setState(value);\n setIsSynced(true);\n });\n\n return () => {\n unsubscribe();\n };\n }, [bridge, channelOptions]);\n\n return { isSynced, state };\n};\n\nexport { useSyncState as useZustandSyncState };\n\n/**\n * Create a synced Zustand store (hook-less version for vanilla JS)\n *\n * @example\n * ```typescript\n * import { createSyncedStore } from 'electron-state-sync/zustand';\n *\n * const store = createSyncedStore(\n * { count: 0 },\n * { name: 'counter' },\n * (set) => ({\n * increment: () => set((state) => ({ count: state.count + 1 }))\n * })\n * );\n * ```\n */\nexport const createSyncedStore = <State extends Record<string, unknown>>(\n initialState: State,\n options: SyncStateZustandOptions,\n): StoreApi<State> => {\n const globalConfig = getGlobalConfig();\n const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);\n const channelOptions = createChannelOptions(options);\n\n // Track remote updates to prevent infinite loops\n let isApplyingRemoteValue = false;\n\n const store = createStore<State>()(() => initialState);\n\n // Wrap setState to intercept local updates\n const originalSetState = store.setState.bind(store);\n store.setState = (partial: Partial<State> | ((state: State) => State), replace?: boolean) => {\n const currentState = store.getState();\n\n // Check if this is a remote update\n if (isApplyingRemoteValue) {\n isApplyingRemoteValue = false;\n return originalSetState(partial, replace);\n }\n\n // Local update - sync to main process\n let nextState: State;\n if (typeof partial === \"function\") {\n nextState = (partial as (state: State) => State)(currentState);\n } else if (replace) {\n nextState = partial as State;\n } else {\n nextState = { ...currentState, ...partial } as State;\n }\n\n // Extract only data properties (exclude functions) for syncing to main process\n const dataOnlyState = Object.fromEntries(\n Object.entries(nextState as Record<string, unknown>).filter(\n ([_, v]) => typeof v !== \"function\",\n ),\n ) as State;\n\n void bridge.set(channelOptions, dataOnlyState);\n return originalSetState(partial, replace);\n };\n\n // 使用包装后的 setState 应用远程更新\n const applyRemoteValue = (value: State): void => {\n isApplyingRemoteValue = true;\n // If value is a primitive or the store state has nested structure,\n // merge it appropriately to preserve actions\n const currentState = store.getState();\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n // Value is an object, merge it\n store.setState({ ...currentState, ...value } as State);\n } else {\n // Value is a primitive, assume it should be nested under a property\n // Try to infer the property name from current state structure\n const keys = Object.keys(currentState as Record<string, unknown>).filter(\n (k) => typeof (currentState as Record<string, unknown>)[k] !== \"function\",\n );\n if (keys.length === 1) {\n store.setState({ ...currentState, [keys[0]]: value } as State);\n } else {\n // Fallback: replace with value\n store.setState(value as Partial<State>);\n }\n }\n };\n\n // Subscribe to remote updates from main process\n const unsubscribe = bridge.subscribe<State>(channelOptions, applyRemoteValue);\n\n // Initialize from main process\n void bridge.get<State>(channelOptions).then(applyRemoteValue);\n\n // Store unsubscribe for cleanup\n const storeWithCleanup = store as unknown as { destroy?: () => void };\n if (storeWithCleanup.destroy) {\n const originalDestroy = storeWithCleanup.destroy;\n storeWithCleanup.destroy = () => {\n unsubscribe();\n originalDestroy();\n };\n } else {\n storeWithCleanup.destroy = () => {\n unsubscribe();\n };\n }\n\n return store;\n};\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "electron-state-sync",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Electron main/renderer process state synchronization library",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"electron",
|
|
8
|
+
"state",
|
|
9
|
+
"sync",
|
|
10
|
+
"ipc",
|
|
11
|
+
"renderer",
|
|
12
|
+
"react",
|
|
13
|
+
"preact",
|
|
14
|
+
"vue",
|
|
15
|
+
"svelte",
|
|
16
|
+
"solid",
|
|
17
|
+
"zustand",
|
|
18
|
+
"react-query",
|
|
19
|
+
"tanstack-query",
|
|
20
|
+
"jotai",
|
|
21
|
+
"redux",
|
|
22
|
+
"redux-toolkit",
|
|
23
|
+
"state-management"
|
|
24
|
+
],
|
|
25
|
+
"author": "abramdev",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/abramdev/electron-state-sync.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/abramdev/electron-state-sync#readme",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/abramdev/electron-state-sync/issues"
|
|
34
|
+
},
|
|
35
|
+
"exports": {
|
|
36
|
+
"./main": {
|
|
37
|
+
"types": "./dist/main.d.ts",
|
|
38
|
+
"import": "./dist/main.js",
|
|
39
|
+
"require": "./dist/main.cjs",
|
|
40
|
+
"source": "./src/main.ts"
|
|
41
|
+
},
|
|
42
|
+
"./preload": {
|
|
43
|
+
"types": "./dist/preload.d.ts",
|
|
44
|
+
"import": "./dist/preload.js",
|
|
45
|
+
"require": "./dist/preload.cjs",
|
|
46
|
+
"source": "./src/preload.ts"
|
|
47
|
+
},
|
|
48
|
+
"./renderer": {
|
|
49
|
+
"types": "./dist/renderer/index.d.ts",
|
|
50
|
+
"import": "./dist/renderer/index.js",
|
|
51
|
+
"require": "./dist/renderer/index.cjs",
|
|
52
|
+
"source": "./src/renderer/index.ts"
|
|
53
|
+
},
|
|
54
|
+
"./react": {
|
|
55
|
+
"types": "./dist/react.d.ts",
|
|
56
|
+
"import": "./dist/react.js",
|
|
57
|
+
"require": "./dist/react.cjs",
|
|
58
|
+
"source": "./src/renderer/react.ts"
|
|
59
|
+
},
|
|
60
|
+
"./preact": {
|
|
61
|
+
"types": "./dist/preact.d.ts",
|
|
62
|
+
"import": "./dist/preact.js",
|
|
63
|
+
"require": "./dist/preact.cjs",
|
|
64
|
+
"source": "./src/renderer/preact.ts"
|
|
65
|
+
},
|
|
66
|
+
"./vue": {
|
|
67
|
+
"types": "./dist/vue.d.ts",
|
|
68
|
+
"import": "./dist/vue.js",
|
|
69
|
+
"require": "./dist/vue.cjs",
|
|
70
|
+
"source": "./src/renderer/vue.ts"
|
|
71
|
+
},
|
|
72
|
+
"./svelte": {
|
|
73
|
+
"types": "./dist/svelte.d.ts",
|
|
74
|
+
"import": "./dist/svelte.js",
|
|
75
|
+
"require": "./dist/svelte.cjs",
|
|
76
|
+
"source": "./src/renderer/svelte.ts"
|
|
77
|
+
},
|
|
78
|
+
"./solid": {
|
|
79
|
+
"types": "./dist/solid.d.ts",
|
|
80
|
+
"import": "./dist/solid.js",
|
|
81
|
+
"require": "./dist/solid.cjs",
|
|
82
|
+
"source": "./src/renderer/solid.ts"
|
|
83
|
+
},
|
|
84
|
+
"./zustand": {
|
|
85
|
+
"types": "./dist/zustand.d.ts",
|
|
86
|
+
"import": "./dist/zustand.js",
|
|
87
|
+
"require": "./dist/zustand.cjs",
|
|
88
|
+
"source": "./src/renderer/zustand.ts"
|
|
89
|
+
},
|
|
90
|
+
"./react-query": {
|
|
91
|
+
"types": "./dist/react-query.d.ts",
|
|
92
|
+
"import": "./dist/react-query.js",
|
|
93
|
+
"require": "./dist/react-query.cjs",
|
|
94
|
+
"source": "./src/renderer/react-query.ts"
|
|
95
|
+
},
|
|
96
|
+
"./jotai": {
|
|
97
|
+
"types": "./dist/jotai.d.ts",
|
|
98
|
+
"import": "./dist/jotai.js",
|
|
99
|
+
"require": "./dist/jotai.cjs",
|
|
100
|
+
"source": "./src/renderer/jotai.ts"
|
|
101
|
+
},
|
|
102
|
+
"./redux": {
|
|
103
|
+
"types": "./dist/redux.d.ts",
|
|
104
|
+
"import": "./dist/redux.js",
|
|
105
|
+
"require": "./dist/redux.cjs",
|
|
106
|
+
"source": "./src/renderer/redux.ts"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"scripts": {
|
|
110
|
+
"prepublishOnly": "bun run build",
|
|
111
|
+
"dev": "bun run src/index.ts",
|
|
112
|
+
"test": "bun test test",
|
|
113
|
+
"coverage": "bun test test --coverage",
|
|
114
|
+
"test:e2e": "bun run build && node ./node_modules/@playwright/test/cli.js test",
|
|
115
|
+
"build:main": "tsdown -c tsdown.main.config.ts",
|
|
116
|
+
"build:preload": "tsdown -c tsdown.preload.config.ts",
|
|
117
|
+
"build:renderer": "tsdown -c tsdown.config.ts",
|
|
118
|
+
"build": "bun run build:main && bun run build:preload && bun run build:renderer",
|
|
119
|
+
"lint": "oxlint src",
|
|
120
|
+
"lint:fix": "oxlint --fix src",
|
|
121
|
+
"format": "oxfmt src",
|
|
122
|
+
"format:check": "oxfmt --check src",
|
|
123
|
+
"check": "bun run lint && bun run format:check"
|
|
124
|
+
},
|
|
125
|
+
"devDependencies": {
|
|
126
|
+
"@playwright/test": "^1.57.0",
|
|
127
|
+
"@types/bun": "latest",
|
|
128
|
+
"@types/node": "^25.0.7",
|
|
129
|
+
"@types/react": "^19.2.8",
|
|
130
|
+
"electron": "^39.2.7",
|
|
131
|
+
"lefthook": "^2.0.15",
|
|
132
|
+
"oxfmt": "^0.24.0",
|
|
133
|
+
"oxlint": "^1.39.0",
|
|
134
|
+
"preact": "^10.25.1",
|
|
135
|
+
"react": "^19.2.3",
|
|
136
|
+
"react-dom": "^19.2.3",
|
|
137
|
+
"solid-js": "^1.9.10",
|
|
138
|
+
"svelte": "^5.46.3",
|
|
139
|
+
"zustand": "^4.5.0",
|
|
140
|
+
"@tanstack/react-query": "^5.0.0",
|
|
141
|
+
"jotai": "^2.0.0",
|
|
142
|
+
"@reduxjs/toolkit": "^2.0.0",
|
|
143
|
+
"react-redux": "^9.0.0",
|
|
144
|
+
"tsdown": "^0.20.0-beta.1",
|
|
145
|
+
"tsx": "^4.21.0",
|
|
146
|
+
"typescript": "^5.9.3",
|
|
147
|
+
"vue": "^3.5.26"
|
|
148
|
+
},
|
|
149
|
+
"peerDependencies": {
|
|
150
|
+
"electron": "^18.0.0",
|
|
151
|
+
"preact": "^10.0.0",
|
|
152
|
+
"react": "^18.0.0",
|
|
153
|
+
"solid-js": "^1.0.0",
|
|
154
|
+
"svelte": "^3.0.0",
|
|
155
|
+
"vue": "^3.0.0",
|
|
156
|
+
"zustand": "^4.0.0",
|
|
157
|
+
"@tanstack/react-query": "^5.0.0",
|
|
158
|
+
"jotai": "^2.0.0",
|
|
159
|
+
"@reduxjs/toolkit": "^2.0.0",
|
|
160
|
+
"react-redux": "^9.0.0"
|
|
161
|
+
}
|
|
162
|
+
}
|