crann 2.0.1 → 2.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 +60 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
import { createConfig, Persist } from "crann";
|
|
37
37
|
|
|
38
38
|
export const config = createConfig({
|
|
39
|
-
name: "myExtension",
|
|
40
|
-
version: 1,
|
|
41
|
-
|
|
39
|
+
name: "myExtension", // Required: unique store name
|
|
40
|
+
version: 1, // Optional: for migrations
|
|
41
|
+
|
|
42
42
|
// Define your state
|
|
43
43
|
isEnabled: { default: false },
|
|
44
44
|
count: { default: 0, persist: Persist.Local },
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
// Define actions (RPC)
|
|
47
47
|
actions: {
|
|
48
48
|
increment: {
|
|
@@ -79,10 +79,10 @@ const agent = connectStore(config);
|
|
|
79
79
|
|
|
80
80
|
agent.onReady(() => {
|
|
81
81
|
console.log("Connected! Current state:", agent.getState());
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
// Update state
|
|
84
84
|
agent.setState({ isEnabled: true });
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
// Call actions
|
|
87
87
|
agent.actions.increment(5);
|
|
88
88
|
});
|
|
@@ -95,21 +95,18 @@ agent.onReady(() => {
|
|
|
95
95
|
import { createCrannHooks } from "crann/react";
|
|
96
96
|
import { config } from "./config";
|
|
97
97
|
|
|
98
|
-
export const { useCrannState, useCrannActions, useCrannReady } =
|
|
98
|
+
export const { useCrannState, useCrannActions, useCrannReady } =
|
|
99
|
+
createCrannHooks(config);
|
|
99
100
|
|
|
100
101
|
// Counter.tsx
|
|
101
102
|
function Counter() {
|
|
102
|
-
const count = useCrannState(s => s.count);
|
|
103
|
+
const count = useCrannState((s) => s.count);
|
|
103
104
|
const { increment } = useCrannActions();
|
|
104
105
|
const isReady = useCrannReady();
|
|
105
106
|
|
|
106
107
|
if (!isReady) return <div>Loading...</div>;
|
|
107
108
|
|
|
108
|
-
return (
|
|
109
|
-
<button onClick={() => increment(1)}>
|
|
110
|
-
Count: {count}
|
|
111
|
-
</button>
|
|
112
|
-
);
|
|
109
|
+
return <button onClick={() => increment(1)}>Count: {count}</button>;
|
|
113
110
|
}
|
|
114
111
|
```
|
|
115
112
|
|
|
@@ -123,25 +120,25 @@ import { createConfig, Scope, Persist } from "crann";
|
|
|
123
120
|
const config = createConfig({
|
|
124
121
|
// Required: unique identifier for this store
|
|
125
122
|
name: "myStore",
|
|
126
|
-
|
|
123
|
+
|
|
127
124
|
// Optional: version number for migrations (default: 1)
|
|
128
125
|
version: 1,
|
|
129
|
-
|
|
126
|
+
|
|
130
127
|
// State definitions
|
|
131
128
|
count: { default: 0 },
|
|
132
|
-
|
|
129
|
+
|
|
133
130
|
// With persistence
|
|
134
|
-
theme: {
|
|
131
|
+
theme: {
|
|
135
132
|
default: "light" as "light" | "dark",
|
|
136
|
-
persist: Persist.Local,
|
|
133
|
+
persist: Persist.Local, // Persist.Local | Persist.Session | Persist.None
|
|
137
134
|
},
|
|
138
|
-
|
|
135
|
+
|
|
139
136
|
// Agent-scoped state (each tab/frame gets its own copy)
|
|
140
137
|
selectedElement: {
|
|
141
138
|
default: null as HTMLElement | null,
|
|
142
|
-
scope: Scope.Agent,
|
|
139
|
+
scope: Scope.Agent, // Scope.Shared (default) | Scope.Agent
|
|
143
140
|
},
|
|
144
|
-
|
|
141
|
+
|
|
145
142
|
// Actions (RPC handlers)
|
|
146
143
|
actions: {
|
|
147
144
|
doSomething: {
|
|
@@ -167,7 +164,7 @@ The Store runs in the service worker and manages all state:
|
|
|
167
164
|
import { createStore } from "crann";
|
|
168
165
|
|
|
169
166
|
const store = createStore(config, {
|
|
170
|
-
debug: true,
|
|
167
|
+
debug: true, // Enable debug logging
|
|
171
168
|
});
|
|
172
169
|
|
|
173
170
|
// Get current state
|
|
@@ -275,8 +272,8 @@ Two patterns for reading state:
|
|
|
275
272
|
|
|
276
273
|
```typescript
|
|
277
274
|
// Selector pattern - returns selected value
|
|
278
|
-
const count = useCrannState(s => s.count);
|
|
279
|
-
const theme = useCrannState(s => s.settings.theme);
|
|
275
|
+
const count = useCrannState((s) => s.count);
|
|
276
|
+
const theme = useCrannState((s) => s.settings.theme);
|
|
280
277
|
|
|
281
278
|
// Key pattern - returns [value, setValue] tuple
|
|
282
279
|
const [count, setCount] = useCrannState("count");
|
|
@@ -331,20 +328,20 @@ Actions execute in the service worker but can be called from any context:
|
|
|
331
328
|
const config = createConfig({
|
|
332
329
|
name: "myStore",
|
|
333
330
|
count: { default: 0 },
|
|
334
|
-
|
|
331
|
+
|
|
335
332
|
actions: {
|
|
336
333
|
increment: {
|
|
337
334
|
handler: async (ctx, amount: number = 1) => {
|
|
338
335
|
const newCount = ctx.state.count + amount;
|
|
339
336
|
// Option 1: Return state updates
|
|
340
337
|
return { count: newCount };
|
|
341
|
-
|
|
338
|
+
|
|
342
339
|
// Option 2: Use ctx.setState
|
|
343
340
|
// await ctx.setState({ count: newCount });
|
|
344
341
|
// return { success: true };
|
|
345
342
|
},
|
|
346
343
|
},
|
|
347
|
-
|
|
344
|
+
|
|
348
345
|
fetchUser: {
|
|
349
346
|
handler: async (ctx, userId: string) => {
|
|
350
347
|
// Runs in service worker - can make network requests
|
|
@@ -376,10 +373,10 @@ Action handlers receive a context object:
|
|
|
376
373
|
|
|
377
374
|
```typescript
|
|
378
375
|
interface ActionContext<TState> {
|
|
379
|
-
state: TState;
|
|
380
|
-
setState: (partial: Partial<TState>) => Promise<void>;
|
|
381
|
-
agentId: string;
|
|
382
|
-
agentLocation: BrowserLocation;
|
|
376
|
+
state: TState; // Current state snapshot
|
|
377
|
+
setState: (partial: Partial<TState>) => Promise<void>; // Update state
|
|
378
|
+
agentId: string; // Calling agent's ID
|
|
379
|
+
agentLocation: BrowserLocation; // Tab/frame info
|
|
383
380
|
}
|
|
384
381
|
```
|
|
385
382
|
|
|
@@ -392,16 +389,16 @@ import { createConfig, Persist } from "crann";
|
|
|
392
389
|
|
|
393
390
|
const config = createConfig({
|
|
394
391
|
name: "myStore",
|
|
395
|
-
|
|
392
|
+
|
|
396
393
|
// No persistence (default) - resets on service worker restart
|
|
397
394
|
volatile: { default: null },
|
|
398
|
-
|
|
395
|
+
|
|
399
396
|
// Local storage - persists across browser sessions
|
|
400
|
-
preferences: {
|
|
397
|
+
preferences: {
|
|
401
398
|
default: { theme: "light" },
|
|
402
399
|
persist: Persist.Local,
|
|
403
400
|
},
|
|
404
|
-
|
|
401
|
+
|
|
405
402
|
// Session storage - persists until browser closes
|
|
406
403
|
sessionData: {
|
|
407
404
|
default: {},
|
|
@@ -420,16 +417,16 @@ This prevents collisions and enables clean migrations.
|
|
|
420
417
|
|
|
421
418
|
### Key Changes
|
|
422
419
|
|
|
423
|
-
| v1
|
|
424
|
-
|
|
425
|
-
| `create()`
|
|
426
|
-
| `connect()`
|
|
427
|
-
| `Partition.Instance`
|
|
428
|
-
| `Partition.Service`
|
|
429
|
-
| `crann.set()`
|
|
430
|
-
| `crann.get()`
|
|
420
|
+
| v1 | v2 |
|
|
421
|
+
| ------------------------- | ------------------------- |
|
|
422
|
+
| `create()` | `createStore()` |
|
|
423
|
+
| `connect()` | `connectStore()` |
|
|
424
|
+
| `Partition.Instance` | `Scope.Agent` |
|
|
425
|
+
| `Partition.Service` | `Scope.Shared` |
|
|
426
|
+
| `crann.set()` | `store.setState()` |
|
|
427
|
+
| `crann.get()` | `store.getState()` |
|
|
431
428
|
| `callAction("name", arg)` | `agent.actions.name(arg)` |
|
|
432
|
-
| Config object literal
|
|
429
|
+
| Config object literal | `createConfig()` |
|
|
433
430
|
|
|
434
431
|
### Migration Steps
|
|
435
432
|
|
|
@@ -443,7 +440,7 @@ const crann = create({
|
|
|
443
440
|
|
|
444
441
|
// After (v2)
|
|
445
442
|
const config = createConfig({
|
|
446
|
-
name: "myStore",
|
|
443
|
+
name: "myStore", // Required in v2
|
|
447
444
|
count: { default: 0 },
|
|
448
445
|
});
|
|
449
446
|
|
|
@@ -454,10 +451,10 @@ const store = createStore(config);
|
|
|
454
451
|
|
|
455
452
|
```typescript
|
|
456
453
|
// Before (v1)
|
|
457
|
-
partition: Partition.Instance
|
|
454
|
+
partition: Partition.Instance;
|
|
458
455
|
|
|
459
456
|
// After (v2)
|
|
460
|
-
scope: Scope.Agent
|
|
457
|
+
scope: Scope.Agent;
|
|
461
458
|
```
|
|
462
459
|
|
|
463
460
|
3. **Update React hooks:**
|
|
@@ -484,16 +481,25 @@ await agent.actions.increment(5);
|
|
|
484
481
|
|
|
485
482
|
## Why Crann?
|
|
486
483
|
|
|
487
|
-
Browser extensions have multiple isolated contexts that need to share state:
|
|
484
|
+
Browser extensions have multiple isolated contexts (content scripts, popup, devtools, sidepanel) that need to share state. The traditional approach using `sendMessage`/`onMessage` forces a painful pattern:
|
|
485
|
+
|
|
486
|
+
[](https://mermaid.live/edit#pako:eNp9k2tvmzAUhv-KZSlikyjCEBLgwyJK2FapbSJwVbVhihi4BDXYyJi2WZT_PodcmtvmT4f3PK998DlewpRlBLqw01kWtBAuWCpiRkqiuEDJEv6qrFadTkxf5uw9nSVcgNswpkCuuvmd86SaAS8nVNSTGLYB8BkV5EPUMfy1AdfLj9CkTUggSnlRCYCO0sZp2jhIj0eTMaua6kAa4smQvAnG5vVWJTSL6Ulp0aMsKyL8rUgJeGT8lfCjsrKCk1QUjAJ8_amGowcchNJ5R-o6yQkIWSNOnJ_RwWE32P8pbfV7IdLZl7LONbGoyNcj47-PbS8CSX-a1AQoPwI8jbCHA-XM7xt7KvoPZe6pMLj1nqZ4NMXe9QWwuwe_B_IXpkMPexcwa489REE49Xx8M7q_wPUkp2naeeJ-t8FgMDjKtr07aaIcGXB19W3bjZ1mnGnj0Zk0xGfSJm7lTZ-gCkvCy6TI5PAv11AM28GPoSvD9ejHMKYrySWNYNGCptAVvCEq5KzJZ9B9Sea1_GqqLBFkWCRyCsq9WiX0mbFyZ5Gf0F3CD-gamqMjByHLNsyurfdtU4UL6CK9r3Wdfh_pes80bKuPVir80-6ga47joC6ykWEaXcswbRXmfF339iwub41wnzVUQLdnWSokWSEYv9u87PaBr_4CI3YYvA)
|
|
487
|
+
|
|
488
|
+
**The problem with `sendMessage` / `onMessage`:**
|
|
488
489
|
|
|
489
|
-
-
|
|
490
|
-
-
|
|
491
|
-
-
|
|
492
|
-
-
|
|
490
|
+
- Agents can't message each other directly—everything routes through the service worker
|
|
491
|
+
- Your service worker becomes a message router with growing `switch/case` statements
|
|
492
|
+
- Every new feature means more message types, more handlers, more coupling
|
|
493
|
+
- Manual async handling (`return true` in Chrome, different in Firefox)
|
|
494
|
+
- Hand-rolled TypeScript types that may or may not stay in sync
|
|
493
495
|
|
|
494
|
-
|
|
496
|
+
**With Crann:**
|
|
495
497
|
|
|
496
|
-
|
|
498
|
+
- Define your state and actions in one place
|
|
499
|
+
- Agents sync automatically through the central store
|
|
500
|
+
- Full TypeScript inference—no manual type definitions
|
|
501
|
+
- No message routing, no relay logic, no `return true`
|
|
502
|
+
- Focus on your features, not the plumbing
|
|
497
503
|
|
|
498
504
|
---
|
|
499
505
|
|