Skip to content

Commit f91375c

Browse files
authored
Refactor/simplify event names and cleanup (#4)
* docs: add demo page and GitHub Pages deployment * docs: remove GitHub Actions pages workflow (using branch-based deployment) * refactor: simplify event names and cleanup - Change event names from past tense to present tense (acquired → acquire, lost → lose, changed → change) - Remove unused generateRequestId function - Update file size documentation (6KB → 7KB, 2KB → 3KB) - Fix failing tests by setting activeIterators correctly - Add WebSocket usage guide link in README - Update architecture documentation - Remove RPC-related code * refactor: improve code quality and add automatic cleanup - Add automatic lease cleanup on tab close (pagehide/beforeunload events) - Extract executeCallbacks helper function to reduce code duplication - Move MessageQueue and EventQueue interfaces to types.ts for consistency - Use queueMicrotask instead of setTimeout(0) for better performance - Apply early return pattern to reduce nesting - Add tests for page unload handlers and executeCallbacks - Improve test coverage to 95.91% statements, 90.64% branch
1 parent c957e5f commit f91375c

18 files changed

Lines changed: 2669 additions & 722 deletions

README.md

Lines changed: 83 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -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
141153
leader.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
149212
const 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.
325347
Subscribes to a specific leader event. Returns an unsubscribe function.
326348

327349
Events:
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({
363385
leader.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();
391413
bus.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
397421
import createSocket from 'purrcat';
@@ -406,7 +430,7 @@ const leader = createLeaderElector({
406430
leader.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

Comments
 (0)