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
@@ -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"}
@@ -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
+ }