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.
- package/README.md +80 -112
- package/dist/{chunk-42VOZR6E.js → chunk-4JDWAUYM.js} +216 -93
- package/dist/{chunk-BQCNBNBT.cjs → chunk-TGEXRVAL.cjs} +219 -92
- package/dist/index.cjs +41 -25
- package/dist/index.d.cts +110 -5
- package/dist/index.d.ts +110 -5
- package/dist/index.js +2 -2
- package/dist/{types-BtK4ixKz.d.cts → instance-5LIItazN.d.cts} +58 -80
- package/dist/{types-BtK4ixKz.d.ts → instance-5LIItazN.d.ts} +58 -80
- package/dist/react/index.cjs +82 -9
- package/dist/react/index.d.cts +47 -2
- package/dist/react/index.d.ts +47 -2
- package/dist/react/index.js +79 -9
- package/package.json +5 -4
|
@@ -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,
|
|
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 {
|
|
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,
|
|
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 {
|
|
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 };
|
package/dist/react/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
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 =
|
|
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
|
|
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.
|
|
131
|
-
|
|
132
|
-
|
|
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;
|
package/dist/react/index.d.cts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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 };
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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 };
|