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.
Files changed (2) hide show
  1. package/README.md +60 -54
  2. 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", // Required: unique store name
40
- version: 1, // Optional: for migrations
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 } = createCrannHooks(config);
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, // Persist.Local | Persist.Session | Persist.None
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, // Scope.Shared (default) | 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, // Enable debug logging
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; // Current state snapshot
380
- setState: (partial: Partial<TState>) => Promise<void>; // Update state
381
- agentId: string; // Calling agent's ID
382
- agentLocation: BrowserLocation; // Tab/frame info
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 | v2 |
424
- |----|----|
425
- | `create()` | `createStore()` |
426
- | `connect()` | `connectStore()` |
427
- | `Partition.Instance` | `Scope.Agent` |
428
- | `Partition.Service` | `Scope.Shared` |
429
- | `crann.set()` | `store.setState()` |
430
- | `crann.get()` | `store.getState()` |
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 | `createConfig()` |
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", // Required in v2
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
+ [![Message Router Problem](https://mermaid.ink/img/pako:eNp9k2tvmzAUhv-KZSlikyjCEBLgwyJK2FapbSJwVbVhihi4BDXYyJi2WZT_PodcmtvmT4f3PK998DlewpRlBLqw01kWtBAuWCpiRkqiuEDJEv6qrFadTkxf5uw9nSVcgNswpkCuuvmd86SaAS8nVNSTGLYB8BkV5EPUMfy1AdfLj9CkTUggSnlRCYCO0sZp2jhIj0eTMaua6kAa4smQvAnG5vVWJTSL6Ulp0aMsKyL8rUgJeGT8lfCjsrKCk1QUjAJ8_amGowcchNJ5R-o6yQkIWSNOnJ_RwWE32P8pbfV7IdLZl7LONbGoyNcj47-PbS8CSX-a1AQoPwI8jbCHA-XM7xt7KvoPZe6pMLj1nqZ4NMXe9QWwuwe_B_IXpkMPexcwa489REE49Xx8M7q_wPUkp2naeeJ-t8FgMDjKtr07aaIcGXB19W3bjZ1mnGnj0Zk0xGfSJm7lTZ-gCkvCy6TI5PAv11AM28GPoSvD9ejHMKYrySWNYNGCptAVvCEq5KzJZ9B9Sea1_GqqLBFkWCRyCsq9WiX0mbFyZ5Gf0F3CD-gamqMjByHLNsyurfdtU4UL6CK9r3Wdfh_pes80bKuPVir80-6ga47joC6ykWEaXcswbRXmfF339iwub41wnzVUQLdnWSokWSEYv9u87PaBr_4CI3YYvA?type=png)](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
- - **Service Worker** - Background logic and events
490
- - **Content Scripts** - Injected into web pages
491
- - **Popup** - Extension icon click UI
492
- - **Side Panels, DevTools** - Other specialized contexts
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
- Traditionally, this requires complex `chrome.runtime.sendMessage` / `onMessage` patterns. Crann eliminates this boilerplate by providing a central state hub that all contexts can connect to.
496
+ **With Crann:**
495
497
 
496
- ![Traditional vs Crann Architecture](img/with_crann.png)
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crann",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Effortless State Synchronization for Web Extensions",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",