@chromahq/store 0.0.2
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/README.md +557 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
# @chromahq/store
|
|
2
|
+
|
|
3
|
+
> 🏪 **Simple, powerful state management** for Chrome extensions with automatic synchronization between service worker and UI contexts.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- **🔄 Auto-Sync**: Service worker ↔ Popup ↔ Content scripts
|
|
8
|
+
- **💾 Auto-Persist**: All stores automatically persist to Chrome storage
|
|
9
|
+
- **🎯 Zero Config**: Smart context detection - no complex setup
|
|
10
|
+
- **🎨 Modern API**: Clean, fluent builder pattern
|
|
11
|
+
- **🔒 Type Safe**: Full TypeScript support with excellent DX
|
|
12
|
+
- **⚡ Fast**: Optimistic updates with background sync
|
|
13
|
+
|
|
14
|
+
## 🚀 Quick Setup
|
|
15
|
+
|
|
16
|
+
### Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @chromahq/store @chromahq/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## � Usage
|
|
23
|
+
|
|
24
|
+
### 1. Define Your Slices
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// src/slices/counter.ts
|
|
28
|
+
import type { StateCreator } from 'zustand';
|
|
29
|
+
|
|
30
|
+
export interface CounterSlice {
|
|
31
|
+
count: number;
|
|
32
|
+
increment: () => void;
|
|
33
|
+
decrement: () => void;
|
|
34
|
+
reset: () => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const counterSlice: StateCreator<CounterSlice> = (set) => ({
|
|
38
|
+
count: 0,
|
|
39
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
40
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
41
|
+
reset: () => set({ count: 0 }),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Export combined type for TypeScript
|
|
45
|
+
export type RootState = CounterSlice; // Add more slices with &
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Service Worker Setup
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// src/service-worker.ts
|
|
52
|
+
import '@abraham/reflection';
|
|
53
|
+
import { StoreDefinition } from '@chromahq/store';
|
|
54
|
+
import { bootstrap } from '@chromahq/core';
|
|
55
|
+
import { counterSlice } from './slices/counter';
|
|
56
|
+
|
|
57
|
+
const store: StoreDefinition = {
|
|
58
|
+
name: 'app',
|
|
59
|
+
slices: [counterSlice],
|
|
60
|
+
// Persistence is automatic - no config needed!
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
bootstrap().withStore(store).create();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. React UI Setup
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// src/hooks/useAppStore.ts
|
|
70
|
+
import { RootState } from '../slices/counter';
|
|
71
|
+
import { useBridge } from '@chromahq/react';
|
|
72
|
+
import { CentralStore, createStore } from '@chromahq/store';
|
|
73
|
+
import { useEffect, useState } from 'react';
|
|
74
|
+
|
|
75
|
+
export function useAppStore() {
|
|
76
|
+
const { bridge } = useBridge();
|
|
77
|
+
const [store, setStore] = useState<CentralStore<RootState>>();
|
|
78
|
+
const [loading, setLoading] = useState(true);
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
async function initStore() {
|
|
82
|
+
if (!bridge) return;
|
|
83
|
+
|
|
84
|
+
const store = await createStore<RootState>('app')
|
|
85
|
+
.withSlices(counterSlice)
|
|
86
|
+
.withBridge(bridge)
|
|
87
|
+
.create();
|
|
88
|
+
|
|
89
|
+
setStore(store);
|
|
90
|
+
setLoading(false);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
initStore();
|
|
94
|
+
}, [bridge]);
|
|
95
|
+
|
|
96
|
+
return { store, loading };
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// src/components/Counter.tsx
|
|
102
|
+
import { useCentralStore } from '@chromahq/store';
|
|
103
|
+
import { useAppStore } from '../hooks/useAppStore';
|
|
104
|
+
|
|
105
|
+
export function Counter() {
|
|
106
|
+
const { store, loading } = useAppStore();
|
|
107
|
+
|
|
108
|
+
// Use the store with a selector
|
|
109
|
+
const count = useCentralStore(store!, (state) => state.count);
|
|
110
|
+
const increment = useCentralStore(store!, (state) => state.increment);
|
|
111
|
+
const decrement = useCentralStore(store!, (state) => state.decrement);
|
|
112
|
+
|
|
113
|
+
if (loading) return <div>Loading...</div>;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div>
|
|
117
|
+
<h2>Count: {count}</h2>
|
|
118
|
+
<button onClick={increment}>+</button>
|
|
119
|
+
<button onClick={decrement}>-</button>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 🏗 Architecture
|
|
126
|
+
|
|
127
|
+
### Service Worker (Source of Truth)
|
|
128
|
+
|
|
129
|
+
- **ServiceWorkerStore**: Real Zustand store with automatic Chrome storage persistence
|
|
130
|
+
- **Message Handlers**: Auto-registered for cross-context communication
|
|
131
|
+
- **State Authority**: The single source of truth for all application state
|
|
132
|
+
|
|
133
|
+
### UI Contexts (React/Popup/Content Scripts)
|
|
134
|
+
|
|
135
|
+
- **BridgeStore**: Lightweight proxy that connects to service worker via bridge
|
|
136
|
+
- **Reactive Updates**: Automatically syncs with service worker state changes
|
|
137
|
+
- **Optimistic Updates**: Immediate UI feedback with background synchronization
|
|
138
|
+
|
|
139
|
+
## 🎯 Key Benefits
|
|
140
|
+
|
|
141
|
+
### Simple & Clean
|
|
142
|
+
|
|
143
|
+
- **No plugin system complexity** - just slices and bridges
|
|
144
|
+
- **Automatic persistence** - every store persists without configuration
|
|
145
|
+
- **Smart context detection** - creates the right store type automatically
|
|
146
|
+
|
|
147
|
+
### Developer Experience
|
|
148
|
+
|
|
149
|
+
- **TypeScript first** - excellent type inference and safety
|
|
150
|
+
- **Familiar API** - built on Zustand, same patterns you know
|
|
151
|
+
- **Zero boilerplate** - minimal setup, maximum functionality
|
|
152
|
+
|
|
153
|
+
### Performance
|
|
154
|
+
|
|
155
|
+
- **Optimistic updates** - UI responds immediately
|
|
156
|
+
- **Background sync** - service worker handles persistence
|
|
157
|
+
- **Efficient bridge** - only sends serializable data, executes functions locally
|
|
158
|
+
|
|
159
|
+
## 📚 API Reference
|
|
160
|
+
|
|
161
|
+
### Core Functions
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// Create a store builder
|
|
165
|
+
createStore<T>(name?: string): StoreBuilder<T>
|
|
166
|
+
|
|
167
|
+
// StoreBuilder methods
|
|
168
|
+
.withSlices(...slices): StoreBuilder<T> // Add state slices
|
|
169
|
+
.withBridge(bridge): StoreBuilder<T> // Connect to service worker (UI only)
|
|
170
|
+
.create(): Promise<CentralStore<T>> // Create the store
|
|
171
|
+
|
|
172
|
+
// React hooks
|
|
173
|
+
useCentralStore<T, U>(store, selector): U // Subscribe to state
|
|
174
|
+
useCentralDispatch<T>(store): SetState<T> // Get state updater
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Types
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// Store definition for service worker bootstrap
|
|
181
|
+
interface StoreDefinition {
|
|
182
|
+
name: string;
|
|
183
|
+
slices: StateCreator<any, [], [], any>[];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Central store interface (both ServiceWorkerStore and BridgeStore)
|
|
187
|
+
interface CentralStore<T> {
|
|
188
|
+
getState(): T;
|
|
189
|
+
setState(partial: T | Partial<T> | ((state: T) => T | Partial<T>), replace?: boolean): void;
|
|
190
|
+
subscribe(listener: (state: T, prevState: T) => void): () => void;
|
|
191
|
+
ready: Promise<void>;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## 🔧 Advanced Usage
|
|
196
|
+
|
|
197
|
+
### Multiple Stores
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// Service worker
|
|
201
|
+
const userStore: StoreDefinition = { name: 'user', slices: [userSlice] };
|
|
202
|
+
const settingsStore: StoreDefinition = { name: 'settings', slices: [settingsSlice] };
|
|
203
|
+
|
|
204
|
+
bootstrap().withStore(userStore).withStore(settingsStore).create();
|
|
205
|
+
|
|
206
|
+
// React
|
|
207
|
+
const userStore = await createStore<UserState>('user')
|
|
208
|
+
.withSlices(userSlice)
|
|
209
|
+
.withBridge(bridge)
|
|
210
|
+
.create();
|
|
211
|
+
const settingsStore = await createStore<SettingsState>('settings')
|
|
212
|
+
.withSlices(settingsSlice)
|
|
213
|
+
.withBridge(bridge)
|
|
214
|
+
.create();
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Context Providers
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// Create typed hooks with context
|
|
221
|
+
import { createStoreHooks } from '@chromahq/store';
|
|
222
|
+
|
|
223
|
+
const { StoreProvider, useStore } = createStoreHooks<RootState>();
|
|
224
|
+
|
|
225
|
+
function App() {
|
|
226
|
+
const { store } = useAppStore();
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<StoreProvider store={store}>
|
|
230
|
+
<MyComponent />
|
|
231
|
+
</StoreProvider>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function MyComponent() {
|
|
236
|
+
// No need to pass store around - uses context
|
|
237
|
+
const count = useStore(state => state.count);
|
|
238
|
+
const increment = useStore(state => state.increment);
|
|
239
|
+
|
|
240
|
+
return <button onClick={increment}>{count}</button>;
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
**🎉 That's it!** You now have powerful, type-safe state management across your entire Chrome extension with automatic persistence and synchronization.
|
|
247
|
+
return (
|
|
248
|
+
<BridgeProvider>
|
|
249
|
+
<AppContent />
|
|
250
|
+
</BridgeProvider>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
````
|
|
255
|
+
|
|
256
|
+
### 3. Use in Components
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// src/popup/components/Counter.tsx
|
|
260
|
+
import React from 'react';
|
|
261
|
+
import { useStore } from '../../hooks/useAppStore';
|
|
262
|
+
|
|
263
|
+
export function Counter() {
|
|
264
|
+
// Select specific state (automatically typed!)
|
|
265
|
+
const count = useStore(state => state.count);
|
|
266
|
+
const { increment, decrement, reset } = useStore(state => ({
|
|
267
|
+
increment: state.increment,
|
|
268
|
+
decrement: state.decrement,
|
|
269
|
+
reset: state.reset
|
|
270
|
+
}));
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<div className="counter">
|
|
274
|
+
<h2>Counter: {count}</h2>
|
|
275
|
+
<div className="buttons">
|
|
276
|
+
<button onClick={increment}>+1</button>
|
|
277
|
+
<button onClick={decrement}>-1</button>
|
|
278
|
+
<button onClick={reset}>Reset</button>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
````
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
// src/popup/components/UserProfile.tsx
|
|
287
|
+
import React from 'react';
|
|
288
|
+
import { useStore } from '../../hooks/useAppStore';
|
|
289
|
+
|
|
290
|
+
export function UserProfile() {
|
|
291
|
+
const { user, isAuthenticated } = useStore(state => ({
|
|
292
|
+
user: state.user,
|
|
293
|
+
isAuthenticated: state.isAuthenticated
|
|
294
|
+
}));
|
|
295
|
+
const { login, logout } = useStore(state => ({
|
|
296
|
+
login: state.login,
|
|
297
|
+
logout: state.logout
|
|
298
|
+
}));
|
|
299
|
+
|
|
300
|
+
const handleLogin = () => {
|
|
301
|
+
login({
|
|
302
|
+
name: 'John Doe',
|
|
303
|
+
email: 'john@example.com',
|
|
304
|
+
id: '12345'
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
if (!isAuthenticated) {
|
|
309
|
+
return (
|
|
310
|
+
<div className="login">
|
|
311
|
+
<h3>Please log in</h3>
|
|
312
|
+
<button onClick={handleLogin}>Login</button>
|
|
313
|
+
</div>
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<div className="profile">
|
|
319
|
+
<h3>Welcome, {user.name}!</h3>
|
|
320
|
+
<p>Email: {user.email}</p>
|
|
321
|
+
<button onClick={logout}>Logout</button>
|
|
322
|
+
</div>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 🔄 How Sync Works
|
|
330
|
+
|
|
331
|
+
### Automatic Synchronization
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// Any change in service worker...
|
|
335
|
+
store.getState().increment(); // count: 0 → 1
|
|
336
|
+
|
|
337
|
+
// ...automatically appears in React components!
|
|
338
|
+
// No manual sync code needed ✨
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### The Magic Behind The Scenes
|
|
342
|
+
|
|
343
|
+
1. **Service Worker** creates real store with persistence
|
|
344
|
+
2. **React Components** create bridge store with same name
|
|
345
|
+
3. **@chromahq/core** bridge automatically syncs all changes
|
|
346
|
+
4. **State updates** flow instantly between contexts
|
|
347
|
+
5. **Persistence** ensures state survives browser restarts
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 🛠️ Advanced Usage
|
|
352
|
+
|
|
353
|
+
### Custom Plugins
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// src/app/stores/plugins.ts
|
|
357
|
+
import type { StorePlugin } from '@chromahq/store';
|
|
358
|
+
|
|
359
|
+
// Analytics plugin
|
|
360
|
+
export const analyticsPlugin: StorePlugin = {
|
|
361
|
+
name: 'analytics',
|
|
362
|
+
priority: 50,
|
|
363
|
+
async setup(store, config) {
|
|
364
|
+
store.subscribe((state, prevState) => {
|
|
365
|
+
// Track state changes
|
|
366
|
+
chrome.runtime.sendMessage({
|
|
367
|
+
type: 'ANALYTICS_EVENT',
|
|
368
|
+
data: { store: config.name, state },
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// Logging plugin
|
|
375
|
+
export const loggingPlugin: StorePlugin = {
|
|
376
|
+
name: 'logging',
|
|
377
|
+
priority: 10, // Higher priority = runs first
|
|
378
|
+
async setup(store, config) {
|
|
379
|
+
console.log(`🏪 Store "${config.name}" initialized`);
|
|
380
|
+
|
|
381
|
+
store.subscribe((state, prevState) => {
|
|
382
|
+
console.log('State change:', { from: prevState, to: state });
|
|
383
|
+
});
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Enhanced Store Definition
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
// src/app/stores/app.store.ts
|
|
392
|
+
import { counterSlice, userSlice } from '../slices';
|
|
393
|
+
import { analyticsPlugin, loggingPlugin } from './plugins';
|
|
394
|
+
import type { StoreDefinition } from '@chromahq/store';
|
|
395
|
+
|
|
396
|
+
const store: StoreDefinition = {
|
|
397
|
+
name: 'app',
|
|
398
|
+
slices: [counterSlice, userSlice],
|
|
399
|
+
persistence: {
|
|
400
|
+
name: 'my-extension-state',
|
|
401
|
+
version: 2,
|
|
402
|
+
migrate: (state, version) => {
|
|
403
|
+
// Handle state migrations
|
|
404
|
+
if (version < 2) {
|
|
405
|
+
return { ...state, newField: 'default' };
|
|
406
|
+
}
|
|
407
|
+
return state;
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
plugins: [loggingPlugin, analyticsPlugin],
|
|
411
|
+
config: {
|
|
412
|
+
apiUrl: 'https://api.myservice.com',
|
|
413
|
+
debugMode: true,
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
export default store;
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Multiple Stores
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
// src/app/stores/user.store.ts
|
|
424
|
+
import { userSlice, authSlice } from '../slices';
|
|
425
|
+
|
|
426
|
+
export default {
|
|
427
|
+
name: 'user',
|
|
428
|
+
slices: [userSlice, authSlice],
|
|
429
|
+
persistence: { name: 'user-data' },
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// src/app/stores/settings.store.ts
|
|
433
|
+
import { settingsSlice, themeSlice } from '../slices';
|
|
434
|
+
|
|
435
|
+
export default {
|
|
436
|
+
name: 'settings',
|
|
437
|
+
slices: [settingsSlice, themeSlice],
|
|
438
|
+
persistence: { name: 'user-settings' },
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// src/app/stores/cache.store.ts
|
|
442
|
+
import { cacheSlice } from '../slices';
|
|
443
|
+
import { ttlPlugin } from './plugins';
|
|
444
|
+
|
|
445
|
+
export default {
|
|
446
|
+
name: 'cache',
|
|
447
|
+
slices: [cacheSlice],
|
|
448
|
+
plugins: [ttlPlugin],
|
|
449
|
+
};
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## 🎯 Best Practices
|
|
455
|
+
|
|
456
|
+
### 1. **Store Organization**
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
// ✅ Good: Feature-based slices
|
|
460
|
+
const userSlice = (set, get) => ({
|
|
461
|
+
/* user logic */
|
|
462
|
+
});
|
|
463
|
+
const settingsSlice = (set, get) => ({
|
|
464
|
+
/* settings logic */
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// ❌ Avoid: One giant slice
|
|
468
|
+
const everythingSlice = (set, get) => ({
|
|
469
|
+
/* 500 lines of code */
|
|
470
|
+
});
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### 2. **State Selection**
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
// ✅ Good: Specific selectors
|
|
477
|
+
const count = useStore((state) => state.count);
|
|
478
|
+
const userName = useStore((state) => state.user?.name);
|
|
479
|
+
|
|
480
|
+
// ❌ Avoid: Selecting entire state
|
|
481
|
+
const everything = useStore((state) => state); // Causes unnecessary re-renders
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### 3. **Store Names**
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
// ✅ Good: Descriptive and consistent
|
|
488
|
+
createStore('user-preferences'); // Service worker
|
|
489
|
+
createStore('user-preferences'); // React (same name!)
|
|
490
|
+
|
|
491
|
+
// ❌ Avoid: Generic or mismatched names
|
|
492
|
+
createStore('store'); // Service worker
|
|
493
|
+
createStore('data'); // React (different name!)
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### 4. **Error Handling**
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
// ✅ Good: Handle connection states
|
|
500
|
+
function AppContent() {
|
|
501
|
+
const store = useAppStore();
|
|
502
|
+
|
|
503
|
+
if (!store) {
|
|
504
|
+
return <LoadingSpinner />;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return <MainApp store={store} />;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ❌ Avoid: Assuming store is always ready
|
|
511
|
+
function App() {
|
|
512
|
+
const store = useAppStore();
|
|
513
|
+
return <StoreProvider store={store}>...</StoreProvider>; // Might be null!
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## 🚨 Troubleshooting
|
|
520
|
+
|
|
521
|
+
### Store Not Syncing
|
|
522
|
+
|
|
523
|
+
- ✅ Ensure both stores use the **exact same name**
|
|
524
|
+
- ✅ Check that service worker store is created first
|
|
525
|
+
- ✅ Verify `@chromahq/core` bridge is initialized with `create()`
|
|
526
|
+
|
|
527
|
+
### React Components Not Updating
|
|
528
|
+
|
|
529
|
+
- ✅ Use specific selectors: `state => state.count` not `state => state`
|
|
530
|
+
- ✅ Ensure components are wrapped in `<StoreProvider>`
|
|
531
|
+
- ✅ Check that bridge connection is established
|
|
532
|
+
|
|
533
|
+
### State Not Persisting
|
|
534
|
+
|
|
535
|
+
- ✅ Add `.withPersistence({ name: 'unique-name' })` to service worker store
|
|
536
|
+
- ✅ Ensure service worker has storage permissions in manifest
|
|
537
|
+
- ✅ Check Chrome DevTools → Application → Storage → Local Storage
|
|
538
|
+
|
|
539
|
+
### TypeScript Errors
|
|
540
|
+
|
|
541
|
+
- ✅ Define interfaces for your slices
|
|
542
|
+
- ✅ Use `createStoreHooks<YourStateType>()` for typed hooks
|
|
543
|
+
- ✅ Ensure same state shape in service worker and React
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## 📦 Package Info
|
|
548
|
+
|
|
549
|
+
- **Main API**: `createStore()` with plugin system
|
|
550
|
+
- **React Integration**: `createStoreHooks()` for type-safe hooks
|
|
551
|
+
- **Auto-Sync**: Works with `@chromahq/core` bridge
|
|
552
|
+
- **Persistence**: Built-in Chrome storage support
|
|
553
|
+
- **TypeScript**: Full type safety throughout
|
|
554
|
+
|
|
555
|
+
## 📄 License
|
|
556
|
+
|
|
557
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chromahq/store",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Centralized, persistent store for Chrome extensions using zustand, accessible from service workers and React, with chrome.storage.local persistence.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs.js",
|
|
7
|
+
"module": "dist/index.es.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.es.js",
|
|
15
|
+
"require": "./dist/index.cjs.js",
|
|
16
|
+
"types": "./dist/index.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./autoRegister": {
|
|
19
|
+
"import": "./dist/autoRegister.js",
|
|
20
|
+
"require": "./dist/autoRegister.cjs.js",
|
|
21
|
+
"types": "./dist/autoRegister.d.ts"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/chromaHQ/chroma.git",
|
|
30
|
+
"directory": "packages/store"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"chrome-extension",
|
|
34
|
+
"browser-extension",
|
|
35
|
+
"store",
|
|
36
|
+
"zustand",
|
|
37
|
+
"react",
|
|
38
|
+
"service-worker",
|
|
39
|
+
"persistence",
|
|
40
|
+
"chrome-storage"
|
|
41
|
+
],
|
|
42
|
+
"author": "Chroma Team",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"homepage": "https://github.com/chromaHQ/chroma#readme",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/chromaHQ/chroma/issues"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@chromahq/core": ">=0.0.1",
|
|
50
|
+
"react": ">=18"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"zustand": "^5.0.7"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/chrome": "^0.0.326",
|
|
57
|
+
"@types/react": "^18.2.7",
|
|
58
|
+
"typescript": "^5.8.3"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "rollup -c rollup.config.mjs",
|
|
62
|
+
"dev": "rollup -c rollup.config.mjs --watch"
|
|
63
|
+
}
|
|
64
|
+
}
|