@@ -14,7 +14,7 @@ A lightweight library for cross-tab communication and leader election in browser
1414
1515## Highlights
1616
17- ** Microscopic** : weighs less than 6KB minified (~ 2KB gzipped)
17+ ** Microscopic** : weighs less than 7KB minified (~ 3KB gzipped)
1818
1919** Reliable** : leader election with lease-based heartbeat mechanism
2020
@@ -26,6 +26,18 @@ A lightweight library for cross-tab communication and leader election in browser
2626
2727** Flexible** : callback-based or generator-based APIs, your choice
2828
29+ ## 🎮 Try It Live
30+
31+ ** [ Interactive Demo →] ( https://let-sunny.github.io/purrtabby/ ) **
32+
33+ Try purrtabby in your browser with our interactive demo. Test cross-tab communication and leader election between multiple tabs.
34+
35+ ## 📚 Documentation
36+
37+ ** [ Architecture Documentation →] ( ./docs/ARCHITECTURE.md ) **
38+
39+ Learn about purrtabby's internal architecture and design decisions.
40+
2941## Features
3042
3143- ** Cross-tab communication** using BroadcastChannel
@@ -125,27 +137,78 @@ if (leader.isLeader()) {
125137}
126138
127139// Listen for leader events
128- leader .on (' acquired ' , () => {
140+ leader .on (' acquire ' , () => {
129141 console .log (' Became the leader!' );
130142});
131143
132- leader .on (' lost ' , (event ) => {
144+ leader .on (' lose ' , (event ) => {
133145 console .log (' Lost leadership:' , event .meta ?.newLeader );
134146});
135147
136- leader .on (' changed ' , (event ) => {
148+ leader .on (' change ' , (event ) => {
137149 console .log (' Leadership changed to:' , event .meta ?.newLeader );
138150});
139151
140152// Stop leader election
141153leader .stop ();
142154```
143155
144- ### Generator-based Streams
156+ ### Choosing Between Callback and Generator APIs
157+
158+ purrtabby provides two ways to consume messages and events: ** callbacks** and ** generators** . Choose based on your needs:
159+
160+ - ** Callbacks** : Simple, event-driven, good for one-off handlers
161+ - ** Generators** : Modern async/await patterns, better for complex flows, supports AbortSignal
162+
163+ ### Callback-based API
145164
146165``` typescript
147- import { createBus , createLeaderElector } from ' purrtabby' ;
166+ const bus = createBus ({ channel: ' my-channel' });
167+
168+ // Subscribe to specific message types
169+ const unsubscribeMessage = bus .subscribe (' user-action' , (message ) => {
170+ console .log (' Message:' , message .payload );
171+ });
172+
173+ // Subscribe to all messages
174+ const unsubscribeAll = bus .subscribeAll ((message ) => {
175+ console .log (' Any message:' , message .type , message .payload );
176+ });
177+
178+ // Unsubscribe when done
179+ unsubscribeMessage ();
180+ unsubscribeAll ();
181+
182+ const leader = createLeaderElector ({
183+ key: ' my-leader' ,
184+ tabId: ' tab-1' ,
185+ });
186+
187+ leader .start ();
188+
189+ // Subscribe to specific leader events
190+ const unsubscribeAcquired = leader .on (' acquire' , (event ) => {
191+ console .log (' Became leader' );
192+ });
193+
194+ const unsubscribeLost = leader .on (' lose' , (event ) => {
195+ console .log (' Lost leadership' );
196+ });
197+
198+ // Subscribe to all leader events
199+ const unsubscribeAllEvents = leader .onAll ((event ) => {
200+ console .log (' Leader event:' , event .type );
201+ });
202+
203+ // Unsubscribe when done
204+ unsubscribeAcquired ();
205+ unsubscribeLost ();
206+ unsubscribeAllEvents ();
207+ ```
208+
209+ ### Generator-based Streams
148210
211+ ``` typescript
149212const bus = createBus ({ channel: ' my-channel' });
150213
151214// Consume messages as async iterable
@@ -185,52 +248,6 @@ const controller = new AbortController();
185248})();
186249```
187250
188- ### Callback-based API
189-
190- ``` typescript
191- const bus = createBus ({ channel: ' my-channel' });
192-
193- // Subscribe to messages
194- const unsubscribeMessage = bus .subscribe (' type' , (message ) => {
195- console .log (' Message:' , message );
196- });
197-
198- // Subscribe to all messages
199- const unsubscribeAll = bus .subscribeAll ((message ) => {
200- console .log (' Any message:' , message );
201- });
202-
203- // Unsubscribe when done
204- unsubscribeMessage ();
205- unsubscribeAll ();
206-
207- const leader = createLeaderElector ({
208- key: ' my-leader' ,
209- tabId: ' tab-1' ,
210- });
211-
212- leader .start ();
213-
214- // Subscribe to leader events
215- const unsubscribeAcquired = leader .on (' acquired' , (event ) => {
216- console .log (' Became leader' );
217- });
218-
219- const unsubscribeLost = leader .on (' lost' , (event ) => {
220- console .log (' Lost leadership' );
221- });
222-
223- // Subscribe to all leader events
224- const unsubscribeAll = leader .onAll ((event ) => {
225- console .log (' Leader event:' , event .type );
226- });
227-
228- // Unsubscribe when done
229- unsubscribeAcquired ();
230- unsubscribeLost ();
231- unsubscribeAll ();
232- ```
233-
234251## API
235252
236253### ` createBus(options) `
@@ -305,6 +322,11 @@ Creates a new LeaderElector instance for leader election.
305322| ` leaseMs ` | ` number ` | ` 5000 ` | Lease duration in milliseconds |
306323| ` heartbeatMs ` | ` number ` | ` 2000 ` | Heartbeat interval in milliseconds |
307324| ` jitterMs ` | ` number ` | ` 500 ` | Jitter range to avoid synchronization |
325+ | ` buffer ` | ` BufferConfig ` | ` { size: 100, overflow: 'oldest' } ` | Buffer configuration for stream generators |
326+
327+ ** BufferConfig:**
328+ - ` size ` : Maximum queue size (default: 100)
329+ - ` overflow ` : Overflow policy - ` 'oldest' ` (drop oldest), ` 'newest' ` (drop newest), or ` 'error' ` (throw error)
308330
309331#### LeaderElector Methods
310332
@@ -325,9 +347,9 @@ Returns `true` if this tab is currently the leader.
325347Subscribes to a specific leader event. Returns an unsubscribe function.
326348
327349Events:
328- - ` 'acquired ' ` - This tab became the leader
329- - ` 'lost ' ` - This tab lost leadership
330- - ` 'changed ' ` - Leadership changed to another tab
350+ - ` 'acquire ' ` - This tab became the leader
351+ - ` 'lose ' ` - This tab lost leadership
352+ - ` 'change ' ` - Leadership changed to another tab
331353
332354##### ` onAll(handler) `
333355
@@ -363,13 +385,13 @@ const leader = createLeaderElector({
363385leader .start ();
364386
365387// Handle leader events
366- leader .on (' acquired ' , () => {
388+ leader .on (' acquire ' , () => {
367389 console .log (' This tab is now the leader' );
368390 // Only the leader performs certain tasks
369391 bus .publish (' leader-announcement' , { tabId: leader .getTabId () });
370392});
371393
372- leader .on (' lost ' , () => {
394+ leader .on (' lose ' , () => {
373395 console .log (' This tab is no longer the leader' );
374396});
375397
@@ -391,7 +413,9 @@ leader.stop();
391413bus .close ();
392414```
393415
394- ### Using with purrcat (WebSocket)
416+ ### Using with [ purrcat] ( https://www.npmjs.com/package/purrcat ) (WebSocket)
417+
418+ > ** [ 📖 Full WebSocket Usage Guide →] ( ./docs/WEBSOCKET-USAGE.md ) ** - Learn how to share WebSocket connections across tabs, implement request-response patterns, and handle edge cases.
395419
396420``` typescript
397421import createSocket from ' purrcat' ;
@@ -406,7 +430,7 @@ const leader = createLeaderElector({
406430leader .start ();
407431
408432// Only the leader maintains WebSocket connection
409- leader .on (' acquired ' , () => {
433+ leader .on (' acquire ' , () => {
410434 const socket = createSocket ({
411435 url: ' wss://api.example.com/ws' ,
412436 });
0 commit comments