purrtabby 0.1.0 → 0.1.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 CHANGED
@@ -14,7 +14,7 @@ A lightweight library for cross-tab communication and leader election in browser
14
14
 
15
15
  ## Highlights
16
16
 
17
- **Microscopic**: weighs less than 6KB minified (~2KB gzipped)
17
+ **Microscopic**: weighs less than 7KB minified (~3KB gzipped)
18
18
 
19
19
  **Reliable**: leader election with lease-based heartbeat mechanism
20
20
 
@@ -26,6 +26,18 @@ A lightweight library for cross-tab communication and leader election in browser
26
26
 
27
27
  **Flexible**: callback-based or generator-based APIs, your choice
28
28
 
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
+
29
41
  ## Features
30
42
 
31
43
  - **Cross-tab communication** using BroadcastChannel
@@ -125,15 +137,15 @@ if (leader.isLeader()) {
125
137
  }
126
138
 
127
139
  // Listen for leader events
128
- leader.on('acquired', () => {
140
+ leader.on('acquire', () => {
129
141
  console.log('Became the leader!');
130
142
  });
131
143
 
132
- leader.on('lost', (event) => {
144
+ leader.on('lose', (event) => {
133
145
  console.log('Lost leadership:', event.meta?.newLeader);
134
146
  });
135
147
 
136
- leader.on('changed', (event) => {
148
+ leader.on('change', (event) => {
137
149
  console.log('Leadership changed to:', event.meta?.newLeader);
138
150
  });
139
151
 
@@ -141,11 +153,62 @@ leader.on('changed', (event) => {
141
153
  leader.stop();
142
154
  ```
143
155
 
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
145
164
 
146
165
  ```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
148
210
 
211
+ ```typescript
149
212
  const bus = createBus({ channel: 'my-channel' });
150
213
 
151
214
  // Consume messages as async iterable
@@ -185,52 +248,6 @@ const controller = new AbortController();
185
248
  })();
186
249
  ```
187
250
 
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
-
234
251
  ## API
235
252
 
236
253
  ### `createBus(options)`
@@ -305,6 +322,11 @@ Creates a new LeaderElector instance for leader election.
305
322
  | `leaseMs` | `number` | `5000` | Lease duration in milliseconds |
306
323
  | `heartbeatMs` | `number` | `2000` | Heartbeat interval in milliseconds |
307
324
  | `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)
308
330
 
309
331
  #### LeaderElector Methods
310
332
 
@@ -325,9 +347,9 @@ Returns `true` if this tab is currently the leader.
325
347
  Subscribes to a specific leader event. Returns an unsubscribe function.
326
348
 
327
349
  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
331
353
 
332
354
  ##### `onAll(handler)`
333
355
 
@@ -363,13 +385,13 @@ const leader = createLeaderElector({
363
385
  leader.start();
364
386
 
365
387
  // Handle leader events
366
- leader.on('acquired', () => {
388
+ leader.on('acquire', () => {
367
389
  console.log('This tab is now the leader');
368
390
  // Only the leader performs certain tasks
369
391
  bus.publish('leader-announcement', { tabId: leader.getTabId() });
370
392
  });
371
393
 
372
- leader.on('lost', () => {
394
+ leader.on('lose', () => {
373
395
  console.log('This tab is no longer the leader');
374
396
  });
375
397
 
@@ -391,7 +413,9 @@ leader.stop();
391
413
  bus.close();
392
414
  ```
393
415
 
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.
395
419
 
396
420
  ```typescript
397
421
  import createSocket from 'purrcat';
@@ -406,7 +430,7 @@ const leader = createLeaderElector({
406
430
  leader.start();
407
431
 
408
432
  // Only the leader maintains WebSocket connection
409
- leader.on('acquired', () => {
433
+ leader.on('acquire', () => {
410
434
  const socket = createSocket({
411
435
  url: 'wss://api.example.com/ws',
412
436
  });
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var T=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var G=(e,r)=>{for(var a in r)T(e,a,{get:r[a],enumerable:!0})},J=(e,r,a,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of O(r))!$.call(e,s)&&s!==a&&T(e,s,{get:()=>r[s],enumerable:!(n=j(r,s))||n.enumerable});return e};var N=e=>J(T({},"__esModule",{value:!0}),e);var U={};G(U,{createBus:()=>L,createLeaderElector:()=>g,createRPC:()=>I,default:()=>K});module.exports=N(U);function C(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function w(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function k(e,r){return{type:e,ts:Date.now(),meta:r}}function p(e,r){return{type:e,ts:Date.now(),meta:r}}function y(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function m(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function h(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function B(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function v(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function E(e,r,a,n,s){return new Promise(o=>{let t=null,c=!1,l=()=>{c||(e&&e.removeEventListener("abort",i),s(d),t!==null&&(clearTimeout(t),t=null))},d=()=>{c||(c=!0,l(),o())},i=()=>d();if(e?.aborted){d();return}if(e&&e.addEventListener("abort",i),r()){d();return}if(n(d),!e){let u=()=>{if(!c){if(r()){d();return}t=setTimeout(u,100)}};u()}})}async function*S(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await E(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*M(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await E(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function P(e,r,a,n){let s=a.messageCallbacks.get(e.type);s&&s.forEach(o=>{try{o(e)}catch(t){console.error("Error in TabBus callback:",t)}}),a.allCallbacks.forEach(o=>{try{o(e)}catch(t){console.error("Error in TabBus all callback:",t)}}),n.push(e),a.messageResolvers.forEach(o=>o()),a.messageResolvers.clear()}function F(e,r,a,n){try{let s=e.data;P(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function L(e){let{channel:r,tabId:a}=e,n=a||C();if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let s=new BroadcastChannel(r),o={channel:s,tabId:n,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0},t=[];return s.onmessage=l=>{F(l,n,o,t)},s.onmessageerror=()=>{let l=k("err",{error:"Failed to receive message"})},{publish(l,d){let i={type:l,payload:d,tabId:n,ts:Date.now()};s.postMessage(i),setTimeout(()=>{P(i,n,o,t)},0)},subscribe(l,d){return o.messageCallbacks.has(l)||o.messageCallbacks.set(l,new Set),o.messageCallbacks.get(l).add(d),()=>{let i=o.messageCallbacks.get(l);i&&(i.delete(d),i.size===0&&o.messageCallbacks.delete(l))}},subscribeAll(l){return o.allCallbacks.add(l),()=>{o.allCallbacks.delete(l)}},stream(l){return S(o,{messages:t},l?.signal)},getTabId(){return n},close(){s.close(),o.channel=null,o.messageCallbacks.clear(),o.allCallbacks.clear(),o.messageResolvers.clear()}}}function b(e,r,a){let n=r.eventCallbacks.get(e.type);n&&n.forEach(s=>{try{s(e)}catch(o){console.error("Error in LeaderElector callback:",o)}}),r.allCallbacks.forEach(s=>{try{s(e)}catch(o){console.error("Error in LeaderElector all callback:",o)}}),a.push(e),r.eventResolvers.forEach(s=>s()),r.eventResolvers.clear()}function z(e,r){if(e.stopped)return!1;let a=m(e.key);if(!v(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(h(e.key,n),m(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,b(p("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&v(a)?(e.isLeader||(e.isLeader=!0,b(p("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,b(p("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function H(e,r){if(e.stopped||!e.isLeader)return;let a=m(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};h(e.key,n)}else e.isLeader&&(e.isLeader=!1,b(p("lose",{tabId:e.tabId}),e,r))}function x(e,r){if(e.stopped)return;let a=m(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&v(a);!n&&s?(e.isLeader=!0,b(p("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,b(p("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&b(p("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function g(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:o=500}=e;if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:o,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1},c=[];function l(i){i.key!==t.key||i.storageArea!==localStorage||x(t,c)}return typeof window<"u"&&window.addEventListener("storage",l),{start(){if(t.stopped&&(t.stopped=!1),z(t,c),t.isLeader){let u=y(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{H(t,c)},u)}let i=y(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{x(t,c)},i)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(m(t.key)?.tabId===t.tabId&&B(t.key),t.isLeader=!1,b(p("lose",{tabId:t.tabId,reason:"stopped"}),t,c)),typeof window<"u"&&window.removeEventListener("storage",l)},isLeader(){return t.isLeader},on(i,u){return t.eventCallbacks.has(i)||t.eventCallbacks.set(i,new Set),t.eventCallbacks.get(i).add(u),()=>{let f=t.eventCallbacks.get(i);f&&(f.delete(u),f.size===0&&t.eventCallbacks.delete(i))}},onAll(i){return t.allCallbacks.add(i),()=>{t.allCallbacks.delete(i)}},stream(i){return M(t,{events:c},i?.signal)},getTabId(){return t.tabId}}}function Q(e,r,a){if(e.tabId===a)return;let n=r.handlers.get(e.method);if(!n){let s={type:"rpc-response",requestId:e.requestId,error:`No handler found for method: ${e.method}`,tabId:a,ts:Date.now()};try{r.bus.publish("rpc-response",s)}catch{}return}Promise.resolve(n(e.params)).then(s=>{let o={type:"rpc-response",requestId:e.requestId,result:s,tabId:a,ts:Date.now()};try{r.bus.publish("rpc-response",o)}catch{}}).catch(s=>{let o={type:"rpc-response",requestId:e.requestId,error:s instanceof Error?s.message:String(s),tabId:a,ts:Date.now()};try{r.bus.publish("rpc-response",o)}catch{}})}function V(e,r,a){let n=r.pendingRequests.get(e.requestId);n&&(clearTimeout(n.timeout),r.pendingRequests.delete(e.requestId),e.error?n.reject(new Error(e.error)):n.resolve(e.result))}function I(e){let{bus:r,timeout:a=5e3}=e,n=r.getTabId(),s={bus:r,timeout:a,pendingRequests:new Map,handlers:new Map},o=r.subscribe("rpc-request",l=>{let d=l.payload;d&&d.type==="rpc-request"&&Q(d,s,n)}),t=r.subscribe("rpc-response",l=>{let d=l.payload;d&&d.type==="rpc-response"&&V(d,s,n)});return{call(l,d,i){let u=w(),f=i?.timeout??s.timeout;return new Promise((q,R)=>{let A=setTimeout(()=>{s.pendingRequests.delete(u),R(new Error(`RPC call timeout: ${l} (${f}ms)`))},f);s.pendingRequests.set(u,{resolve:q,reject:R,timeout:A});let D={type:"rpc-request",method:l,params:d,requestId:u,tabId:n,ts:Date.now()};r.publish("rpc-request",D)})},handle(l,d){return s.handlers.set(l,d),()=>{s.handlers.delete(l)}},close(){s.pendingRequests.forEach(l=>{clearTimeout(l.timeout),l.reject(new Error("RPC closed"))}),s.pendingRequests.clear(),s.handlers.clear(),o(),t()}}}var K={createBus:L,createLeaderElector:g,createRPC:I};0&&(module.exports={createBus,createLeaderElector,createRPC});
1
+ "use strict";var k=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var P=(e,r)=>{for(var a in r)k(e,a,{get:r[a],enumerable:!0})},J=(e,r,a,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of D(r))!G.call(e,s)&&s!==a&&k(e,s,{get:()=>r[s],enumerable:!(n=_(r,s))||n.enumerable});return e};var F=e=>J(k({},"__esModule",{value:!0}),e);var H={};P(H,{createBus:()=>E,createLeaderElector:()=>y,default:()=>q});module.exports=F(H);function m(e,r,a){e&&e.forEach(n=>{try{n(r)}catch(s){console.error(a,s)}})}function x(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function O(e,r){return{type:e,ts:Date.now(),meta:r}}function b(e,r){return{type:e,ts:Date.now(),meta:r}}function B(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function S(e,r,a,n,s){return new Promise(l=>{if(e?.aborted){l();return}if(r()){l();return}let u=()=>{s(l),e&&e.removeEventListener("abort",c)},c=()=>{u(),l()};e&&e.addEventListener("abort",c),n(()=>{u(),l()})})}function g(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function M(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function C(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function T(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function w(e,r,a,n){return r.length<n?{action:"add"}:e==="oldest"?{action:"drop_oldest",dropped:r.shift()}:e==="newest"?{action:"drop_newest",dropped:a}:{action:"error"}}async function*R(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await S(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*A(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await S(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function j(e,r,a,n){let s=a.messageCallbacks.get(e.type);if(m(s,e,"Error in TabBus callback:"),m(a.allCallbacks,e,"Error in TabBus all callback:"),a.activeIterators===0)return;let l=w(a.bufferOverflow,n,e,a.bufferSize);if(l.action==="error"){console.error("Message buffer overflow");return}l.action!=="drop_newest"&&(l.action,n.push(e),a.messageResolvers.forEach(u=>u()),a.messageResolvers.clear())}function N(e,r,a,n){try{let s=e.data;j(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function E(e){let{channel:r,tabId:a,buffer:n}=e,s=a||x(),l=n?.size??100,u=n?.overflow??"oldest";if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let c=new BroadcastChannel(r),i={channel:c,tabId:s,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0,bufferSize:l,bufferOverflow:u},t=[];return c.onmessage=o=>{N(o,s,i,t)},c.onmessageerror=()=>{let o=O("err",{error:"Failed to receive message"})},{publish(o,f){let p={type:o,payload:f,tabId:s,ts:Date.now()};c.postMessage(p),queueMicrotask(()=>{j(p,s,i,t)})},subscribe(o,f){return i.messageCallbacks.has(o)||i.messageCallbacks.set(o,new Set),i.messageCallbacks.get(o).add(f),()=>{let p=i.messageCallbacks.get(o);p&&(p.delete(f),p.size===0&&i.messageCallbacks.delete(o))}},subscribeAll(o){return i.allCallbacks.add(o),()=>{i.allCallbacks.delete(o)}},stream(o){return R(i,{messages:t},o?.signal)},getTabId(){return s},close(){c.close(),i.channel=null,i.messageCallbacks.clear(),i.allCallbacks.clear(),i.messageResolvers.clear()}}}function v(e,r,a){let n=r.eventCallbacks.get(e.type);if(m(n,e,"Error in LeaderElector callback:"),m(r.allCallbacks,e,"Error in LeaderElector all callback:"),r.activeIterators===0)return;let s=w(r.bufferOverflow,a,e,r.bufferSize);if(s.action==="error"){console.error("Event buffer overflow");return}s.action!=="drop_newest"&&(s.action,a.push(e),r.eventResolvers.forEach(l=>l()),r.eventResolvers.clear())}function V(e,r){if(e.stopped)return!1;let a=g(e.key);if(!T(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(M(e.key,n),g(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&T(a)?(e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function $(e,r){if(e.stopped||!e.isLeader)return;let a=g(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};M(e.key,n)}else e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId}),e,r))}function z(e,r){if(e.stopped)return;let a=g(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&T(a);!n&&s?(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&v(b("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function y(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:l=500,buffer:u}=e,c=u?.size??100,i=u?.overflow??"oldest";if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:l,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1,bufferSize:c,bufferOverflow:i},L=[];typeof window<"u"&&(window.addEventListener("storage",o),window.addEventListener("pagehide",f),window.addEventListener("beforeunload",f));function o(d){d.key!==t.key||d.storageArea!==localStorage||z(t,L)}function f(){t.isLeader&&g(t.key)?.tabId===t.tabId&&C(t.key)}return{start(){if(t.stopped&&(t.stopped=!1),V(t,L),t.isLeader){let I=B(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{$(t,L)},I)}let d=B(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{z(t,L)},d)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(g(t.key)?.tabId===t.tabId&&C(t.key),t.isLeader=!1,v(b("lose",{tabId:t.tabId,reason:"stopped"}),t,L)),typeof window<"u"&&(window.removeEventListener("storage",o),window.removeEventListener("pagehide",f),window.removeEventListener("beforeunload",f))},isLeader(){return t.isLeader},on(d,I){return t.eventCallbacks.has(d)||t.eventCallbacks.set(d,new Set),t.eventCallbacks.get(d).add(I),()=>{let h=t.eventCallbacks.get(d);h&&(h.delete(I),h.size===0&&t.eventCallbacks.delete(d))}},onAll(d){return t.allCallbacks.add(d),()=>{t.allCallbacks.delete(d)}},stream(d){return A(t,{events:L},d?.signal)},getTabId(){return t.tabId}}}var q={createBus:E,createLeaderElector:y};0&&(module.exports={createBus,createLeaderElector});
package/dist/index.d.cts CHANGED
@@ -13,10 +13,18 @@ interface TabBusEvent {
13
13
  ts: number;
14
14
  meta?: Record<string, any>;
15
15
  }
16
+ /** Buffer overflow policy */
17
+ type BufferOverflowPolicy = 'oldest' | 'newest' | 'error';
18
+ /** Buffer configuration */
19
+ interface BufferConfig {
20
+ size?: number;
21
+ overflow?: BufferOverflowPolicy;
22
+ }
16
23
  /** Options for createBus() */
17
24
  interface BusOptions {
18
25
  channel: string;
19
26
  tabId?: string;
27
+ buffer?: BufferConfig;
20
28
  }
21
29
  /** Return type of createBus(), provides async iterables and callbacks */
22
30
  interface TabBus<T = any> {
@@ -50,6 +58,7 @@ interface LeaderElectorOptions {
50
58
  leaseMs?: number;
51
59
  heartbeatMs?: number;
52
60
  jitterMs?: number;
61
+ buffer?: BufferConfig;
53
62
  }
54
63
  /** Return type of createLeaderElector() */
55
64
  interface LeaderElector {
@@ -70,40 +79,6 @@ interface LeaderElector {
70
79
  /** Get the current tab ID */
71
80
  getTabId(): string;
72
81
  }
73
- /** RPC request message */
74
- interface RPCRequest<T = any> {
75
- type: 'rpc-request';
76
- method: string;
77
- params?: T;
78
- requestId: string;
79
- tabId: string;
80
- ts: number;
81
- }
82
- /** RPC response message */
83
- interface RPCResponse<T = any> {
84
- type: 'rpc-response';
85
- requestId: string;
86
- result?: T;
87
- error?: string;
88
- tabId: string;
89
- ts: number;
90
- }
91
- /** Options for createRPC() */
92
- interface RPCOptions {
93
- bus: TabBus;
94
- timeout?: number;
95
- }
96
- /** Return type of createRPC() */
97
- interface RPC {
98
- /** Call a method on the leader (or any tab) */
99
- call<TParams = any, TResult = any>(method: string, params?: TParams, options?: {
100
- timeout?: number;
101
- }): Promise<TResult>;
102
- /** Handle incoming RPC requests */
103
- handle<TParams = any, TResult = any>(method: string, handler: (params?: TParams) => Promise<TResult> | TResult): () => void;
104
- /** Close the RPC instance and cleanup */
105
- close(): void;
106
- }
107
82
 
108
83
  /**
109
84
  * Create a TabBus instance for cross-tab communication
@@ -119,17 +94,9 @@ declare function createBus<T = any>(options: BusOptions): TabBus<T>;
119
94
  */
120
95
  declare function createLeaderElector(options: LeaderElectorOptions): LeaderElector;
121
96
 
122
- /**
123
- * Create an RPC instance for request-response communication
124
- * @param options - RPC configuration options
125
- * @returns RPC instance
126
- */
127
- declare function createRPC(options: RPCOptions): RPC;
128
-
129
97
  declare const _default: {
130
98
  createBus: typeof createBus;
131
99
  createLeaderElector: typeof createLeaderElector;
132
- createRPC: typeof createRPC;
133
100
  };
134
101
 
135
- export { type BusOptions, createBus as CreateBus, createLeaderElector as CreateLeaderElector, createRPC as CreateRPC, type LeaderElector, type LeaderElectorOptions, type LeaderEvent, type LeaderEventType, type RPC, type RPCOptions, type RPCRequest, type RPCResponse, type TabBus, type TabBusEvent, type TabBusEventType, type TabBusMessage, createBus, createLeaderElector, createRPC, _default as default };
102
+ export { type BusOptions, createBus as CreateBus, createLeaderElector as CreateLeaderElector, type LeaderElector, type LeaderElectorOptions, type LeaderEvent, type LeaderEventType, type TabBus, type TabBusEvent, type TabBusEventType, type TabBusMessage, createBus, createLeaderElector, _default as default };
package/dist/index.d.ts CHANGED
@@ -13,10 +13,18 @@ interface TabBusEvent {
13
13
  ts: number;
14
14
  meta?: Record<string, any>;
15
15
  }
16
+ /** Buffer overflow policy */
17
+ type BufferOverflowPolicy = 'oldest' | 'newest' | 'error';
18
+ /** Buffer configuration */
19
+ interface BufferConfig {
20
+ size?: number;
21
+ overflow?: BufferOverflowPolicy;
22
+ }
16
23
  /** Options for createBus() */
17
24
  interface BusOptions {
18
25
  channel: string;
19
26
  tabId?: string;
27
+ buffer?: BufferConfig;
20
28
  }
21
29
  /** Return type of createBus(), provides async iterables and callbacks */
22
30
  interface TabBus<T = any> {
@@ -50,6 +58,7 @@ interface LeaderElectorOptions {
50
58
  leaseMs?: number;
51
59
  heartbeatMs?: number;
52
60
  jitterMs?: number;
61
+ buffer?: BufferConfig;
53
62
  }
54
63
  /** Return type of createLeaderElector() */
55
64
  interface LeaderElector {
@@ -70,40 +79,6 @@ interface LeaderElector {
70
79
  /** Get the current tab ID */
71
80
  getTabId(): string;
72
81
  }
73
- /** RPC request message */
74
- interface RPCRequest<T = any> {
75
- type: 'rpc-request';
76
- method: string;
77
- params?: T;
78
- requestId: string;
79
- tabId: string;
80
- ts: number;
81
- }
82
- /** RPC response message */
83
- interface RPCResponse<T = any> {
84
- type: 'rpc-response';
85
- requestId: string;
86
- result?: T;
87
- error?: string;
88
- tabId: string;
89
- ts: number;
90
- }
91
- /** Options for createRPC() */
92
- interface RPCOptions {
93
- bus: TabBus;
94
- timeout?: number;
95
- }
96
- /** Return type of createRPC() */
97
- interface RPC {
98
- /** Call a method on the leader (or any tab) */
99
- call<TParams = any, TResult = any>(method: string, params?: TParams, options?: {
100
- timeout?: number;
101
- }): Promise<TResult>;
102
- /** Handle incoming RPC requests */
103
- handle<TParams = any, TResult = any>(method: string, handler: (params?: TParams) => Promise<TResult> | TResult): () => void;
104
- /** Close the RPC instance and cleanup */
105
- close(): void;
106
- }
107
82
 
108
83
  /**
109
84
  * Create a TabBus instance for cross-tab communication
@@ -119,17 +94,9 @@ declare function createBus<T = any>(options: BusOptions): TabBus<T>;
119
94
  */
120
95
  declare function createLeaderElector(options: LeaderElectorOptions): LeaderElector;
121
96
 
122
- /**
123
- * Create an RPC instance for request-response communication
124
- * @param options - RPC configuration options
125
- * @returns RPC instance
126
- */
127
- declare function createRPC(options: RPCOptions): RPC;
128
-
129
97
  declare const _default: {
130
98
  createBus: typeof createBus;
131
99
  createLeaderElector: typeof createLeaderElector;
132
- createRPC: typeof createRPC;
133
100
  };
134
101
 
135
- export { type BusOptions, createBus as CreateBus, createLeaderElector as CreateLeaderElector, createRPC as CreateRPC, type LeaderElector, type LeaderElectorOptions, type LeaderEvent, type LeaderEventType, type RPC, type RPCOptions, type RPCRequest, type RPCResponse, type TabBus, type TabBusEvent, type TabBusEventType, type TabBusMessage, createBus, createLeaderElector, createRPC, _default as default };
102
+ export { type BusOptions, createBus as CreateBus, createLeaderElector as CreateLeaderElector, type LeaderElector, type LeaderElectorOptions, type LeaderEvent, type LeaderEventType, type TabBus, type TabBusEvent, type TabBusEventType, type TabBusMessage, createBus, createLeaderElector, _default as default };
@@ -1 +1 @@
1
- "use strict";var purrtabby=(()=>{var T=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var G=(e,r)=>{for(var a in r)T(e,a,{get:r[a],enumerable:!0})},J=(e,r,a,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of O(r))!$.call(e,s)&&s!==a&&T(e,s,{get:()=>r[s],enumerable:!(n=j(r,s))||n.enumerable});return e};var N=e=>J(T({},"__esModule",{value:!0}),e);var U={};G(U,{createBus:()=>L,createLeaderElector:()=>g,createRPC:()=>I,default:()=>K});function C(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function w(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function k(e,r){return{type:e,ts:Date.now(),meta:r}}function p(e,r){return{type:e,ts:Date.now(),meta:r}}function y(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function m(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function h(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function B(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function v(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function E(e,r,a,n,s){return new Promise(o=>{let t=null,c=!1,l=()=>{c||(e&&e.removeEventListener("abort",i),s(d),t!==null&&(clearTimeout(t),t=null))},d=()=>{c||(c=!0,l(),o())},i=()=>d();if(e?.aborted){d();return}if(e&&e.addEventListener("abort",i),r()){d();return}if(n(d),!e){let u=()=>{if(!c){if(r()){d();return}t=setTimeout(u,100)}};u()}})}async function*S(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await E(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*M(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await E(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function P(e,r,a,n){let s=a.messageCallbacks.get(e.type);s&&s.forEach(o=>{try{o(e)}catch(t){console.error("Error in TabBus callback:",t)}}),a.allCallbacks.forEach(o=>{try{o(e)}catch(t){console.error("Error in TabBus all callback:",t)}}),n.push(e),a.messageResolvers.forEach(o=>o()),a.messageResolvers.clear()}function F(e,r,a,n){try{let s=e.data;P(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function L(e){let{channel:r,tabId:a}=e,n=a||C();if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let s=new BroadcastChannel(r),o={channel:s,tabId:n,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0},t=[];return s.onmessage=l=>{F(l,n,o,t)},s.onmessageerror=()=>{let l=k("err",{error:"Failed to receive message"})},{publish(l,d){let i={type:l,payload:d,tabId:n,ts:Date.now()};s.postMessage(i),setTimeout(()=>{P(i,n,o,t)},0)},subscribe(l,d){return o.messageCallbacks.has(l)||o.messageCallbacks.set(l,new Set),o.messageCallbacks.get(l).add(d),()=>{let i=o.messageCallbacks.get(l);i&&(i.delete(d),i.size===0&&o.messageCallbacks.delete(l))}},subscribeAll(l){return o.allCallbacks.add(l),()=>{o.allCallbacks.delete(l)}},stream(l){return S(o,{messages:t},l?.signal)},getTabId(){return n},close(){s.close(),o.channel=null,o.messageCallbacks.clear(),o.allCallbacks.clear(),o.messageResolvers.clear()}}}function b(e,r,a){let n=r.eventCallbacks.get(e.type);n&&n.forEach(s=>{try{s(e)}catch(o){console.error("Error in LeaderElector callback:",o)}}),r.allCallbacks.forEach(s=>{try{s(e)}catch(o){console.error("Error in LeaderElector all callback:",o)}}),a.push(e),r.eventResolvers.forEach(s=>s()),r.eventResolvers.clear()}function z(e,r){if(e.stopped)return!1;let a=m(e.key);if(!v(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(h(e.key,n),m(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,b(p("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&v(a)?(e.isLeader||(e.isLeader=!0,b(p("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,b(p("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function H(e,r){if(e.stopped||!e.isLeader)return;let a=m(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};h(e.key,n)}else e.isLeader&&(e.isLeader=!1,b(p("lose",{tabId:e.tabId}),e,r))}function x(e,r){if(e.stopped)return;let a=m(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&v(a);!n&&s?(e.isLeader=!0,b(p("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,b(p("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&b(p("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function g(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:o=500}=e;if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:o,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1},c=[];function l(i){i.key!==t.key||i.storageArea!==localStorage||x(t,c)}return typeof window<"u"&&window.addEventListener("storage",l),{start(){if(t.stopped&&(t.stopped=!1),z(t,c),t.isLeader){let u=y(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{H(t,c)},u)}let i=y(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{x(t,c)},i)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(m(t.key)?.tabId===t.tabId&&B(t.key),t.isLeader=!1,b(p("lose",{tabId:t.tabId,reason:"stopped"}),t,c)),typeof window<"u"&&window.removeEventListener("storage",l)},isLeader(){return t.isLeader},on(i,u){return t.eventCallbacks.has(i)||t.eventCallbacks.set(i,new Set),t.eventCallbacks.get(i).add(u),()=>{let f=t.eventCallbacks.get(i);f&&(f.delete(u),f.size===0&&t.eventCallbacks.delete(i))}},onAll(i){return t.allCallbacks.add(i),()=>{t.allCallbacks.delete(i)}},stream(i){return M(t,{events:c},i?.signal)},getTabId(){return t.tabId}}}function Q(e,r,a){if(e.tabId===a)return;let n=r.handlers.get(e.method);if(!n){let s={type:"rpc-response",requestId:e.requestId,error:`No handler found for method: ${e.method}`,tabId:a,ts:Date.now()};try{r.bus.publish("rpc-response",s)}catch{}return}Promise.resolve(n(e.params)).then(s=>{let o={type:"rpc-response",requestId:e.requestId,result:s,tabId:a,ts:Date.now()};try{r.bus.publish("rpc-response",o)}catch{}}).catch(s=>{let o={type:"rpc-response",requestId:e.requestId,error:s instanceof Error?s.message:String(s),tabId:a,ts:Date.now()};try{r.bus.publish("rpc-response",o)}catch{}})}function V(e,r,a){let n=r.pendingRequests.get(e.requestId);n&&(clearTimeout(n.timeout),r.pendingRequests.delete(e.requestId),e.error?n.reject(new Error(e.error)):n.resolve(e.result))}function I(e){let{bus:r,timeout:a=5e3}=e,n=r.getTabId(),s={bus:r,timeout:a,pendingRequests:new Map,handlers:new Map},o=r.subscribe("rpc-request",l=>{let d=l.payload;d&&d.type==="rpc-request"&&Q(d,s,n)}),t=r.subscribe("rpc-response",l=>{let d=l.payload;d&&d.type==="rpc-response"&&V(d,s,n)});return{call(l,d,i){let u=w(),f=i?.timeout??s.timeout;return new Promise((q,R)=>{let A=setTimeout(()=>{s.pendingRequests.delete(u),R(new Error(`RPC call timeout: ${l} (${f}ms)`))},f);s.pendingRequests.set(u,{resolve:q,reject:R,timeout:A});let D={type:"rpc-request",method:l,params:d,requestId:u,tabId:n,ts:Date.now()};r.publish("rpc-request",D)})},handle(l,d){return s.handlers.set(l,d),()=>{s.handlers.delete(l)}},close(){s.pendingRequests.forEach(l=>{clearTimeout(l.timeout),l.reject(new Error("RPC closed"))}),s.pendingRequests.clear(),s.handlers.clear(),o(),t()}}}var K={createBus:L,createLeaderElector:g,createRPC:I};return N(U);})();
1
+ "use strict";var purrtabby=(()=>{var k=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var P=(e,r)=>{for(var a in r)k(e,a,{get:r[a],enumerable:!0})},J=(e,r,a,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of D(r))!G.call(e,s)&&s!==a&&k(e,s,{get:()=>r[s],enumerable:!(n=_(r,s))||n.enumerable});return e};var F=e=>J(k({},"__esModule",{value:!0}),e);var H={};P(H,{createBus:()=>E,createLeaderElector:()=>y,default:()=>q});function m(e,r,a){e&&e.forEach(n=>{try{n(r)}catch(s){console.error(a,s)}})}function x(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function O(e,r){return{type:e,ts:Date.now(),meta:r}}function b(e,r){return{type:e,ts:Date.now(),meta:r}}function B(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function S(e,r,a,n,s){return new Promise(l=>{if(e?.aborted){l();return}if(r()){l();return}let u=()=>{s(l),e&&e.removeEventListener("abort",c)},c=()=>{u(),l()};e&&e.addEventListener("abort",c),n(()=>{u(),l()})})}function g(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function M(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function C(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function T(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function w(e,r,a,n){return r.length<n?{action:"add"}:e==="oldest"?{action:"drop_oldest",dropped:r.shift()}:e==="newest"?{action:"drop_newest",dropped:a}:{action:"error"}}async function*R(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await S(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*A(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await S(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function j(e,r,a,n){let s=a.messageCallbacks.get(e.type);if(m(s,e,"Error in TabBus callback:"),m(a.allCallbacks,e,"Error in TabBus all callback:"),a.activeIterators===0)return;let l=w(a.bufferOverflow,n,e,a.bufferSize);if(l.action==="error"){console.error("Message buffer overflow");return}l.action!=="drop_newest"&&(l.action,n.push(e),a.messageResolvers.forEach(u=>u()),a.messageResolvers.clear())}function N(e,r,a,n){try{let s=e.data;j(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function E(e){let{channel:r,tabId:a,buffer:n}=e,s=a||x(),l=n?.size??100,u=n?.overflow??"oldest";if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let c=new BroadcastChannel(r),i={channel:c,tabId:s,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0,bufferSize:l,bufferOverflow:u},t=[];return c.onmessage=o=>{N(o,s,i,t)},c.onmessageerror=()=>{let o=O("err",{error:"Failed to receive message"})},{publish(o,f){let p={type:o,payload:f,tabId:s,ts:Date.now()};c.postMessage(p),queueMicrotask(()=>{j(p,s,i,t)})},subscribe(o,f){return i.messageCallbacks.has(o)||i.messageCallbacks.set(o,new Set),i.messageCallbacks.get(o).add(f),()=>{let p=i.messageCallbacks.get(o);p&&(p.delete(f),p.size===0&&i.messageCallbacks.delete(o))}},subscribeAll(o){return i.allCallbacks.add(o),()=>{i.allCallbacks.delete(o)}},stream(o){return R(i,{messages:t},o?.signal)},getTabId(){return s},close(){c.close(),i.channel=null,i.messageCallbacks.clear(),i.allCallbacks.clear(),i.messageResolvers.clear()}}}function v(e,r,a){let n=r.eventCallbacks.get(e.type);if(m(n,e,"Error in LeaderElector callback:"),m(r.allCallbacks,e,"Error in LeaderElector all callback:"),r.activeIterators===0)return;let s=w(r.bufferOverflow,a,e,r.bufferSize);if(s.action==="error"){console.error("Event buffer overflow");return}s.action!=="drop_newest"&&(s.action,a.push(e),r.eventResolvers.forEach(l=>l()),r.eventResolvers.clear())}function V(e,r){if(e.stopped)return!1;let a=g(e.key);if(!T(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(M(e.key,n),g(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&T(a)?(e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function $(e,r){if(e.stopped||!e.isLeader)return;let a=g(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};M(e.key,n)}else e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId}),e,r))}function z(e,r){if(e.stopped)return;let a=g(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&T(a);!n&&s?(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&v(b("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function y(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:l=500,buffer:u}=e,c=u?.size??100,i=u?.overflow??"oldest";if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:l,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1,bufferSize:c,bufferOverflow:i},L=[];typeof window<"u"&&(window.addEventListener("storage",o),window.addEventListener("pagehide",f),window.addEventListener("beforeunload",f));function o(d){d.key!==t.key||d.storageArea!==localStorage||z(t,L)}function f(){t.isLeader&&g(t.key)?.tabId===t.tabId&&C(t.key)}return{start(){if(t.stopped&&(t.stopped=!1),V(t,L),t.isLeader){let I=B(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{$(t,L)},I)}let d=B(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{z(t,L)},d)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(g(t.key)?.tabId===t.tabId&&C(t.key),t.isLeader=!1,v(b("lose",{tabId:t.tabId,reason:"stopped"}),t,L)),typeof window<"u"&&(window.removeEventListener("storage",o),window.removeEventListener("pagehide",f),window.removeEventListener("beforeunload",f))},isLeader(){return t.isLeader},on(d,I){return t.eventCallbacks.has(d)||t.eventCallbacks.set(d,new Set),t.eventCallbacks.get(d).add(I),()=>{let h=t.eventCallbacks.get(d);h&&(h.delete(I),h.size===0&&t.eventCallbacks.delete(d))}},onAll(d){return t.allCallbacks.add(d),()=>{t.allCallbacks.delete(d)}},stream(d){return A(t,{events:L},d?.signal)},getTabId(){return t.tabId}}}var q={createBus:E,createLeaderElector:y};return F(H);})();
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- function R(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function C(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function w(e,r){return{type:e,ts:Date.now(),meta:r}}function p(e,r){return{type:e,ts:Date.now(),meta:r}}function L(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function m(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function g(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function k(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function v(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function I(e,r,a,n,s){return new Promise(o=>{let t=null,c=!1,l=()=>{c||(e&&e.removeEventListener("abort",i),s(d),t!==null&&(clearTimeout(t),t=null))},d=()=>{c||(c=!0,l(),o())},i=()=>d();if(e?.aborted){d();return}if(e&&e.addEventListener("abort",i),r()){d();return}if(n(d),!e){let u=()=>{if(!c){if(r()){d();return}t=setTimeout(u,100)}};u()}})}async function*B(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await I(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*S(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await I(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function M(e,r,a,n){let s=a.messageCallbacks.get(e.type);s&&s.forEach(o=>{try{o(e)}catch(t){console.error("Error in TabBus callback:",t)}}),a.allCallbacks.forEach(o=>{try{o(e)}catch(t){console.error("Error in TabBus all callback:",t)}}),n.push(e),a.messageResolvers.forEach(o=>o()),a.messageResolvers.clear()}function D(e,r,a,n){try{let s=e.data;M(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function T(e){let{channel:r,tabId:a}=e,n=a||R();if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let s=new BroadcastChannel(r),o={channel:s,tabId:n,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0},t=[];return s.onmessage=l=>{D(l,n,o,t)},s.onmessageerror=()=>{let l=w("err",{error:"Failed to receive message"})},{publish(l,d){let i={type:l,payload:d,tabId:n,ts:Date.now()};s.postMessage(i),setTimeout(()=>{M(i,n,o,t)},0)},subscribe(l,d){return o.messageCallbacks.has(l)||o.messageCallbacks.set(l,new Set),o.messageCallbacks.get(l).add(d),()=>{let i=o.messageCallbacks.get(l);i&&(i.delete(d),i.size===0&&o.messageCallbacks.delete(l))}},subscribeAll(l){return o.allCallbacks.add(l),()=>{o.allCallbacks.delete(l)}},stream(l){return B(o,{messages:t},l?.signal)},getTabId(){return n},close(){s.close(),o.channel=null,o.messageCallbacks.clear(),o.allCallbacks.clear(),o.messageResolvers.clear()}}}function b(e,r,a){let n=r.eventCallbacks.get(e.type);n&&n.forEach(s=>{try{s(e)}catch(o){console.error("Error in LeaderElector callback:",o)}}),r.allCallbacks.forEach(s=>{try{s(e)}catch(o){console.error("Error in LeaderElector all callback:",o)}}),a.push(e),r.eventResolvers.forEach(s=>s()),r.eventResolvers.clear()}function j(e,r){if(e.stopped)return!1;let a=m(e.key);if(!v(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(g(e.key,n),m(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,b(p("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&v(a)?(e.isLeader||(e.isLeader=!0,b(p("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,b(p("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function O(e,r){if(e.stopped||!e.isLeader)return;let a=m(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};g(e.key,n)}else e.isLeader&&(e.isLeader=!1,b(p("lose",{tabId:e.tabId}),e,r))}function P(e,r){if(e.stopped)return;let a=m(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&v(a);!n&&s?(e.isLeader=!0,b(p("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,b(p("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&b(p("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function y(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:o=500}=e;if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:o,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1},c=[];function l(i){i.key!==t.key||i.storageArea!==localStorage||P(t,c)}return typeof window<"u"&&window.addEventListener("storage",l),{start(){if(t.stopped&&(t.stopped=!1),j(t,c),t.isLeader){let u=L(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{O(t,c)},u)}let i=L(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{P(t,c)},i)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(m(t.key)?.tabId===t.tabId&&k(t.key),t.isLeader=!1,b(p("lose",{tabId:t.tabId,reason:"stopped"}),t,c)),typeof window<"u"&&window.removeEventListener("storage",l)},isLeader(){return t.isLeader},on(i,u){return t.eventCallbacks.has(i)||t.eventCallbacks.set(i,new Set),t.eventCallbacks.get(i).add(u),()=>{let f=t.eventCallbacks.get(i);f&&(f.delete(u),f.size===0&&t.eventCallbacks.delete(i))}},onAll(i){return t.allCallbacks.add(i),()=>{t.allCallbacks.delete(i)}},stream(i){return S(t,{events:c},i?.signal)},getTabId(){return t.tabId}}}function $(e,r,a){if(e.tabId===a)return;let n=r.handlers.get(e.method);if(!n){let s={type:"rpc-response",requestId:e.requestId,error:`No handler found for method: ${e.method}`,tabId:a,ts:Date.now()};try{r.bus.publish("rpc-response",s)}catch{}return}Promise.resolve(n(e.params)).then(s=>{let o={type:"rpc-response",requestId:e.requestId,result:s,tabId:a,ts:Date.now()};try{r.bus.publish("rpc-response",o)}catch{}}).catch(s=>{let o={type:"rpc-response",requestId:e.requestId,error:s instanceof Error?s.message:String(s),tabId:a,ts:Date.now()};try{r.bus.publish("rpc-response",o)}catch{}})}function G(e,r,a){let n=r.pendingRequests.get(e.requestId);n&&(clearTimeout(n.timeout),r.pendingRequests.delete(e.requestId),e.error?n.reject(new Error(e.error)):n.resolve(e.result))}function h(e){let{bus:r,timeout:a=5e3}=e,n=r.getTabId(),s={bus:r,timeout:a,pendingRequests:new Map,handlers:new Map},o=r.subscribe("rpc-request",l=>{let d=l.payload;d&&d.type==="rpc-request"&&$(d,s,n)}),t=r.subscribe("rpc-response",l=>{let d=l.payload;d&&d.type==="rpc-response"&&G(d,s,n)});return{call(l,d,i){let u=C(),f=i?.timeout??s.timeout;return new Promise((x,E)=>{let q=setTimeout(()=>{s.pendingRequests.delete(u),E(new Error(`RPC call timeout: ${l} (${f}ms)`))},f);s.pendingRequests.set(u,{resolve:x,reject:E,timeout:q});let A={type:"rpc-request",method:l,params:d,requestId:u,tabId:n,ts:Date.now()};r.publish("rpc-request",A)})},handle(l,d){return s.handlers.set(l,d),()=>{s.handlers.delete(l)}},close(){s.pendingRequests.forEach(l=>{clearTimeout(l.timeout),l.reject(new Error("RPC closed"))}),s.pendingRequests.clear(),s.handlers.clear(),o(),t()}}}var ee={createBus:T,createLeaderElector:y,createRPC:h};export{T as createBus,y as createLeaderElector,h as createRPC,ee as default};
1
+ function m(e,r,a){e&&e.forEach(n=>{try{n(r)}catch(s){console.error(a,s)}})}function C(){return`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}function x(e,r){return{type:e,ts:Date.now(),meta:r}}function b(e,r){return{type:e,ts:Date.now(),meta:r}}function y(e,r){let a=(Math.random()*2-1)*r;return Math.max(0,e+a)}function h(e,r,a,n,s){return new Promise(l=>{if(e?.aborted){l();return}if(r()){l();return}let u=()=>{s(l),e&&e.removeEventListener("abort",c)},c=()=>{u(),l()};e&&e.addEventListener("abort",c),n(()=>{u(),l()})})}function g(e){try{let r=localStorage.getItem(e);return r?JSON.parse(r):null}catch{return null}}function k(e,r){try{localStorage.setItem(e,JSON.stringify(r))}catch(a){console.error("Error writing leader lease:",a)}}function B(e){try{localStorage.removeItem(e)}catch(r){console.error("Error removing leader lease:",r)}}function T(e){return e?Date.now()-e.timestamp<e.leaseMs:!1}function w(e,r,a,n){return r.length<n?{action:"add"}:e==="oldest"?{action:"drop_oldest",dropped:r.shift()}:e==="newest"?{action:"drop_newest",dropped:a}:{action:"error"}}async function*O(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.messages.length>0&&!a?.aborted;)yield r.messages.shift();await h(a,()=>r.messages.length>0,e.messageResolvers,n=>e.messageResolvers.add(n),n=>e.messageResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.messages=[])}}async function*R(e,r,a){e.activeIterators++;try{for(;!a?.aborted;){for(;r.events.length>0&&!a?.aborted;)yield r.events.shift();await h(a,()=>r.events.length>0,e.eventResolvers,n=>e.eventResolvers.add(n),n=>e.eventResolvers.delete(n))}}finally{e.activeIterators--,e.activeIterators===0&&(r.events=[])}}function A(e,r,a,n){let s=a.messageCallbacks.get(e.type);if(m(s,e,"Error in TabBus callback:"),m(a.allCallbacks,e,"Error in TabBus all callback:"),a.activeIterators===0)return;let l=w(a.bufferOverflow,n,e,a.bufferSize);if(l.action==="error"){console.error("Message buffer overflow");return}l.action!=="drop_newest"&&(l.action,n.push(e),a.messageResolvers.forEach(u=>u()),a.messageResolvers.clear())}function z(e,r,a,n){try{let s=e.data;A(s,r,a,n)}catch(s){console.error("Error handling TabBus message:",s)}}function S(e){let{channel:r,tabId:a,buffer:n}=e,s=a||C(),l=n?.size??100,u=n?.overflow??"oldest";if(typeof BroadcastChannel>"u")throw new Error("BroadcastChannel is not supported in this environment");let c=new BroadcastChannel(r),i={channel:c,tabId:s,messageCallbacks:new Map,allCallbacks:new Set,messageResolvers:new Set,activeIterators:0,bufferSize:l,bufferOverflow:u},t=[];return c.onmessage=o=>{z(o,s,i,t)},c.onmessageerror=()=>{let o=x("err",{error:"Failed to receive message"})},{publish(o,f){let p={type:o,payload:f,tabId:s,ts:Date.now()};c.postMessage(p),queueMicrotask(()=>{A(p,s,i,t)})},subscribe(o,f){return i.messageCallbacks.has(o)||i.messageCallbacks.set(o,new Set),i.messageCallbacks.get(o).add(f),()=>{let p=i.messageCallbacks.get(o);p&&(p.delete(f),p.size===0&&i.messageCallbacks.delete(o))}},subscribeAll(o){return i.allCallbacks.add(o),()=>{i.allCallbacks.delete(o)}},stream(o){return O(i,{messages:t},o?.signal)},getTabId(){return s},close(){c.close(),i.channel=null,i.messageCallbacks.clear(),i.allCallbacks.clear(),i.messageResolvers.clear()}}}function v(e,r,a){let n=r.eventCallbacks.get(e.type);if(m(n,e,"Error in LeaderElector callback:"),m(r.allCallbacks,e,"Error in LeaderElector all callback:"),r.activeIterators===0)return;let s=w(r.bufferOverflow,a,e,r.bufferSize);if(s.action==="error"){console.error("Event buffer overflow");return}s.action!=="drop_newest"&&(s.action,a.push(e),r.eventResolvers.forEach(l=>l()),r.eventResolvers.clear())}function _(e,r){if(e.stopped)return!1;let a=g(e.key);if(!T(a)){let n={tabId:e.tabId,timestamp:Date.now(),leaseMs:e.leaseMs};if(k(e.key,n),g(e.key)?.tabId===e.tabId)return e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0}return a?.tabId===e.tabId&&T(a)?(e.isLeader||(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)),!0):(e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)),!1)}function D(e,r){if(e.stopped||!e.isLeader)return;let a=g(e.key);if(a?.tabId===e.tabId){let n={...a,timestamp:Date.now()};k(e.key,n)}else e.isLeader&&(e.isLeader=!1,v(b("lose",{tabId:e.tabId}),e,r))}function j(e,r){if(e.stopped)return;let a=g(e.key),n=e.isLeader,s=a?.tabId===e.tabId&&T(a);!n&&s?(e.isLeader=!0,v(b("acquire",{tabId:e.tabId}),e,r)):n&&!s?(e.isLeader=!1,v(b("lose",{tabId:e.tabId,newLeader:a?.tabId}),e,r)):n&&s&&a?.tabId!==e.tabId&&v(b("change",{tabId:e.tabId,newLeader:a.tabId}),e,r)}function M(e){let{key:r,tabId:a,leaseMs:n=5e3,heartbeatMs:s=2e3,jitterMs:l=500,buffer:u}=e,c=u?.size??100,i=u?.overflow??"oldest";if(typeof localStorage>"u")throw new Error("localStorage is not supported in this environment");let t={key:r,tabId:a,leaseMs:n,heartbeatMs:s,jitterMs:l,isLeader:!1,heartbeatTimer:null,checkTimer:null,eventCallbacks:new Map,allCallbacks:new Set,eventResolvers:new Set,activeIterators:0,stopped:!1,bufferSize:c,bufferOverflow:i},L=[];typeof window<"u"&&(window.addEventListener("storage",o),window.addEventListener("pagehide",f),window.addEventListener("beforeunload",f));function o(d){d.key!==t.key||d.storageArea!==localStorage||j(t,L)}function f(){t.isLeader&&g(t.key)?.tabId===t.tabId&&B(t.key)}return{start(){if(t.stopped&&(t.stopped=!1),_(t,L),t.isLeader){let I=y(t.heartbeatMs,t.jitterMs);t.heartbeatTimer=setInterval(()=>{D(t,L)},I)}let d=y(t.heartbeatMs/2,t.jitterMs);t.checkTimer=setInterval(()=>{j(t,L)},d)},stop(){t.stopped=!0,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.checkTimer&&(clearInterval(t.checkTimer),t.checkTimer=null),t.isLeader&&(g(t.key)?.tabId===t.tabId&&B(t.key),t.isLeader=!1,v(b("lose",{tabId:t.tabId,reason:"stopped"}),t,L)),typeof window<"u"&&(window.removeEventListener("storage",o),window.removeEventListener("pagehide",f),window.removeEventListener("beforeunload",f))},isLeader(){return t.isLeader},on(d,I){return t.eventCallbacks.has(d)||t.eventCallbacks.set(d,new Set),t.eventCallbacks.get(d).add(I),()=>{let E=t.eventCallbacks.get(d);E&&(E.delete(I),E.size===0&&t.eventCallbacks.delete(d))}},onAll(d){return t.allCallbacks.add(d),()=>{t.allCallbacks.delete(d)}},stream(d){return R(t,{events:L},d?.signal)},getTabId(){return t.tabId}}}var Q={createBus:S,createLeaderElector:M};export{S as createBus,M as createLeaderElector,Q as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "purrtabby",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Lightweight browser tab communication and leader election library using BroadcastChannel and localStorage",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -30,7 +30,6 @@
30
30
  "inter-tab",
31
31
  "cross-tab",
32
32
  "pub-sub",
33
- "rpc",
34
33
  "typescript",
35
34
  "async-iterable",
36
35
  "generator"