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/bunfig.toml ADDED
@@ -0,0 +1,7 @@
1
+ [test]
2
+ # Only run tests in the test/ directory, exclude e2e
3
+ coverage = false
4
+ coverageDir = "coverage"
5
+ coverageThreshold = { "global": { "branches": 0, "functions": 0, "lines": 0, "statements": 0 } }
6
+ preload = ["./test/setup.ts"]
7
+ root = "./test"
package/dist/jotai.cjs ADDED
@@ -0,0 +1,106 @@
1
+ const require_renderer = require('./renderer-D3YziJ_U.cjs');
2
+ let react = require("react");
3
+ let jotai = require("jotai");
4
+
5
+ //#region src/renderer/jotai.ts
6
+ const createChannelOptions = (options) => {
7
+ const globalConfig = require_renderer.getGlobalConfig();
8
+ return {
9
+ baseChannel: options.baseChannel ?? globalConfig.baseChannel,
10
+ name: options.name
11
+ };
12
+ };
13
+ const syncedAtoms = /* @__PURE__ */ new Map();
14
+ /**
15
+ * Create a synced atom that synchronizes with Electron main process
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { syncStateAtom, useAtom } from 'electron-state-sync/jotai';
20
+ *
21
+ * const countAtom = syncStateAtom(0, { name: 'counter' });
22
+ *
23
+ * function App() {
24
+ * const [count, setCount] = useAtom(countAtom);
25
+ * return <div onClick={() => setCount(5)}>{count}</div>;
26
+ * }
27
+ * ```
28
+ */
29
+ const syncStateAtom = (initialValue, options) => {
30
+ const globalConfig = require_renderer.getGlobalConfig();
31
+ const bridge = require_renderer.resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
32
+ const channelOptions = createChannelOptions(options);
33
+ const baseAtom = (0, jotai.atom)(initialValue);
34
+ const wrapper = { atom: baseAtom };
35
+ const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;
36
+ syncedAtoms.set(atomKey, wrapper);
37
+ let pendingRemoteValue = initialValue;
38
+ let hasPendingRemoteValue = false;
39
+ const applyRemoteValue = (value) => {
40
+ if (wrapper.setAtom) {
41
+ wrapper.setAtom(value);
42
+ return;
43
+ }
44
+ pendingRemoteValue = value;
45
+ hasPendingRemoteValue = true;
46
+ };
47
+ baseAtom.onMount = (setAtom) => {
48
+ wrapper.setAtom = setAtom;
49
+ if (hasPendingRemoteValue) {
50
+ setAtom(pendingRemoteValue);
51
+ hasPendingRemoteValue = false;
52
+ }
53
+ return () => {
54
+ wrapper.setAtom = void 0;
55
+ };
56
+ };
57
+ wrapper.unsubscribe = bridge.subscribe(channelOptions, applyRemoteValue);
58
+ bridge.get(channelOptions).then(applyRemoteValue);
59
+ return (0, jotai.atom)((get) => get(baseAtom), (get, set, value) => {
60
+ const currentValue = get(baseAtom);
61
+ const nextValue = typeof value === "function" ? value(currentValue) : value;
62
+ set(baseAtom, nextValue);
63
+ bridge.set(channelOptions, nextValue);
64
+ });
65
+ };
66
+ /**
67
+ * Hook to access sync status for a synced atom
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * import { useSyncStateStatus } from 'electron-state-sync/jotai';
72
+ *
73
+ * function App() {
74
+ * const { isSynced } = useSyncStateStatus({ name: 'counter' });
75
+ * return isSynced ? <div>Synced</div> : <div>Loading...</div>;
76
+ * }
77
+ * ```
78
+ */
79
+ const useSyncStateStatus = (options) => {
80
+ const [isSynced, setIsSynced] = (0, react.useState)(false);
81
+ const globalConfig = require_renderer.getGlobalConfig();
82
+ const bridge = require_renderer.resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
83
+ const channelOptions = (0, react.useMemo)(() => createChannelOptions(options), [options.baseChannel, options.name]);
84
+ (0, react.useEffect)(() => {
85
+ bridge.get(channelOptions).then(() => {
86
+ setIsSynced(true);
87
+ });
88
+ }, [bridge, channelOptions]);
89
+ return { isSynced };
90
+ };
91
+ /**
92
+ * Cleanup function for synced atoms
93
+ *
94
+ * Call this when unmounting components to prevent memory leaks
95
+ */
96
+ const cleanupSyncedAtom = (options) => {
97
+ const channelOptions = createChannelOptions(options);
98
+ const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;
99
+ syncedAtoms.get(atomKey)?.unsubscribe?.();
100
+ syncedAtoms.delete(atomKey);
101
+ };
102
+
103
+ //#endregion
104
+ exports.cleanupSyncedAtom = cleanupSyncedAtom;
105
+ exports.syncStateAtom = syncStateAtom;
106
+ exports.useSyncStateStatus = useSyncStateStatus;
@@ -0,0 +1,48 @@
1
+ import { a as SyncStateChannelOptions, t as SyncStateBridge } from "./types-C18dHgLI.cjs";
2
+ import { PrimitiveAtom } from "jotai";
3
+
4
+ //#region src/renderer/jotai.d.ts
5
+ interface SyncStateJotaiOptions extends SyncStateChannelOptions {
6
+ bridge?: SyncStateBridge;
7
+ }
8
+ /**
9
+ * Create a synced atom that synchronizes with Electron main process
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { syncStateAtom, useAtom } from 'electron-state-sync/jotai';
14
+ *
15
+ * const countAtom = syncStateAtom(0, { name: 'counter' });
16
+ *
17
+ * function App() {
18
+ * const [count, setCount] = useAtom(countAtom);
19
+ * return <div onClick={() => setCount(5)}>{count}</div>;
20
+ * }
21
+ * ```
22
+ */
23
+ declare const syncStateAtom: <StateValue>(initialValue: StateValue, options: SyncStateJotaiOptions) => PrimitiveAtom<StateValue>;
24
+ /**
25
+ * Hook to access sync status for a synced atom
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * import { useSyncStateStatus } from 'electron-state-sync/jotai';
30
+ *
31
+ * function App() {
32
+ * const { isSynced } = useSyncStateStatus({ name: 'counter' });
33
+ * return isSynced ? <div>Synced</div> : <div>Loading...</div>;
34
+ * }
35
+ * ```
36
+ */
37
+ declare const useSyncStateStatus: (options: SyncStateJotaiOptions) => {
38
+ isSynced: boolean;
39
+ };
40
+ /**
41
+ * Cleanup function for synced atoms
42
+ *
43
+ * Call this when unmounting components to prevent memory leaks
44
+ */
45
+ declare const cleanupSyncedAtom: (options: SyncStateJotaiOptions) => void;
46
+ //#endregion
47
+ export { SyncStateJotaiOptions, cleanupSyncedAtom, syncStateAtom, useSyncStateStatus };
48
+ //# sourceMappingURL=jotai.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jotai.d.cts","names":[],"sources":["../src/renderer/jotai.ts"],"sourcesContent":[],"mappings":";;;;UAQiB,qBAAA,SAA8B;EAA9B,MAAA,CAAA,EACN,eAD4B;AAsCvC;;;;;;AAoFA;AAuBA;;;;;;;;;cA3Ga,0CACG,qBACL,0BACR,cAAc;;;;;;;;;;;;;;cAiFJ,8BAA+B;;;;;;;;cAuB/B,6BAA8B"}
@@ -0,0 +1,48 @@
1
+ import { a as SyncStateChannelOptions, t as SyncStateBridge } from "./types-7wPPX0ty.js";
2
+ import { PrimitiveAtom } from "jotai";
3
+
4
+ //#region src/renderer/jotai.d.ts
5
+ interface SyncStateJotaiOptions extends SyncStateChannelOptions {
6
+ bridge?: SyncStateBridge;
7
+ }
8
+ /**
9
+ * Create a synced atom that synchronizes with Electron main process
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { syncStateAtom, useAtom } from 'electron-state-sync/jotai';
14
+ *
15
+ * const countAtom = syncStateAtom(0, { name: 'counter' });
16
+ *
17
+ * function App() {
18
+ * const [count, setCount] = useAtom(countAtom);
19
+ * return <div onClick={() => setCount(5)}>{count}</div>;
20
+ * }
21
+ * ```
22
+ */
23
+ declare const syncStateAtom: <StateValue>(initialValue: StateValue, options: SyncStateJotaiOptions) => PrimitiveAtom<StateValue>;
24
+ /**
25
+ * Hook to access sync status for a synced atom
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * import { useSyncStateStatus } from 'electron-state-sync/jotai';
30
+ *
31
+ * function App() {
32
+ * const { isSynced } = useSyncStateStatus({ name: 'counter' });
33
+ * return isSynced ? <div>Synced</div> : <div>Loading...</div>;
34
+ * }
35
+ * ```
36
+ */
37
+ declare const useSyncStateStatus: (options: SyncStateJotaiOptions) => {
38
+ isSynced: boolean;
39
+ };
40
+ /**
41
+ * Cleanup function for synced atoms
42
+ *
43
+ * Call this when unmounting components to prevent memory leaks
44
+ */
45
+ declare const cleanupSyncedAtom: (options: SyncStateJotaiOptions) => void;
46
+ //#endregion
47
+ export { SyncStateJotaiOptions, cleanupSyncedAtom, syncStateAtom, useSyncStateStatus };
48
+ //# sourceMappingURL=jotai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jotai.d.ts","names":[],"sources":["../src/renderer/jotai.ts"],"sourcesContent":[],"mappings":";;;;UAQiB,qBAAA,SAA8B;EAA9B,MAAA,CAAA,EACN,eAD4B;AAsCvC;;;;;;AAoFA;AAuBA;;;;;;;;;cA3Ga,0CACG,qBACL,0BACR,cAAc;;;;;;;;;;;;;;cAiFJ,8BAA+B;;;;;;;;cAuB/B,6BAA8B"}
package/dist/jotai.js ADDED
@@ -0,0 +1,105 @@
1
+ import { a as resolveSyncStateBridge, t as getGlobalConfig } from "./renderer-C7zF3UQm.js";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { atom } from "jotai";
4
+
5
+ //#region src/renderer/jotai.ts
6
+ const createChannelOptions = (options) => {
7
+ const globalConfig = getGlobalConfig();
8
+ return {
9
+ baseChannel: options.baseChannel ?? globalConfig.baseChannel,
10
+ name: options.name
11
+ };
12
+ };
13
+ const syncedAtoms = /* @__PURE__ */ new Map();
14
+ /**
15
+ * Create a synced atom that synchronizes with Electron main process
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { syncStateAtom, useAtom } from 'electron-state-sync/jotai';
20
+ *
21
+ * const countAtom = syncStateAtom(0, { name: 'counter' });
22
+ *
23
+ * function App() {
24
+ * const [count, setCount] = useAtom(countAtom);
25
+ * return <div onClick={() => setCount(5)}>{count}</div>;
26
+ * }
27
+ * ```
28
+ */
29
+ const syncStateAtom = (initialValue, options) => {
30
+ const globalConfig = getGlobalConfig();
31
+ const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
32
+ const channelOptions = createChannelOptions(options);
33
+ const baseAtom = atom(initialValue);
34
+ const wrapper = { atom: baseAtom };
35
+ const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;
36
+ syncedAtoms.set(atomKey, wrapper);
37
+ let pendingRemoteValue = initialValue;
38
+ let hasPendingRemoteValue = false;
39
+ const applyRemoteValue = (value) => {
40
+ if (wrapper.setAtom) {
41
+ wrapper.setAtom(value);
42
+ return;
43
+ }
44
+ pendingRemoteValue = value;
45
+ hasPendingRemoteValue = true;
46
+ };
47
+ baseAtom.onMount = (setAtom) => {
48
+ wrapper.setAtom = setAtom;
49
+ if (hasPendingRemoteValue) {
50
+ setAtom(pendingRemoteValue);
51
+ hasPendingRemoteValue = false;
52
+ }
53
+ return () => {
54
+ wrapper.setAtom = void 0;
55
+ };
56
+ };
57
+ wrapper.unsubscribe = bridge.subscribe(channelOptions, applyRemoteValue);
58
+ bridge.get(channelOptions).then(applyRemoteValue);
59
+ return atom((get) => get(baseAtom), (get, set, value) => {
60
+ const currentValue = get(baseAtom);
61
+ const nextValue = typeof value === "function" ? value(currentValue) : value;
62
+ set(baseAtom, nextValue);
63
+ bridge.set(channelOptions, nextValue);
64
+ });
65
+ };
66
+ /**
67
+ * Hook to access sync status for a synced atom
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * import { useSyncStateStatus } from 'electron-state-sync/jotai';
72
+ *
73
+ * function App() {
74
+ * const { isSynced } = useSyncStateStatus({ name: 'counter' });
75
+ * return isSynced ? <div>Synced</div> : <div>Loading...</div>;
76
+ * }
77
+ * ```
78
+ */
79
+ const useSyncStateStatus = (options) => {
80
+ const [isSynced, setIsSynced] = useState(false);
81
+ const globalConfig = getGlobalConfig();
82
+ const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
83
+ const channelOptions = useMemo(() => createChannelOptions(options), [options.baseChannel, options.name]);
84
+ useEffect(() => {
85
+ bridge.get(channelOptions).then(() => {
86
+ setIsSynced(true);
87
+ });
88
+ }, [bridge, channelOptions]);
89
+ return { isSynced };
90
+ };
91
+ /**
92
+ * Cleanup function for synced atoms
93
+ *
94
+ * Call this when unmounting components to prevent memory leaks
95
+ */
96
+ const cleanupSyncedAtom = (options) => {
97
+ const channelOptions = createChannelOptions(options);
98
+ const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;
99
+ syncedAtoms.get(atomKey)?.unsubscribe?.();
100
+ syncedAtoms.delete(atomKey);
101
+ };
102
+
103
+ //#endregion
104
+ export { cleanupSyncedAtom, syncStateAtom, useSyncStateStatus };
105
+ //# sourceMappingURL=jotai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jotai.js","names":[],"sources":["../src/renderer/jotai.ts"],"sourcesContent":["import { atom, type Getter, type PrimitiveAtom, type Setter } from \"jotai\";\nimport { useEffect, useMemo, useState } from \"react\";\n\nimport type { SyncStateChannelOptions } from \"../channels\";\nimport type { SyncStateBridge } from \"../types\";\nimport { resolveSyncStateBridge } from \"./bridge\";\nimport { getGlobalConfig } from \"./index\";\n\nexport interface SyncStateJotaiOptions extends SyncStateChannelOptions {\n bridge?: SyncStateBridge;\n}\n\ninterface SyncedAtomWrapper<StateValue> {\n atom: PrimitiveAtom<StateValue>;\n // 保存基础 atom 的 setter,用于应用远程更新\n setAtom?: (value: StateValue) => void;\n // 记录订阅的清理函数,供外部释放\n unsubscribe?: () => void;\n}\n\nconst createChannelOptions = (options: SyncStateJotaiOptions): SyncStateChannelOptions => {\n const globalConfig = getGlobalConfig();\n return {\n baseChannel: options.baseChannel ?? globalConfig.baseChannel,\n name: options.name,\n };\n};\n\n// Store synced atoms for cleanup\nconst syncedAtoms = new Map<string, SyncedAtomWrapper<unknown>>();\n\n/**\n * Create a synced atom that synchronizes with Electron main process\n *\n * @example\n * ```typescript\n * import { syncStateAtom, useAtom } from 'electron-state-sync/jotai';\n *\n * const countAtom = syncStateAtom(0, { name: 'counter' });\n *\n * function App() {\n * const [count, setCount] = useAtom(countAtom);\n * return <div onClick={() => setCount(5)}>{count}</div>;\n * }\n * ```\n */\nexport const syncStateAtom = <StateValue>(\n initialValue: StateValue,\n options: SyncStateJotaiOptions,\n): PrimitiveAtom<StateValue> => {\n const globalConfig = getGlobalConfig();\n const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);\n const channelOptions = createChannelOptions(options);\n\n // Create wrapper to track sync state\n const baseAtom = atom(initialValue);\n const wrapper: SyncedAtomWrapper<StateValue> = {\n atom: baseAtom,\n };\n\n // Store for cleanup\n const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;\n syncedAtoms.set(atomKey, wrapper as SyncedAtomWrapper<unknown>);\n\n // 待应用的远程值(atom 挂载前缓存)\n let pendingRemoteValue = initialValue;\n // 是否存在待处理的远程更新\n let hasPendingRemoteValue = false;\n\n // 应用主进程的远程更新\n const applyRemoteValue = (value: StateValue): void => {\n if (wrapper.setAtom) {\n wrapper.setAtom(value);\n return;\n }\n pendingRemoteValue = value;\n hasPendingRemoteValue = true;\n };\n\n // 基础 atom 挂载时,补发缓存的远程值\n baseAtom.onMount = (setAtom) => {\n wrapper.setAtom = setAtom;\n if (hasPendingRemoteValue) {\n setAtom(pendingRemoteValue);\n hasPendingRemoteValue = false;\n }\n return () => {\n wrapper.setAtom = undefined;\n };\n };\n\n // Subscribe to remote updates from main process\n const unsubscribe = bridge.subscribe<StateValue>(channelOptions, applyRemoteValue);\n wrapper.unsubscribe = unsubscribe;\n\n // Initialize from main process\n void bridge.get<StateValue>(channelOptions).then(applyRemoteValue);\n\n // Create read-write atom with sync logic\n const syncedAtom = atom(\n (get: Getter) => get(baseAtom),\n (get: Getter, set: Setter, value: StateValue | ((prev: StateValue) => StateValue)) => {\n const currentValue = get(baseAtom);\n const nextValue =\n typeof value === \"function\"\n ? (value as (prev: StateValue) => StateValue)(currentValue)\n : value;\n\n // Local update - sync to main process\n set(baseAtom, nextValue);\n void bridge.set(channelOptions, nextValue);\n },\n );\n\n return syncedAtom as PrimitiveAtom<StateValue>;\n};\n\n/**\n * Hook to access sync status for a synced atom\n *\n * @example\n * ```typescript\n * import { useSyncStateStatus } from 'electron-state-sync/jotai';\n *\n * function App() {\n * const { isSynced } = useSyncStateStatus({ name: 'counter' });\n * return isSynced ? <div>Synced</div> : <div>Loading...</div>;\n * }\n * ```\n */\nexport const useSyncStateStatus = (options: SyncStateJotaiOptions): { isSynced: boolean } => {\n const [isSynced, setIsSynced] = useState(false);\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 void bridge.get(channelOptions).then(() => {\n setIsSynced(true);\n });\n }, [bridge, channelOptions]);\n\n return { isSynced };\n};\n\n/**\n * Cleanup function for synced atoms\n *\n * Call this when unmounting components to prevent memory leaks\n */\nexport const cleanupSyncedAtom = (options: SyncStateJotaiOptions): void => {\n const channelOptions = createChannelOptions(options);\n const atomKey = `${channelOptions.baseChannel}:${channelOptions.name}`;\n // 清理订阅避免内存泄漏\n const wrapper = syncedAtoms.get(atomKey);\n wrapper?.unsubscribe?.();\n syncedAtoms.delete(atomKey);\n};\n"],"mappings":";;;;;AAoBA,MAAM,wBAAwB,YAA4D;CACxF,MAAM,eAAe,iBAAiB;AACtC,QAAO;EACL,aAAa,QAAQ,eAAe,aAAa;EACjD,MAAM,QAAQ;EACf;;AAIH,MAAM,8BAAc,IAAI,KAAyC;;;;;;;;;;;;;;;;AAiBjE,MAAa,iBACX,cACA,YAC8B;CAC9B,MAAM,eAAe,iBAAiB;CACtC,MAAM,SAAS,uBAAuB,QAAQ,UAAU,aAAa,OAAO;CAC5E,MAAM,iBAAiB,qBAAqB,QAAQ;CAGpD,MAAM,WAAW,KAAK,aAAa;CACnC,MAAM,UAAyC,EAC7C,MAAM,UACP;CAGD,MAAM,UAAU,GAAG,eAAe,YAAY,GAAG,eAAe;AAChE,aAAY,IAAI,SAAS,QAAsC;CAG/D,IAAI,qBAAqB;CAEzB,IAAI,wBAAwB;CAG5B,MAAM,oBAAoB,UAA4B;AACpD,MAAI,QAAQ,SAAS;AACnB,WAAQ,QAAQ,MAAM;AACtB;;AAEF,uBAAqB;AACrB,0BAAwB;;AAI1B,UAAS,WAAW,YAAY;AAC9B,UAAQ,UAAU;AAClB,MAAI,uBAAuB;AACzB,WAAQ,mBAAmB;AAC3B,2BAAwB;;AAE1B,eAAa;AACX,WAAQ,UAAU;;;AAMtB,SAAQ,cADY,OAAO,UAAsB,gBAAgB,iBAAiB;AAIlF,CAAK,OAAO,IAAgB,eAAe,CAAC,KAAK,iBAAiB;AAkBlE,QAfmB,MAChB,QAAgB,IAAI,SAAS,GAC7B,KAAa,KAAa,UAA2D;EACpF,MAAM,eAAe,IAAI,SAAS;EAClC,MAAM,YACJ,OAAO,UAAU,aACZ,MAA2C,aAAa,GACzD;AAGN,MAAI,UAAU,UAAU;AACxB,EAAK,OAAO,IAAI,gBAAgB,UAAU;GAE7C;;;;;;;;;;;;;;;AAkBH,MAAa,sBAAsB,YAA0D;CAC3F,MAAM,CAAC,UAAU,eAAe,SAAS,MAAM;CAC/C,MAAM,eAAe,iBAAiB;CACtC,MAAM,SAAS,uBAAuB,QAAQ,UAAU,aAAa,OAAO;CAC5E,MAAM,iBAAiB,cACf,qBAAqB,QAAQ,EACnC,CAAC,QAAQ,aAAa,QAAQ,KAAK,CACpC;AAED,iBAAgB;AACd,EAAK,OAAO,IAAI,eAAe,CAAC,WAAW;AACzC,eAAY,KAAK;IACjB;IACD,CAAC,QAAQ,eAAe,CAAC;AAE5B,QAAO,EAAE,UAAU;;;;;;;AAQrB,MAAa,qBAAqB,YAAyC;CACzE,MAAM,iBAAiB,qBAAqB,QAAQ;CACpD,MAAM,UAAU,GAAG,eAAe,YAAY,GAAG,eAAe;AAGhE,CADgB,YAAY,IAAI,QAAQ,EAC/B,eAAe;AACxB,aAAY,OAAO,QAAQ"}
package/dist/main.cjs ADDED
@@ -0,0 +1,177 @@
1
+ let electron = require("electron");
2
+
3
+ //#region src/channels.ts
4
+ const createSyncStateChannels = (options) => {
5
+ const channelPrefix = `${options.baseChannel ?? "state"}:${options.name}`;
6
+ return {
7
+ getChannel: `${channelPrefix}:get`,
8
+ setChannel: `${channelPrefix}:set`,
9
+ subscribeChannel: `${channelPrefix}:subscribe`,
10
+ unsubscribeChannel: `${channelPrefix}:unsubscribe`,
11
+ updateChannel: `${channelPrefix}:update`
12
+ };
13
+ };
14
+
15
+ //#endregion
16
+ //#region src/types.ts
17
+ var SyncStateError = class SyncStateError extends Error {
18
+ code;
19
+ stateName;
20
+ baseChannel;
21
+ cause;
22
+ constructor(code, message, context) {
23
+ super(message);
24
+ this.code = code;
25
+ this.name = "SyncStateError";
26
+ this.stateName = context?.stateName;
27
+ this.baseChannel = context?.baseChannel;
28
+ this.cause = context?.cause;
29
+ if (Error.captureStackTrace) Error.captureStackTrace(this, SyncStateError);
30
+ }
31
+ getFullMessage() {
32
+ const parts = [this.message];
33
+ if (this.stateName) parts.push(`\n State name: "${this.stateName}"`);
34
+ if (this.baseChannel) parts.push(`\n Channel prefix: "${this.baseChannel}"`);
35
+ if (this.cause) parts.push(`\n Original error: ${this.cause.message}`);
36
+ return parts.join("");
37
+ }
38
+ };
39
+
40
+ //#endregion
41
+ //#region src/main.ts
42
+ let globalConfig = {};
43
+ const initSyncStateMain = (config) => {
44
+ globalConfig = { ...config };
45
+ };
46
+ const createReadonlyErrorMessage = (stateName, baseChannel) => `syncState renderer write is disabled by main process. State "${stateName}" has channel prefix "${baseChannel}". Please set allowRendererSet: true in main process to enable renderer write.`;
47
+ const createInvalidValueErrorMessage = (stateName, baseChannel) => `syncState renderer write value validation failed. State "${stateName}" has channel prefix "${baseChannel}". Please check if the value conforms to resolveRendererValue validation rules.`;
48
+ const INVALID_INITIAL_VALUE_WARNING_MESSAGE = "syncState Warning: Initial value contains non-serializable content (such as functions, Symbols, BigInts, etc.), which will prevent the value from being transmitted to renderer process via IPC";
49
+ const checkType = (val) => {
50
+ if (val === null || val === void 0) return true;
51
+ if (typeof val === "string") return true;
52
+ if (typeof val === "number") return true;
53
+ if (typeof val === "boolean") return true;
54
+ if (typeof val === "function") return false;
55
+ if (typeof val === "symbol") return false;
56
+ if (typeof val === "bigint") return false;
57
+ if (Array.isArray(val)) return val.every((item) => checkType(item));
58
+ if (typeof val === "object") try {
59
+ JSON.stringify(val);
60
+ return Object.values(val).every((item) => checkType(item));
61
+ } catch {
62
+ return false;
63
+ }
64
+ return true;
65
+ };
66
+ const validateSerializable = (value) => checkType(value);
67
+ const createSyncStateStore = (initialValue) => {
68
+ let state$1 = initialValue;
69
+ const getState = () => state$1;
70
+ const setState = (value) => {
71
+ state$1 = value;
72
+ };
73
+ return {
74
+ getState,
75
+ setState
76
+ };
77
+ };
78
+ const createSubscriptions = (channels, getState) => {
79
+ const subscribers = /* @__PURE__ */ new Set();
80
+ const broadcast = (value) => {
81
+ for (const subscriber of subscribers) if (subscriber.isDestroyed()) subscribers.delete(subscriber);
82
+ else subscriber.send(channels.updateChannel, value);
83
+ };
84
+ const handleSubscribe = ({ sender }) => {
85
+ subscribers.add(sender);
86
+ sender.once("destroyed", () => {
87
+ subscribers.delete(sender);
88
+ });
89
+ sender.send(channels.updateChannel, getState());
90
+ };
91
+ const handleUnsubscribe = ({ sender }) => {
92
+ subscribers.delete(sender);
93
+ };
94
+ const clear = () => {
95
+ subscribers.clear();
96
+ };
97
+ return {
98
+ broadcast,
99
+ clear,
100
+ handleSubscribe,
101
+ handleUnsubscribe
102
+ };
103
+ };
104
+ const createSetStateAndBroadcast = (setState, broadcast) => (value) => {
105
+ setState(value);
106
+ broadcast(value);
107
+ };
108
+ const createInvokeHandlers = (getState, setStateAndBroadcast, options) => {
109
+ const handleGet = () => getState();
110
+ const ensureRendererWritable = () => {
111
+ if (options.allowRendererSet === false) throw new SyncStateError("RENDERER_READONLY", createReadonlyErrorMessage(options.name, options.baseChannel ?? "state"), {
112
+ baseChannel: options.baseChannel ?? "state",
113
+ stateName: options.name
114
+ });
115
+ };
116
+ const resolveRendererValue = (value) => {
117
+ if (!options.resolveRendererValue) return value;
118
+ try {
119
+ return options.resolveRendererValue(value);
120
+ } catch (error) {
121
+ let message = "";
122
+ let cause = void 0;
123
+ if (error instanceof Error) {
124
+ const { message: errorMessage } = error;
125
+ message = errorMessage;
126
+ cause = error;
127
+ } else message = createInvalidValueErrorMessage(options.name, options.baseChannel ?? "state");
128
+ throw new SyncStateError("RENDERER_INVALID_VALUE", message, {
129
+ baseChannel: options.baseChannel ?? "state",
130
+ cause,
131
+ stateName: options.name
132
+ });
133
+ }
134
+ };
135
+ const handleSet = (_event, value) => {
136
+ ensureRendererWritable();
137
+ setStateAndBroadcast(resolveRendererValue(value));
138
+ };
139
+ return {
140
+ handleGet,
141
+ handleSet
142
+ };
143
+ };
144
+ const registerIpcHandlers = (channels, invokeHandlers, subscriptions) => {
145
+ electron.ipcMain.handle(channels.getChannel, invokeHandlers.handleGet);
146
+ electron.ipcMain.handle(channels.setChannel, invokeHandlers.handleSet);
147
+ electron.ipcMain.on(channels.subscribeChannel, subscriptions.handleSubscribe);
148
+ electron.ipcMain.on(channels.unsubscribeChannel, subscriptions.handleUnsubscribe);
149
+ return () => {
150
+ electron.ipcMain.removeHandler(channels.getChannel);
151
+ electron.ipcMain.removeHandler(channels.setChannel);
152
+ electron.ipcMain.removeListener(channels.subscribeChannel, subscriptions.handleSubscribe);
153
+ electron.ipcMain.removeListener(channels.unsubscribeChannel, subscriptions.handleUnsubscribe);
154
+ subscriptions.clear();
155
+ };
156
+ };
157
+ const state = (options) => {
158
+ const mergedOptions = {
159
+ allowRendererSet: globalConfig.allowRendererSet,
160
+ baseChannel: globalConfig.baseChannel,
161
+ ...options
162
+ };
163
+ if (!validateSerializable(mergedOptions.initialValue)) console.warn(`${INVALID_INITIAL_VALUE_WARNING_MESSAGE}\n State name: "${mergedOptions.name}"`);
164
+ const channels = createSyncStateChannels(mergedOptions);
165
+ const store = createSyncStateStore(mergedOptions.initialValue);
166
+ const subscriptions = createSubscriptions(channels, store.getState);
167
+ const setStateAndBroadcast = createSetStateAndBroadcast(store.setState, subscriptions.broadcast);
168
+ return {
169
+ dispose: registerIpcHandlers(channels, createInvokeHandlers(store.getState, setStateAndBroadcast, mergedOptions), subscriptions),
170
+ get: store.getState,
171
+ set: setStateAndBroadcast
172
+ };
173
+ };
174
+
175
+ //#endregion
176
+ exports.initSyncStateMain = initSyncStateMain;
177
+ exports.state = state;
@@ -0,0 +1,26 @@
1
+ //#region src/channels.d.ts
2
+ interface SyncStateChannelOptions {
3
+ name: string;
4
+ baseChannel?: string;
5
+ }
6
+ //#endregion
7
+ //#region src/main.d.ts
8
+ interface SyncStateMainGlobalConfig {
9
+ baseChannel?: string;
10
+ allowRendererSet?: boolean;
11
+ }
12
+ declare const initSyncStateMain: (config: SyncStateMainGlobalConfig) => void;
13
+ interface SyncStateMainOptions<StateValue> extends SyncStateChannelOptions {
14
+ initialValue: StateValue;
15
+ allowRendererSet?: boolean;
16
+ resolveRendererValue?: (value: StateValue) => StateValue;
17
+ }
18
+ interface SyncStateMainHandle<StateValue> {
19
+ get: () => StateValue;
20
+ set: (value: StateValue) => void;
21
+ dispose: () => void;
22
+ }
23
+ declare const state: <StateValue>(options: SyncStateMainOptions<StateValue>) => SyncStateMainHandle<StateValue>;
24
+ //#endregion
25
+ export { SyncStateMainGlobalConfig, SyncStateMainHandle, SyncStateMainOptions, initSyncStateMain, state };
26
+ //# sourceMappingURL=main.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.cts","names":[],"sources":["../src/channels.ts","../src/main.ts"],"sourcesContent":[],"mappings":";UAAiB,uBAAA;EAAA,IAAA,EAAA,MAAA;;;;;AAAA,UCSA,yBAAA,CDTuB;;;;ACSvB,cAOJ,iBAP6B,EAAA,CAAA,MAAA,EAOA,yBAPA,EAAA,GAAA,IAAA;AAO7B,UAII,oBAJyB,CAAA,UAAA,CAAA,SAIgB,uBAJS,CAAA;EAIlD,YAAA,EACD,UADqB;EACrB,gBAAA,CAAA,EAAA,OAAA;EAEiB,oBAAA,CAAA,EAAA,CAAA,KAAA,EAAA,UAAA,EAAA,GAAe,UAAf;;AAHyB,UAMzC,mBANyC,CAAA,UAAA,CAAA,CAAA;EAAuB,GAAA,EAAA,GAAA,GAOpE,UAPoE;EAMhE,GAAA,EAAA,CAAA,KAAA,EAEF,UAFqB,EAAA,GAAA,IAAA;EAuNvB,OAAA,EA0BZ,GAAA,GAAA,IAAA;;AAzBU,cADE,KACF,EAAA,CAAA,UAAA,CAAA,CAAA,OAAA,EAAA,oBAAA,CAAqB,UAArB,CAAA,EAAA,GACR,mBADQ,CACY,UADZ,CAAA"}
package/dist/main.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ //#region src/channels.d.ts
2
+ interface SyncStateChannelOptions {
3
+ name: string;
4
+ baseChannel?: string;
5
+ }
6
+ //#endregion
7
+ //#region src/main.d.ts
8
+ interface SyncStateMainGlobalConfig {
9
+ baseChannel?: string;
10
+ allowRendererSet?: boolean;
11
+ }
12
+ declare const initSyncStateMain: (config: SyncStateMainGlobalConfig) => void;
13
+ interface SyncStateMainOptions<StateValue> extends SyncStateChannelOptions {
14
+ initialValue: StateValue;
15
+ allowRendererSet?: boolean;
16
+ resolveRendererValue?: (value: StateValue) => StateValue;
17
+ }
18
+ interface SyncStateMainHandle<StateValue> {
19
+ get: () => StateValue;
20
+ set: (value: StateValue) => void;
21
+ dispose: () => void;
22
+ }
23
+ declare const state: <StateValue>(options: SyncStateMainOptions<StateValue>) => SyncStateMainHandle<StateValue>;
24
+ //#endregion
25
+ export { SyncStateMainGlobalConfig, SyncStateMainHandle, SyncStateMainOptions, initSyncStateMain, state };
26
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","names":[],"sources":["../src/channels.ts","../src/main.ts"],"sourcesContent":[],"mappings":";UAAiB,uBAAA;EAAA,IAAA,EAAA,MAAA;;;;;AAAA,UCSA,yBAAA,CDTuB;;;;ACSvB,cAOJ,iBAP6B,EAAA,CAAA,MAAA,EAOA,yBAPA,EAAA,GAAA,IAAA;AAO7B,UAII,oBAJyB,CAAA,UAAA,CAAA,SAIgB,uBAJS,CAAA;EAIlD,YAAA,EACD,UADqB;EACrB,gBAAA,CAAA,EAAA,OAAA;EAEiB,oBAAA,CAAA,EAAA,CAAA,KAAA,EAAA,UAAA,EAAA,GAAe,UAAf;;AAHyB,UAMzC,mBANyC,CAAA,UAAA,CAAA,CAAA;EAAuB,GAAA,EAAA,GAAA,GAOpE,UAPoE;EAMhE,GAAA,EAAA,CAAA,KAAA,EAEF,UAFqB,EAAA,GAAA,IAAA;EAuNvB,OAAA,EA0BZ,GAAA,GAAA,IAAA;;AAzBU,cADE,KACF,EAAA,CAAA,UAAA,CAAA,CAAA,OAAA,EAAA,oBAAA,CAAqB,UAArB,CAAA,EAAA,GACR,mBADQ,CACY,UADZ,CAAA"}