tab-bridge 0.1.0 → 0.2.0

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.
@@ -1,81 +1,3 @@
1
- declare const PROTOCOL_VERSION = 1;
2
- interface StateUpdatePayload {
3
- entries: Record<string, {
4
- value: unknown;
5
- timestamp: number;
6
- }>;
7
- }
8
- interface StateSyncResponsePayload {
9
- state: Record<string, {
10
- value: unknown;
11
- timestamp: number;
12
- }>;
13
- }
14
- interface LeaderClaimPayload {
15
- createdAt: number;
16
- }
17
- interface TabAnnouncePayload {
18
- createdAt: number;
19
- isActive: boolean;
20
- url: string;
21
- title?: string;
22
- }
23
- interface RpcRequestPayload {
24
- callId: string;
25
- method: string;
26
- args: unknown;
27
- }
28
- interface RpcResponsePayload {
29
- callId: string;
30
- result?: unknown;
31
- error?: string;
32
- }
33
- /**
34
- * Maps every message type to its exact payload shape.
35
- * Adding a new message only requires extending this interface.
36
- */
37
- interface MessagePayloadMap {
38
- STATE_UPDATE: StateUpdatePayload;
39
- STATE_SYNC_REQUEST: null;
40
- STATE_SYNC_RESPONSE: StateSyncResponsePayload;
41
- LEADER_CLAIM: LeaderClaimPayload;
42
- LEADER_ACK: null;
43
- LEADER_HEARTBEAT: null;
44
- LEADER_RESIGN: null;
45
- TAB_ANNOUNCE: TabAnnouncePayload;
46
- TAB_GOODBYE: null;
47
- RPC_REQUEST: RpcRequestPayload;
48
- RPC_RESPONSE: RpcResponsePayload;
49
- }
50
- type MessageType = keyof MessagePayloadMap;
51
- /**
52
- * Discriminated union of all inter-tab messages.
53
- *
54
- * Checking `msg.type` narrows `msg.payload` to the exact payload type,
55
- * eliminating the need for runtime casts:
56
- *
57
- * ```ts
58
- * if (msg.type === 'STATE_UPDATE') {
59
- * msg.payload.entries; // ← StateUpdatePayload, fully typed
60
- * }
61
- * ```
62
- */
63
- type TabMessage = {
64
- [K in MessageType]: {
65
- readonly type: K;
66
- readonly senderId: string;
67
- readonly targetId?: string;
68
- readonly timestamp: number;
69
- readonly version?: number;
70
- readonly payload: MessagePayloadMap[K];
71
- };
72
- }[MessageType];
73
- /** Extract a single message variant by its type discriminant. */
74
- type MessageOf<T extends MessageType> = Extract<TabMessage, {
75
- type: T;
76
- }>;
77
- /** Function that sends a message through the transport channel. */
78
- type SendFn = (message: TabMessage) => void;
79
1
  interface TabInfo {
80
2
  id: string;
81
3
  createdAt: number;
@@ -90,6 +12,7 @@ interface ChangeMeta {
90
12
  readonly isLocal: boolean;
91
13
  readonly timestamp: number;
92
14
  }
15
+
93
16
  interface MiddlewareContext<TState extends Record<string, unknown>> {
94
17
  readonly key: keyof TState;
95
18
  readonly value: unknown;
@@ -112,6 +35,7 @@ interface Middleware<TState extends Record<string, unknown> = Record<string, unk
112
35
  /** Cleanup when the instance is destroyed. */
113
36
  onDestroy?: () => void;
114
37
  }
38
+
115
39
  interface PersistOptions<TState extends Record<string, unknown> = Record<string, unknown>> {
116
40
  /** Storage key. Default: `'tab-sync:state'` */
117
41
  key?: string;
@@ -127,7 +51,27 @@ interface PersistOptions<TState extends Record<string, unknown> = Record<string,
127
51
  debounce?: number;
128
52
  /** Custom storage backend. Default: `localStorage` */
129
53
  storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
54
+ /**
55
+ * Schema version for state migration. When the persisted version
56
+ * differs from this value, `migrate` is called.
57
+ */
58
+ version?: number;
59
+ /**
60
+ * Migration function called when persisted version differs from current.
61
+ *
62
+ * ```ts
63
+ * persist: {
64
+ * version: 2,
65
+ * migrate: (oldState, oldVersion) => ({
66
+ * ...oldState,
67
+ * newField: oldVersion < 2 ? 'default' : oldState.newField,
68
+ * }),
69
+ * }
70
+ * ```
71
+ */
72
+ migrate?: (oldState: Partial<TState>, oldVersion: number) => Partial<TState>;
130
73
  }
74
+
131
75
  /**
132
76
  * Define your RPC contract for full type inference:
133
77
  *
@@ -145,10 +89,16 @@ type RPCMap = Record<string, {
145
89
  args: unknown;
146
90
  result: unknown;
147
91
  }>;
92
+ interface RPCCallAllResult<T = unknown> {
93
+ tabId: string;
94
+ result?: T;
95
+ error?: string;
96
+ }
148
97
  /** Resolve args type for a method. Falls back to `unknown` for unregistered methods. */
149
98
  type RPCArgs<TMap extends RPCMap, M extends string> = M extends keyof TMap ? TMap[M]['args'] : unknown;
150
99
  /** Resolve result type for a method. Falls back to `unknown` for unregistered methods. */
151
100
  type RPCResult<TMap extends RPCMap, M extends string> = M extends keyof TMap ? TMap[M]['result'] : unknown;
101
+
152
102
  interface LeaderOptions {
153
103
  /** Heartbeat interval in ms. Default: `2000` */
154
104
  heartbeatInterval?: number;
@@ -179,6 +129,7 @@ interface TabSyncOptions<TState extends Record<string, unknown> = Record<string,
179
129
  /** Error callback for non-fatal errors (storage, channel, etc.). */
180
130
  onError?: (error: Error) => void;
181
131
  }
132
+
182
133
  interface TabSyncInstance<TState extends Record<string, unknown>, TRPCMap extends RPCMap = RPCMap> {
183
134
  /** Read a single value by key. */
184
135
  get<K extends keyof TState>(key: K): TState[K];
@@ -191,6 +142,20 @@ interface TabSyncInstance<TState extends Record<string, unknown>, TRPCMap extend
191
142
  set<K extends keyof TState>(key: K, value: TState[K]): void;
192
143
  /** Update multiple keys in a single broadcast. */
193
144
  patch(partial: Partial<TState>): void;
145
+ /**
146
+ * Atomically update multiple keys based on the current state.
147
+ * Return `null` to abort the transaction.
148
+ *
149
+ * ```ts
150
+ * sync.transaction((state) => {
151
+ * if (state.balance >= amount) {
152
+ * return { balance: state.balance - amount, history: [...state.history, tx] };
153
+ * }
154
+ * return null; // abort
155
+ * });
156
+ * ```
157
+ */
158
+ transaction(fn: (state: Readonly<TState>) => Partial<TState> | null): void;
194
159
  /**
195
160
  * Subscribe to changes for a specific key.
196
161
  * @returns Unsubscribe function.
@@ -230,7 +195,10 @@ interface TabSyncInstance<TState extends Record<string, unknown>, TRPCMap extend
230
195
  *
231
196
  * @returns Unsubscribe function.
232
197
  */
233
- select<TResult>(selector: (state: Readonly<TState>) => TResult, callback: (result: TResult, meta: ChangeMeta) => void, isEqual?: (a: TResult, b: TResult) => boolean): () => void;
198
+ select<TResult>(selector: (state: Readonly<TState>) => TResult, callback: (result: TResult, meta: ChangeMeta) => void, options?: {
199
+ isEqual?: (a: TResult, b: TResult) => boolean;
200
+ debounce?: number;
201
+ }): () => void;
234
202
  /** Whether this tab is currently the leader. */
235
203
  isLeader(): boolean;
236
204
  /**
@@ -283,6 +251,16 @@ interface TabSyncInstance<TState extends Record<string, unknown>, TRPCMap extend
283
251
  * @param timeout - Timeout in ms. Default: `5000`.
284
252
  */
285
253
  call<M extends string>(target: string | 'leader', method: M, args?: RPCArgs<TRPCMap, M>, timeout?: number): Promise<RPCResult<TRPCMap, M>>;
254
+ /**
255
+ * Call a method on **all other tabs** (fan-out).
256
+ * Returns an array of per-tab results, including errors.
257
+ *
258
+ * ```ts
259
+ * const results = await sync.callAll('getStatus');
260
+ * for (const { tabId, result, error } of results) { ... }
261
+ * ```
262
+ */
263
+ callAll<M extends string>(method: M, args?: RPCArgs<TRPCMap, M>, timeout?: number): Promise<RPCCallAllResult<RPCResult<TRPCMap, M>>[]>;
286
264
  /**
287
265
  * Register an RPC handler that other tabs can call.
288
266
  *
@@ -303,4 +281,4 @@ interface TabSyncInstance<TState extends Record<string, unknown>, TRPCMap extend
303
281
  readonly ready: boolean;
304
282
  }
305
283
 
306
- export { type ChangeMeta as C, type LeaderClaimPayload as L, type Middleware as M, PROTOCOL_VERSION as P, type RPCMap as R, type SendFn as S, type TabSyncOptions as T, type TabSyncInstance as a, type TabMessage as b, type TabInfo as c, type MiddlewareContext as d, type MessageType as e, type MessagePayloadMap as f, type MessageOf as g, type LeaderOptions as h, type MiddlewareResult as i, type PersistOptions as j, type RPCArgs as k, type RPCResult as l, type RpcRequestPayload as m, type RpcResponsePayload as n, type StateSyncResponsePayload as o, type StateUpdatePayload as p, type TabAnnouncePayload as q };
284
+ export type { ChangeMeta as C, LeaderOptions as L, Middleware as M, PersistOptions as P, RPCMap as R, TabSyncOptions as T, TabSyncInstance as a, TabInfo as b, RPCCallAllResult as c, MiddlewareContext as d, MiddlewareResult as e, RPCArgs as f, RPCResult as g };
@@ -1,81 +1,3 @@
1
- declare const PROTOCOL_VERSION = 1;
2
- interface StateUpdatePayload {
3
- entries: Record<string, {
4
- value: unknown;
5
- timestamp: number;
6
- }>;
7
- }
8
- interface StateSyncResponsePayload {
9
- state: Record<string, {
10
- value: unknown;
11
- timestamp: number;
12
- }>;
13
- }
14
- interface LeaderClaimPayload {
15
- createdAt: number;
16
- }
17
- interface TabAnnouncePayload {
18
- createdAt: number;
19
- isActive: boolean;
20
- url: string;
21
- title?: string;
22
- }
23
- interface RpcRequestPayload {
24
- callId: string;
25
- method: string;
26
- args: unknown;
27
- }
28
- interface RpcResponsePayload {
29
- callId: string;
30
- result?: unknown;
31
- error?: string;
32
- }
33
- /**
34
- * Maps every message type to its exact payload shape.
35
- * Adding a new message only requires extending this interface.
36
- */
37
- interface MessagePayloadMap {
38
- STATE_UPDATE: StateUpdatePayload;
39
- STATE_SYNC_REQUEST: null;
40
- STATE_SYNC_RESPONSE: StateSyncResponsePayload;
41
- LEADER_CLAIM: LeaderClaimPayload;
42
- LEADER_ACK: null;
43
- LEADER_HEARTBEAT: null;
44
- LEADER_RESIGN: null;
45
- TAB_ANNOUNCE: TabAnnouncePayload;
46
- TAB_GOODBYE: null;
47
- RPC_REQUEST: RpcRequestPayload;
48
- RPC_RESPONSE: RpcResponsePayload;
49
- }
50
- type MessageType = keyof MessagePayloadMap;
51
- /**
52
- * Discriminated union of all inter-tab messages.
53
- *
54
- * Checking `msg.type` narrows `msg.payload` to the exact payload type,
55
- * eliminating the need for runtime casts:
56
- *
57
- * ```ts
58
- * if (msg.type === 'STATE_UPDATE') {
59
- * msg.payload.entries; // ← StateUpdatePayload, fully typed
60
- * }
61
- * ```
62
- */
63
- type TabMessage = {
64
- [K in MessageType]: {
65
- readonly type: K;
66
- readonly senderId: string;
67
- readonly targetId?: string;
68
- readonly timestamp: number;
69
- readonly version?: number;
70
- readonly payload: MessagePayloadMap[K];
71
- };
72
- }[MessageType];
73
- /** Extract a single message variant by its type discriminant. */
74
- type MessageOf<T extends MessageType> = Extract<TabMessage, {
75
- type: T;
76
- }>;
77
- /** Function that sends a message through the transport channel. */
78
- type SendFn = (message: TabMessage) => void;
79
1
  interface TabInfo {
80
2
  id: string;
81
3
  createdAt: number;
@@ -90,6 +12,7 @@ interface ChangeMeta {
90
12
  readonly isLocal: boolean;
91
13
  readonly timestamp: number;
92
14
  }
15
+
93
16
  interface MiddlewareContext<TState extends Record<string, unknown>> {
94
17
  readonly key: keyof TState;
95
18
  readonly value: unknown;
@@ -112,6 +35,7 @@ interface Middleware<TState extends Record<string, unknown> = Record<string, unk
112
35
  /** Cleanup when the instance is destroyed. */
113
36
  onDestroy?: () => void;
114
37
  }
38
+
115
39
  interface PersistOptions<TState extends Record<string, unknown> = Record<string, unknown>> {
116
40
  /** Storage key. Default: `'tab-sync:state'` */
117
41
  key?: string;
@@ -127,7 +51,27 @@ interface PersistOptions<TState extends Record<string, unknown> = Record<string,
127
51
  debounce?: number;
128
52
  /** Custom storage backend. Default: `localStorage` */
129
53
  storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
54
+ /**
55
+ * Schema version for state migration. When the persisted version
56
+ * differs from this value, `migrate` is called.
57
+ */
58
+ version?: number;
59
+ /**
60
+ * Migration function called when persisted version differs from current.
61
+ *
62
+ * ```ts
63
+ * persist: {
64
+ * version: 2,
65
+ * migrate: (oldState, oldVersion) => ({
66
+ * ...oldState,
67
+ * newField: oldVersion < 2 ? 'default' : oldState.newField,
68
+ * }),
69
+ * }
70
+ * ```
71
+ */
72
+ migrate?: (oldState: Partial<TState>, oldVersion: number) => Partial<TState>;
130
73
  }
74
+
131
75
  /**
132
76
  * Define your RPC contract for full type inference:
133
77
  *
@@ -145,10 +89,16 @@ type RPCMap = Record<string, {
145
89
  args: unknown;
146
90
  result: unknown;
147
91
  }>;
92
+ interface RPCCallAllResult<T = unknown> {
93
+ tabId: string;
94
+ result?: T;
95
+ error?: string;
96
+ }
148
97
  /** Resolve args type for a method. Falls back to `unknown` for unregistered methods. */
149
98
  type RPCArgs<TMap extends RPCMap, M extends string> = M extends keyof TMap ? TMap[M]['args'] : unknown;
150
99
  /** Resolve result type for a method. Falls back to `unknown` for unregistered methods. */
151
100
  type RPCResult<TMap extends RPCMap, M extends string> = M extends keyof TMap ? TMap[M]['result'] : unknown;
101
+
152
102
  interface LeaderOptions {
153
103
  /** Heartbeat interval in ms. Default: `2000` */
154
104
  heartbeatInterval?: number;
@@ -179,6 +129,7 @@ interface TabSyncOptions<TState extends Record<string, unknown> = Record<string,
179
129
  /** Error callback for non-fatal errors (storage, channel, etc.). */
180
130
  onError?: (error: Error) => void;
181
131
  }
132
+
182
133
  interface TabSyncInstance<TState extends Record<string, unknown>, TRPCMap extends RPCMap = RPCMap> {
183
134
  /** Read a single value by key. */
184
135
  get<K extends keyof TState>(key: K): TState[K];
@@ -191,6 +142,20 @@ interface TabSyncInstance<TState extends Record<string, unknown>, TRPCMap extend
191
142
  set<K extends keyof TState>(key: K, value: TState[K]): void;
192
143
  /** Update multiple keys in a single broadcast. */
193
144
  patch(partial: Partial<TState>): void;
145
+ /**
146
+ * Atomically update multiple keys based on the current state.
147
+ * Return `null` to abort the transaction.
148
+ *
149
+ * ```ts
150
+ * sync.transaction((state) => {
151
+ * if (state.balance >= amount) {
152
+ * return { balance: state.balance - amount, history: [...state.history, tx] };
153
+ * }
154
+ * return null; // abort
155
+ * });
156
+ * ```
157
+ */
158
+ transaction(fn: (state: Readonly<TState>) => Partial<TState> | null): void;
194
159
  /**
195
160
  * Subscribe to changes for a specific key.
196
161
  * @returns Unsubscribe function.
@@ -230,7 +195,10 @@ interface TabSyncInstance<TState extends Record<string, unknown>, TRPCMap extend
230
195
  *
231
196
  * @returns Unsubscribe function.
232
197
  */
233
- select<TResult>(selector: (state: Readonly<TState>) => TResult, callback: (result: TResult, meta: ChangeMeta) => void, isEqual?: (a: TResult, b: TResult) => boolean): () => void;
198
+ select<TResult>(selector: (state: Readonly<TState>) => TResult, callback: (result: TResult, meta: ChangeMeta) => void, options?: {
199
+ isEqual?: (a: TResult, b: TResult) => boolean;
200
+ debounce?: number;
201
+ }): () => void;
234
202
  /** Whether this tab is currently the leader. */
235
203
  isLeader(): boolean;
236
204
  /**
@@ -283,6 +251,16 @@ interface TabSyncInstance<TState extends Record<string, unknown>, TRPCMap extend
283
251
  * @param timeout - Timeout in ms. Default: `5000`.
284
252
  */
285
253
  call<M extends string>(target: string | 'leader', method: M, args?: RPCArgs<TRPCMap, M>, timeout?: number): Promise<RPCResult<TRPCMap, M>>;
254
+ /**
255
+ * Call a method on **all other tabs** (fan-out).
256
+ * Returns an array of per-tab results, including errors.
257
+ *
258
+ * ```ts
259
+ * const results = await sync.callAll('getStatus');
260
+ * for (const { tabId, result, error } of results) { ... }
261
+ * ```
262
+ */
263
+ callAll<M extends string>(method: M, args?: RPCArgs<TRPCMap, M>, timeout?: number): Promise<RPCCallAllResult<RPCResult<TRPCMap, M>>[]>;
286
264
  /**
287
265
  * Register an RPC handler that other tabs can call.
288
266
  *
@@ -303,4 +281,4 @@ interface TabSyncInstance<TState extends Record<string, unknown>, TRPCMap extend
303
281
  readonly ready: boolean;
304
282
  }
305
283
 
306
- export { type ChangeMeta as C, type LeaderClaimPayload as L, type Middleware as M, PROTOCOL_VERSION as P, type RPCMap as R, type SendFn as S, type TabSyncOptions as T, type TabSyncInstance as a, type TabMessage as b, type TabInfo as c, type MiddlewareContext as d, type MessageType as e, type MessagePayloadMap as f, type MessageOf as g, type LeaderOptions as h, type MiddlewareResult as i, type PersistOptions as j, type RPCArgs as k, type RPCResult as l, type RpcRequestPayload as m, type RpcResponsePayload as n, type StateSyncResponsePayload as o, type StateUpdatePayload as p, type TabAnnouncePayload as q };
284
+ export type { ChangeMeta as C, LeaderOptions as L, Middleware as M, PersistOptions as P, RPCMap as R, TabSyncOptions as T, TabSyncInstance as a, TabInfo as b, RPCCallAllResult as c, MiddlewareContext as d, MiddlewareResult as e, RPCArgs as f, RPCResult as g };
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkBQCNBNBT_cjs = require('../chunk-BQCNBNBT.cjs');
3
+ var chunkTGEXRVAL_cjs = require('../chunk-TGEXRVAL.cjs');
4
4
  var react = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
@@ -11,7 +11,7 @@ function TabSyncProvider({
11
11
  }) {
12
12
  const instanceRef = react.useRef(null);
13
13
  if (!instanceRef.current) {
14
- instanceRef.current = chunkBQCNBNBT_cjs.createTabSync(options);
14
+ instanceRef.current = chunkTGEXRVAL_cjs.createTabSync(options);
15
15
  }
16
16
  react.useEffect(() => {
17
17
  return () => {
@@ -26,7 +26,7 @@ function useInstance(options) {
26
26
  const contextInstance = react.useContext(TabSyncContext);
27
27
  const [ownInstance] = react.useState(() => {
28
28
  if (!contextInstance && options) {
29
- return chunkBQCNBNBT_cjs.createTabSync(options);
29
+ return chunkTGEXRVAL_cjs.createTabSync(options);
30
30
  }
31
31
  return null;
32
32
  });
@@ -127,14 +127,14 @@ function useTabSyncSelector(selector, isEqual) {
127
127
  initializedRef.current = true;
128
128
  }
129
129
  const subscribe = react.useCallback(
130
- (onStoreChange) => instance.onChange(() => {
131
- const next = selectorRef.current(instance.getAll());
132
- const equal = isEqualRef.current ?? Object.is;
133
- if (!equal(resultRef.current, next)) {
130
+ (onStoreChange) => instance.select(
131
+ (state) => selectorRef.current(state),
132
+ (next) => {
134
133
  resultRef.current = next;
135
134
  onStoreChange();
136
- }
137
- }),
135
+ },
136
+ { isEqual: isEqualRef.current }
137
+ ),
138
138
  [instance]
139
139
  );
140
140
  const getSnapshot = react.useCallback(() => resultRef.current, []);
@@ -165,10 +165,83 @@ function useIsLeader() {
165
165
  const getServerSnapshot = react.useCallback(() => SERVER_SNAPSHOT, []);
166
166
  return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
167
167
  }
168
+ var SERVER_SNAPSHOT2 = [];
169
+ function useTabs() {
170
+ const instance = react.useContext(TabSyncContext);
171
+ if (!instance) {
172
+ throw new Error("useTabs must be used within a <TabSyncProvider>");
173
+ }
174
+ const tabsRef = react.useRef(instance.getTabs());
175
+ const subscribe = react.useCallback(
176
+ (onStoreChange) => instance.onTabChange((tabs) => {
177
+ tabsRef.current = tabs;
178
+ onStoreChange();
179
+ }),
180
+ [instance]
181
+ );
182
+ const getSnapshot = react.useCallback(() => tabsRef.current, []);
183
+ const getServerSnapshot = react.useCallback(() => SERVER_SNAPSHOT2, []);
184
+ return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
185
+ }
186
+ function useLeaderInfo() {
187
+ const instance = react.useContext(TabSyncContext);
188
+ if (!instance) {
189
+ throw new Error("useLeaderInfo must be used within a <TabSyncProvider>");
190
+ }
191
+ const leaderRef = react.useRef(instance.getLeader());
192
+ const subscribe = react.useCallback(
193
+ (onStoreChange) => {
194
+ const unsubs = [];
195
+ unsubs.push(
196
+ instance.onTabChange(() => {
197
+ const next = instance.getLeader();
198
+ if (next?.id !== leaderRef.current?.id) {
199
+ leaderRef.current = next;
200
+ onStoreChange();
201
+ }
202
+ })
203
+ );
204
+ unsubs.push(
205
+ instance.onLeader(() => {
206
+ leaderRef.current = instance.getLeader();
207
+ onStoreChange();
208
+ return () => {
209
+ leaderRef.current = instance.getLeader();
210
+ onStoreChange();
211
+ };
212
+ })
213
+ );
214
+ return () => {
215
+ for (const u of unsubs) u();
216
+ };
217
+ },
218
+ [instance]
219
+ );
220
+ const getSnapshot = react.useCallback(() => leaderRef.current, []);
221
+ const getServerSnapshot = react.useCallback(() => null, []);
222
+ return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
223
+ }
224
+ function useTabSyncActions() {
225
+ const instance = react.useContext(TabSyncContext);
226
+ if (!instance) {
227
+ throw new Error("useTabSyncActions must be used within a <TabSyncProvider>");
228
+ }
229
+ return react.useMemo(
230
+ () => ({
231
+ set: instance.set.bind(instance),
232
+ patch: instance.patch.bind(instance),
233
+ transaction: instance.transaction.bind(instance)
234
+ }),
235
+ [instance]
236
+ );
237
+ }
168
238
 
169
239
  exports.TabSyncContext = TabSyncContext;
170
240
  exports.TabSyncProvider = TabSyncProvider;
171
241
  exports.useIsLeader = useIsLeader;
242
+ exports.useLeaderInfo = useLeaderInfo;
172
243
  exports.useTabSync = useTabSync;
244
+ exports.useTabSyncActions = useTabSyncActions;
173
245
  exports.useTabSyncSelector = useTabSyncSelector;
174
246
  exports.useTabSyncValue = useTabSyncValue;
247
+ exports.useTabs = useTabs;
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { T as TabSyncOptions, a as TabSyncInstance, R as RPCMap, c as TabInfo } from '../types-BtK4ixKz.cjs';
4
+ import { T as TabSyncOptions, a as TabSyncInstance, R as RPCMap, b as TabInfo } from '../instance-5LIItazN.cjs';
5
5
 
6
6
  interface TabSyncProviderProps<TState extends Record<string, unknown>> {
7
7
  options: TabSyncOptions<TState>;
@@ -35,6 +35,9 @@ declare function useTabSyncValue<TState extends Record<string, unknown>, K exten
35
35
  * Subscribe to a **derived value** from the synced state.
36
36
  * Only re-renders when the selector's output actually changes.
37
37
  *
38
+ * Uses `instance.select()` internally so only changed-key
39
+ * evaluations trigger the selector, reducing unnecessary work.
40
+ *
38
41
  * ```tsx
39
42
  * const doneCount = useTabSyncSelector(
40
43
  * (s) => s.todos.filter(t => t.done).length,
@@ -58,4 +61,46 @@ declare function useTabSyncSelector<TState extends Record<string, unknown>, TRes
58
61
  */
59
62
  declare function useIsLeader(): boolean;
60
63
 
61
- export { TabSyncContext, TabSyncProvider, type TabSyncProviderProps, useIsLeader, useTabSync, useTabSyncSelector, useTabSyncValue };
64
+ /**
65
+ * Subscribe to the list of active tabs. Re-renders when tabs join or leave.
66
+ *
67
+ * ```tsx
68
+ * const tabs = useTabs();
69
+ * // tabs: TabInfo[]
70
+ * ```
71
+ *
72
+ * Must be used within a `<TabSyncProvider>`.
73
+ */
74
+ declare function useTabs(): TabInfo[];
75
+
76
+ /**
77
+ * Subscribe to the current leader's full `TabInfo`.
78
+ * Returns `null` if no leader has been elected yet.
79
+ *
80
+ * ```tsx
81
+ * const leader = useLeaderInfo();
82
+ * // leader: TabInfo | null
83
+ * ```
84
+ *
85
+ * Must be used within a `<TabSyncProvider>`.
86
+ */
87
+ declare function useLeaderInfo(): TabInfo | null;
88
+
89
+ interface TabSyncActions<TState extends Record<string, unknown>> {
90
+ set: TabSyncInstance<TState>['set'];
91
+ patch: TabSyncInstance<TState>['patch'];
92
+ transaction: TabSyncInstance<TState>['transaction'];
93
+ }
94
+ /**
95
+ * Returns `set`, `patch`, and `transaction` without subscribing to state.
96
+ * Components using only this hook never re-render due to state changes.
97
+ *
98
+ * ```tsx
99
+ * const { set, patch, transaction } = useTabSyncActions();
100
+ * ```
101
+ *
102
+ * Must be used within a `<TabSyncProvider>`.
103
+ */
104
+ declare function useTabSyncActions<TState extends Record<string, unknown> = Record<string, unknown>>(): TabSyncActions<TState>;
105
+
106
+ export { type TabSyncActions, TabSyncContext, TabSyncProvider, type TabSyncProviderProps, useIsLeader, useLeaderInfo, useTabSync, useTabSyncActions, useTabSyncSelector, useTabSyncValue, useTabs };
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { T as TabSyncOptions, a as TabSyncInstance, R as RPCMap, c as TabInfo } from '../types-BtK4ixKz.js';
4
+ import { T as TabSyncOptions, a as TabSyncInstance, R as RPCMap, b as TabInfo } from '../instance-5LIItazN.js';
5
5
 
6
6
  interface TabSyncProviderProps<TState extends Record<string, unknown>> {
7
7
  options: TabSyncOptions<TState>;
@@ -35,6 +35,9 @@ declare function useTabSyncValue<TState extends Record<string, unknown>, K exten
35
35
  * Subscribe to a **derived value** from the synced state.
36
36
  * Only re-renders when the selector's output actually changes.
37
37
  *
38
+ * Uses `instance.select()` internally so only changed-key
39
+ * evaluations trigger the selector, reducing unnecessary work.
40
+ *
38
41
  * ```tsx
39
42
  * const doneCount = useTabSyncSelector(
40
43
  * (s) => s.todos.filter(t => t.done).length,
@@ -58,4 +61,46 @@ declare function useTabSyncSelector<TState extends Record<string, unknown>, TRes
58
61
  */
59
62
  declare function useIsLeader(): boolean;
60
63
 
61
- export { TabSyncContext, TabSyncProvider, type TabSyncProviderProps, useIsLeader, useTabSync, useTabSyncSelector, useTabSyncValue };
64
+ /**
65
+ * Subscribe to the list of active tabs. Re-renders when tabs join or leave.
66
+ *
67
+ * ```tsx
68
+ * const tabs = useTabs();
69
+ * // tabs: TabInfo[]
70
+ * ```
71
+ *
72
+ * Must be used within a `<TabSyncProvider>`.
73
+ */
74
+ declare function useTabs(): TabInfo[];
75
+
76
+ /**
77
+ * Subscribe to the current leader's full `TabInfo`.
78
+ * Returns `null` if no leader has been elected yet.
79
+ *
80
+ * ```tsx
81
+ * const leader = useLeaderInfo();
82
+ * // leader: TabInfo | null
83
+ * ```
84
+ *
85
+ * Must be used within a `<TabSyncProvider>`.
86
+ */
87
+ declare function useLeaderInfo(): TabInfo | null;
88
+
89
+ interface TabSyncActions<TState extends Record<string, unknown>> {
90
+ set: TabSyncInstance<TState>['set'];
91
+ patch: TabSyncInstance<TState>['patch'];
92
+ transaction: TabSyncInstance<TState>['transaction'];
93
+ }
94
+ /**
95
+ * Returns `set`, `patch`, and `transaction` without subscribing to state.
96
+ * Components using only this hook never re-render due to state changes.
97
+ *
98
+ * ```tsx
99
+ * const { set, patch, transaction } = useTabSyncActions();
100
+ * ```
101
+ *
102
+ * Must be used within a `<TabSyncProvider>`.
103
+ */
104
+ declare function useTabSyncActions<TState extends Record<string, unknown> = Record<string, unknown>>(): TabSyncActions<TState>;
105
+
106
+ export { type TabSyncActions, TabSyncContext, TabSyncProvider, type TabSyncProviderProps, useIsLeader, useLeaderInfo, useTabSync, useTabSyncActions, useTabSyncSelector, useTabSyncValue, useTabs };