electron-state-sync 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +254 -0
- package/LICENSE +21 -0
- package/README.md +753 -0
- package/README.zh-CN.md +743 -0
- package/bun.lock +542 -0
- package/bunfig.toml +7 -0
- package/dist/jotai.cjs +106 -0
- package/dist/jotai.d.cts +48 -0
- package/dist/jotai.d.cts.map +1 -0
- package/dist/jotai.d.ts +48 -0
- package/dist/jotai.d.ts.map +1 -0
- package/dist/jotai.js +105 -0
- package/dist/jotai.js.map +1 -0
- package/dist/main.cjs +177 -0
- package/dist/main.d.cts +26 -0
- package/dist/main.d.cts.map +1 -0
- package/dist/main.d.ts +26 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +177 -0
- package/dist/main.js.map +1 -0
- package/dist/preact.cjs +46 -0
- package/dist/preact.d.cts +11 -0
- package/dist/preact.d.cts.map +1 -0
- package/dist/preact.d.ts +11 -0
- package/dist/preact.d.ts.map +1 -0
- package/dist/preact.js +46 -0
- package/dist/preact.js.map +1 -0
- package/dist/preload.cjs +51 -0
- package/dist/preload.d.cts +20 -0
- package/dist/preload.d.cts.map +1 -0
- package/dist/preload.d.ts +20 -0
- package/dist/preload.d.ts.map +1 -0
- package/dist/preload.js +51 -0
- package/dist/preload.js.map +1 -0
- package/dist/react-query.cjs +113 -0
- package/dist/react-query.d.cts +58 -0
- package/dist/react-query.d.cts.map +1 -0
- package/dist/react-query.d.ts +58 -0
- package/dist/react-query.d.ts.map +1 -0
- package/dist/react-query.js +112 -0
- package/dist/react-query.js.map +1 -0
- package/dist/react.cjs +46 -0
- package/dist/react.d.cts +11 -0
- package/dist/react.d.cts.map +1 -0
- package/dist/react.d.ts +11 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +46 -0
- package/dist/react.js.map +1 -0
- package/dist/redux.cjs +148 -0
- package/dist/redux.d.cts +80 -0
- package/dist/redux.d.cts.map +1 -0
- package/dist/redux.d.ts +80 -0
- package/dist/redux.d.ts.map +1 -0
- package/dist/redux.js +146 -0
- package/dist/redux.js.map +1 -0
- package/dist/renderer/index.cjs +7 -0
- package/dist/renderer/index.d.cts +23 -0
- package/dist/renderer/index.d.cts.map +1 -0
- package/dist/renderer/index.d.ts +23 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +3 -0
- package/dist/renderer-C7zF3UQm.js +57 -0
- package/dist/renderer-C7zF3UQm.js.map +1 -0
- package/dist/renderer-D3YziJ_U.cjs +86 -0
- package/dist/solid.cjs +74 -0
- package/dist/solid.d.cts +13 -0
- package/dist/solid.d.cts.map +1 -0
- package/dist/solid.d.ts +13 -0
- package/dist/solid.d.ts.map +1 -0
- package/dist/solid.js +74 -0
- package/dist/solid.js.map +1 -0
- package/dist/svelte.cjs +63 -0
- package/dist/svelte.d.cts +14 -0
- package/dist/svelte.d.cts.map +1 -0
- package/dist/svelte.d.ts +14 -0
- package/dist/svelte.d.ts.map +1 -0
- package/dist/svelte.js +63 -0
- package/dist/svelte.js.map +1 -0
- package/dist/types-7wPPX0ty.d.ts +37 -0
- package/dist/types-7wPPX0ty.d.ts.map +1 -0
- package/dist/types-C18dHgLI.d.cts +37 -0
- package/dist/types-C18dHgLI.d.cts.map +1 -0
- package/dist/vue.cjs +69 -0
- package/dist/vue.d.cts +15 -0
- package/dist/vue.d.cts.map +1 -0
- package/dist/vue.d.ts +15 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +70 -0
- package/dist/vue.js.map +1 -0
- package/dist/zustand.cjs +193 -0
- package/dist/zustand.d.cts +61 -0
- package/dist/zustand.d.cts.map +1 -0
- package/dist/zustand.d.ts +61 -0
- package/dist/zustand.d.ts.map +1 -0
- package/dist/zustand.js +191 -0
- package/dist/zustand.js.map +1 -0
- package/package.json +162 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This is a lightweight Electron state synchronization library that enables seamless data sharing between main and renderer processes. It supports React, Vue, Svelte, and SolidJS with automatic multi-window sync.
|
|
8
|
+
|
|
9
|
+
**Key characteristics:**
|
|
10
|
+
- Main process bundle: ~6.3KB, renderer bundles: 1.5-2.2KB per framework
|
|
11
|
+
- Three-tier architecture: main process → preload script → renderer layer
|
|
12
|
+
- IPC-based communication using typed channels
|
|
13
|
+
- Write permission control and validation from main process
|
|
14
|
+
- Automatic multi-window synchronization
|
|
15
|
+
|
|
16
|
+
## Development Commands
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Build all artifacts (main + preload + renderer integrations)
|
|
20
|
+
bun run build
|
|
21
|
+
|
|
22
|
+
# Build individual parts
|
|
23
|
+
bun run build:main # Main process (src/main.ts)
|
|
24
|
+
bun run build:preload # Preload script (src/preload.ts)
|
|
25
|
+
bun run build:renderer # All framework integrations
|
|
26
|
+
|
|
27
|
+
# Linting and formatting
|
|
28
|
+
bun run lint # Check code with oxlint
|
|
29
|
+
bun run lint:fix # Auto-fix linting issues
|
|
30
|
+
bun run format # Format code with oxfmt
|
|
31
|
+
bun run format:check # Check formatting
|
|
32
|
+
bun run check # Run both lint and format checks
|
|
33
|
+
|
|
34
|
+
# Testing
|
|
35
|
+
bun run test # Run unit tests (test/ directory)
|
|
36
|
+
bun run coverage # Run unit tests with coverage
|
|
37
|
+
bun run test:e2e # Run E2E tests (requires build first)
|
|
38
|
+
|
|
39
|
+
# Development
|
|
40
|
+
bun run dev # Run src/index.ts directly
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Important:** E2E tests require the library to be built first (`bun run build`). Git hooks enforce this via lefthook (pre-push hook runs tests and build).
|
|
44
|
+
|
|
45
|
+
## Architecture
|
|
46
|
+
|
|
47
|
+
### Three-Tier Structure
|
|
48
|
+
|
|
49
|
+
**1. Main Process (`src/main.ts`)**
|
|
50
|
+
- Central state management hub with IPC handlers
|
|
51
|
+
- Controls write permissions via `allowRendererSet` flag
|
|
52
|
+
- Validates renderer writes via `resolveRendererValue` function
|
|
53
|
+
- Broadcasts updates to all subscribed windows
|
|
54
|
+
- Uses `WebContents` tracking for multi-window sync
|
|
55
|
+
|
|
56
|
+
**2. Preload Script (`src/preload.ts`)**
|
|
57
|
+
- Security bridge using `contextBridge.exposeInMainWorld`
|
|
58
|
+
- Exposes `SyncStateBridge` interface to renderer
|
|
59
|
+
- Handles IPC communication with type safety
|
|
60
|
+
- Entry point for `electron-state-sync/preload` export
|
|
61
|
+
|
|
62
|
+
**3. Renderer Layer (`src/renderer/`)**
|
|
63
|
+
- **Core**: `bridge.ts` resolves bridge instances, `index.ts` provides global config
|
|
64
|
+
- **Framework integrations**: Each framework has its own adapter
|
|
65
|
+
- `react.ts` - React hook with tuple return `[value, setter, isSynced]`
|
|
66
|
+
- `vue.ts` - Vue composable returning Ref with `isSynced` property
|
|
67
|
+
- `svelte.ts` - Svelte store with `isSynced` readable store
|
|
68
|
+
- `solid.ts` - Solid hook with tuple return similar to React
|
|
69
|
+
|
|
70
|
+
### IPC Channel System (`src/channels.ts`)
|
|
71
|
+
|
|
72
|
+
Channel naming pattern: `${baseChannel}:${name}:action`
|
|
73
|
+
|
|
74
|
+
Five channels per state:
|
|
75
|
+
- `get` - Renderer fetches current value
|
|
76
|
+
- `set` - Renderer updates value (if allowed)
|
|
77
|
+
- `subscribe` - Window subscribes to updates
|
|
78
|
+
- `unsubscribe` - Window unsubscribes
|
|
79
|
+
- `update` - Main broadcasts changes to subscribers
|
|
80
|
+
|
|
81
|
+
**Example:** For `baseChannel="state"` and `name="counter"`:
|
|
82
|
+
- `state:counter:get`
|
|
83
|
+
- `state:counter:set`
|
|
84
|
+
- `state:counter:subscribe`
|
|
85
|
+
- `state:counter:unsubscribe`
|
|
86
|
+
- `state:counter:update`
|
|
87
|
+
|
|
88
|
+
### Data Flow
|
|
89
|
+
|
|
90
|
+
1. **Initialization**: Main creates state with `state({ name, initialValue })`
|
|
91
|
+
2. **Subscription**: Renderer calls `bridge.subscribe()` via IPC
|
|
92
|
+
3. **Initial Sync**: Renderer fetches current value via `bridge.get()`
|
|
93
|
+
4. **Updates**:
|
|
94
|
+
- Main process calls `handle.set()` → broadcasts to all subscribers
|
|
95
|
+
- Renderer calls `bridge.set()` → validated by main → broadcast if valid
|
|
96
|
+
5. **Multi-window**: All subscribed windows receive updates automatically
|
|
97
|
+
|
|
98
|
+
### State Management Pattern
|
|
99
|
+
|
|
100
|
+
Each framework integration follows this pattern:
|
|
101
|
+
1. Create local reactive state with `initialValue` (placeholder)
|
|
102
|
+
2. Subscribe to main process updates in effect/hook
|
|
103
|
+
3. Fetch initial value from main process on mount
|
|
104
|
+
4. Local changes sync to main (if allowed)
|
|
105
|
+
5. Remote updates from main override local state
|
|
106
|
+
6. Track sync completion via `isSynced` flag
|
|
107
|
+
|
|
108
|
+
**Critical detail:** To prevent infinite loops when renderer writes are allowed:
|
|
109
|
+
- Frameworks track when applying remote updates
|
|
110
|
+
- Skip syncing back to main during remote update application
|
|
111
|
+
- React/Solid: Use refs/flags to track update source
|
|
112
|
+
- Vue: Compare resolved values before syncing
|
|
113
|
+
- Svelte: Track update state in callback closure
|
|
114
|
+
|
|
115
|
+
## Build System
|
|
116
|
+
|
|
117
|
+
**Tool:** `tsdown` (TypeScript-to-JavaScript compiler)
|
|
118
|
+
|
|
119
|
+
Three separate build configurations:
|
|
120
|
+
- `tsdown.main.config.ts` - Builds main process entry
|
|
121
|
+
- `tsdown.preload.config.ts` - Builds preload script
|
|
122
|
+
- `tsdown.config.ts` - Builds all renderer framework integrations
|
|
123
|
+
|
|
124
|
+
**Output format:** Dual ESM + CJS with TypeScript definitions
|
|
125
|
+
|
|
126
|
+
**Package exports:** Each layer has its own export path:
|
|
127
|
+
- `electron-state-sync/main` → `dist/main.{js,cjs,d.ts}`
|
|
128
|
+
- `electron-state-sync/preload` → `dist/preload.{js,cjs,d.ts}`
|
|
129
|
+
- `electron-state-sync/renderer` → `dist/renderer/index.{js,cjs,d.ts}`
|
|
130
|
+
- `electron-state-sync/react` → `dist/react.{js,cjs,d.ts}`
|
|
131
|
+
- `electron-state-sync/vue` → `dist/vue.{js,cjs,d.ts}`
|
|
132
|
+
- `electron-state-sync/svelte` → `dist/svelte.{js,cjs,d.ts}`
|
|
133
|
+
- `electron-state-sync/solid` → `dist/solid.{js,cjs,d.ts}`
|
|
134
|
+
|
|
135
|
+
## Testing
|
|
136
|
+
|
|
137
|
+
**Unit Tests (`test/`):**
|
|
138
|
+
- `channels.test.ts` - IPC channel generation logic
|
|
139
|
+
- `validation.test.ts` - Serialization validation
|
|
140
|
+
- `index.test.ts` - SyncStateError class
|
|
141
|
+
- `setup.ts` - Mocks Electron modules for unit tests
|
|
142
|
+
- `mocks.ts` - Test utilities
|
|
143
|
+
|
|
144
|
+
**E2E Tests (`e2e/`):**
|
|
145
|
+
- Uses Playwright with real Electron app
|
|
146
|
+
- `sync-state.spec.ts` - Tests all frameworks (React, Vue, Svelte, Solid)
|
|
147
|
+
- `main.mjs` - Electron main process for testing
|
|
148
|
+
- `renderer-*.js` - Framework-specific test renderers
|
|
149
|
+
- `preload.{cjs,mjs}` - Preload scripts for testing
|
|
150
|
+
|
|
151
|
+
**Key test utilities:**
|
|
152
|
+
- Unit tests mock Electron IPC to avoid needing real Electron
|
|
153
|
+
- E2E tests use `@playwright/test` with `electron` launcher
|
|
154
|
+
- Test framework selection via `--framework=` CLI argument
|
|
155
|
+
- Multi-window testing via `--windows=` argument
|
|
156
|
+
|
|
157
|
+
## Code Conventions
|
|
158
|
+
|
|
159
|
+
### Error Handling
|
|
160
|
+
|
|
161
|
+
Use `SyncStateError` for all library errors:
|
|
162
|
+
```typescript
|
|
163
|
+
throw new SyncStateError("RENDERER_READONLY", message, {
|
|
164
|
+
stateName: options.name,
|
|
165
|
+
baseChannel: options.baseChannel ?? "state",
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Error codes:
|
|
170
|
+
- `RENDERER_READONLY` - Renderer write blocked by `allowRendererSet: false`
|
|
171
|
+
- `RENDERER_INVALID_VALUE` - Renderer write rejected by `resolveRendererValue`
|
|
172
|
+
|
|
173
|
+
### Serialization Safety
|
|
174
|
+
|
|
175
|
+
All state values must be JSON-serializable. The library validates:
|
|
176
|
+
- No functions, Symbols, or BigInt
|
|
177
|
+
- No circular references
|
|
178
|
+
- Arrays and objects are recursively checked
|
|
179
|
+
|
|
180
|
+
**Validation function** (`src/main.ts`): Used to warn about invalid initial values but doesn't block them (renderer's initialValue is just a placeholder).
|
|
181
|
+
|
|
182
|
+
### Framework-Specific Notes
|
|
183
|
+
|
|
184
|
+
**Vue only:**
|
|
185
|
+
- Supports `deep` option for object watching
|
|
186
|
+
- Converts Vue Proxies to raw values before IPC via `toRaw()`
|
|
187
|
+
- Watch flush mode set to `'sync'` for immediate updates
|
|
188
|
+
|
|
189
|
+
**React/Solid:**
|
|
190
|
+
- Return tuple `[value, setter, isSynced]` from hooks
|
|
191
|
+
- Use `useCallback`/`useMemo` to optimize bridge operations
|
|
192
|
+
|
|
193
|
+
**Svelte:**
|
|
194
|
+
- Returns custom store with `isSynced` as separate readable store
|
|
195
|
+
- Store cleanup handles unsubscribe automatically
|
|
196
|
+
|
|
197
|
+
### Global Configuration
|
|
198
|
+
|
|
199
|
+
Both main and renderer support global config:
|
|
200
|
+
```typescript
|
|
201
|
+
// Main process
|
|
202
|
+
import { initSyncStateMain } from "electron-state-sync/main";
|
|
203
|
+
initSyncStateMain({ baseChannel: "myapp", allowRendererSet: false });
|
|
204
|
+
|
|
205
|
+
// Renderer process
|
|
206
|
+
import { initSyncState } from "electron-state-sync/react";
|
|
207
|
+
initSyncState({ baseChannel: "myapp" });
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Global config is merged with per-state options, where per-state options take precedence.
|
|
211
|
+
|
|
212
|
+
### Working with Bridges
|
|
213
|
+
|
|
214
|
+
The `SyncStateBridge` interface enables custom integrations:
|
|
215
|
+
```typescript
|
|
216
|
+
interface SyncStateBridge {
|
|
217
|
+
get: <StateValue>(options: SyncStateChannelOptions) => Promise<StateValue>;
|
|
218
|
+
set: <StateValue>(options: SyncStateChannelOptions, value: StateValue) => Promise<void>;
|
|
219
|
+
subscribe: <StateValue>(options: SyncStateChannelOptions, listener: SyncStateListener<StateValue>) => () => void;
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Framework hooks accept optional `bridge` parameter for custom implementations or testing.
|
|
224
|
+
|
|
225
|
+
## Important Files
|
|
226
|
+
|
|
227
|
+
- `src/main.ts` - Main process state management and IPC handlers
|
|
228
|
+
- `src/preload.ts` - Preload bridge exposure
|
|
229
|
+
- `src/channels.ts` - IPC channel naming
|
|
230
|
+
- `src/types.ts` - Core type definitions and SyncStateError
|
|
231
|
+
- `src/renderer/bridge.ts` - Bridge resolution logic
|
|
232
|
+
- `src/renderer/index.ts` - Global config for renderer
|
|
233
|
+
- `src/renderer/react.ts` - React integration
|
|
234
|
+
- `src/renderer/vue.ts` - Vue integration
|
|
235
|
+
- `src/renderer/svelte.ts` - Svelte integration
|
|
236
|
+
- `src/renderer/solid.ts` - SolidJS integration
|
|
237
|
+
|
|
238
|
+
## Linting and Formatting
|
|
239
|
+
|
|
240
|
+
**Tools:**
|
|
241
|
+
- `oxlint` - Fast linting (used in CI and pre-commit hooks)
|
|
242
|
+
- `oxfmt` - Code formatting
|
|
243
|
+
|
|
244
|
+
**Common warnings that should be fixed:**
|
|
245
|
+
- `arrow-body-style` - Use concise arrow function bodies when possible
|
|
246
|
+
- `init-declarations` - Initialize variables on declaration
|
|
247
|
+
- `curly` - Use braces for all if statements
|
|
248
|
+
- `prefer-destructuring` - Use object destructuring
|
|
249
|
+
|
|
250
|
+
**Warnings to ignore (by convention):**
|
|
251
|
+
- `max-statements` - Complex functions are acceptable
|
|
252
|
+
- `max-lines-per-function` - Long functions are acceptable
|
|
253
|
+
|
|
254
|
+
Pre-commit hooks run `lint` and `format:check` in parallel. Pre-push hooks run `test` and `build`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|