tab-bridge 0.2.0 → 0.4.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 +340 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/{instance-5LIItazN.d.cts → instance-hvEUHx6i.d.cts} +1 -91
- package/dist/{instance-5LIItazN.d.ts → instance-hvEUHx6i.d.ts} +1 -91
- package/dist/jotai/index.cjs +44 -0
- package/dist/jotai/index.d.cts +68 -0
- package/dist/jotai/index.d.ts +68 -0
- package/dist/jotai/index.js +42 -0
- package/dist/options-DmHyGTL0.d.cts +93 -0
- package/dist/options-iN7Rnvwj.d.ts +93 -0
- package/dist/react/index.cjs +332 -0
- package/dist/react/index.d.cts +25 -2
- package/dist/react/index.d.ts +25 -2
- package/dist/react/index.js +333 -2
- package/dist/redux/index.cjs +99 -0
- package/dist/redux/index.d.cts +83 -0
- package/dist/redux/index.d.ts +83 -0
- package/dist/redux/index.js +96 -0
- package/dist/zustand/index.cjs +80 -0
- package/dist/zustand/index.d.cts +87 -0
- package/dist/zustand/index.d.ts +87 -0
- package/dist/zustand/index.js +78 -0
- package/package.json +52 -3
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { WritableAtom } from 'jotai/vanilla';
|
|
2
|
+
import { T as TabSyncInstance } from '../instance-hvEUHx6i.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for `atomWithTabSync`.
|
|
6
|
+
*
|
|
7
|
+
* All atoms sharing the same `channel` reuse a single `createTabSync`
|
|
8
|
+
* instance internally. Channel-level options (`transport`, `debug`,
|
|
9
|
+
* `onError`) are taken from the **first** atom that creates the
|
|
10
|
+
* instance on that channel.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { atomWithTabSync } from 'tab-bridge/jotai';
|
|
15
|
+
*
|
|
16
|
+
* const countAtom = atomWithTabSync('count', 0, {
|
|
17
|
+
* channel: 'my-app',
|
|
18
|
+
* debug: true,
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface AtomWithTabSyncOptions {
|
|
23
|
+
/** Channel name for cross-tab communication. @default 'tab-sync-jotai' */
|
|
24
|
+
channel?: string;
|
|
25
|
+
/** Force a specific transport layer. @default auto-detect */
|
|
26
|
+
transport?: 'broadcast-channel' | 'local-storage';
|
|
27
|
+
/** Enable debug logging. @default false */
|
|
28
|
+
debug?: boolean;
|
|
29
|
+
/** Error callback for non-fatal errors (channel failures, etc.). */
|
|
30
|
+
onError?: (error: Error) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Callback invoked when the underlying `TabSyncInstance` is ready.
|
|
33
|
+
* Useful for accessing advanced features (RPC, leader election, etc.).
|
|
34
|
+
* Called once per shared instance — only the first atom to trigger
|
|
35
|
+
* instance creation will have its callback invoked.
|
|
36
|
+
*/
|
|
37
|
+
onSyncReady?: (instance: TabSyncInstance<Record<string, unknown>>) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type SetStateAction<T> = T | ((prev: T) => T);
|
|
41
|
+
/**
|
|
42
|
+
* Creates a Jotai atom whose value is automatically synchronised across
|
|
43
|
+
* browser tabs via `tab-bridge`.
|
|
44
|
+
*
|
|
45
|
+
* Each atom creates its own `createTabSync` instance scoped to the channel
|
|
46
|
+
* `${channel}:${key}`. The instance is created when the atom is first
|
|
47
|
+
* subscribed to (React component mount / `store.sub`) and destroyed on
|
|
48
|
+
* the last unsubscription (unmount).
|
|
49
|
+
*
|
|
50
|
+
* The atom behaves like a normal writable atom — use `useAtom` in React
|
|
51
|
+
* or `store.get` / `store.set` with Jotai's vanilla store.
|
|
52
|
+
*
|
|
53
|
+
* @param key - Unique key identifying this piece of state within the channel.
|
|
54
|
+
* @param initialValue - Default value used when no synced state exists yet.
|
|
55
|
+
* @param options - Channel and transport configuration.
|
|
56
|
+
* @returns A writable Jotai atom.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { atomWithTabSync } from 'tab-bridge/jotai';
|
|
61
|
+
*
|
|
62
|
+
* const countAtom = atomWithTabSync('count', 0, { channel: 'my-app' });
|
|
63
|
+
* const themeAtom = atomWithTabSync('theme', 'light', { channel: 'my-app' });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function atomWithTabSync<T>(key: string, initialValue: T, options?: AtomWithTabSyncOptions): WritableAtom<T, [SetStateAction<T>], void>;
|
|
67
|
+
|
|
68
|
+
export { type AtomWithTabSyncOptions, atomWithTabSync };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { WritableAtom } from 'jotai/vanilla';
|
|
2
|
+
import { T as TabSyncInstance } from '../instance-hvEUHx6i.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for `atomWithTabSync`.
|
|
6
|
+
*
|
|
7
|
+
* All atoms sharing the same `channel` reuse a single `createTabSync`
|
|
8
|
+
* instance internally. Channel-level options (`transport`, `debug`,
|
|
9
|
+
* `onError`) are taken from the **first** atom that creates the
|
|
10
|
+
* instance on that channel.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { atomWithTabSync } from 'tab-bridge/jotai';
|
|
15
|
+
*
|
|
16
|
+
* const countAtom = atomWithTabSync('count', 0, {
|
|
17
|
+
* channel: 'my-app',
|
|
18
|
+
* debug: true,
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface AtomWithTabSyncOptions {
|
|
23
|
+
/** Channel name for cross-tab communication. @default 'tab-sync-jotai' */
|
|
24
|
+
channel?: string;
|
|
25
|
+
/** Force a specific transport layer. @default auto-detect */
|
|
26
|
+
transport?: 'broadcast-channel' | 'local-storage';
|
|
27
|
+
/** Enable debug logging. @default false */
|
|
28
|
+
debug?: boolean;
|
|
29
|
+
/** Error callback for non-fatal errors (channel failures, etc.). */
|
|
30
|
+
onError?: (error: Error) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Callback invoked when the underlying `TabSyncInstance` is ready.
|
|
33
|
+
* Useful for accessing advanced features (RPC, leader election, etc.).
|
|
34
|
+
* Called once per shared instance — only the first atom to trigger
|
|
35
|
+
* instance creation will have its callback invoked.
|
|
36
|
+
*/
|
|
37
|
+
onSyncReady?: (instance: TabSyncInstance<Record<string, unknown>>) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type SetStateAction<T> = T | ((prev: T) => T);
|
|
41
|
+
/**
|
|
42
|
+
* Creates a Jotai atom whose value is automatically synchronised across
|
|
43
|
+
* browser tabs via `tab-bridge`.
|
|
44
|
+
*
|
|
45
|
+
* Each atom creates its own `createTabSync` instance scoped to the channel
|
|
46
|
+
* `${channel}:${key}`. The instance is created when the atom is first
|
|
47
|
+
* subscribed to (React component mount / `store.sub`) and destroyed on
|
|
48
|
+
* the last unsubscription (unmount).
|
|
49
|
+
*
|
|
50
|
+
* The atom behaves like a normal writable atom — use `useAtom` in React
|
|
51
|
+
* or `store.get` / `store.set` with Jotai's vanilla store.
|
|
52
|
+
*
|
|
53
|
+
* @param key - Unique key identifying this piece of state within the channel.
|
|
54
|
+
* @param initialValue - Default value used when no synced state exists yet.
|
|
55
|
+
* @param options - Channel and transport configuration.
|
|
56
|
+
* @returns A writable Jotai atom.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { atomWithTabSync } from 'tab-bridge/jotai';
|
|
61
|
+
*
|
|
62
|
+
* const countAtom = atomWithTabSync('count', 0, { channel: 'my-app' });
|
|
63
|
+
* const themeAtom = atomWithTabSync('theme', 'light', { channel: 'my-app' });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function atomWithTabSync<T>(key: string, initialValue: T, options?: AtomWithTabSyncOptions): WritableAtom<T, [SetStateAction<T>], void>;
|
|
67
|
+
|
|
68
|
+
export { type AtomWithTabSyncOptions, atomWithTabSync };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isBrowser, createTabSync } from '../chunk-4JDWAUYM.js';
|
|
2
|
+
import { atom } from 'jotai/vanilla';
|
|
3
|
+
|
|
4
|
+
function atomWithTabSync(key, initialValue, options) {
|
|
5
|
+
const base_channel = options?.channel ?? "tab-sync-jotai";
|
|
6
|
+
const instance_channel = `${base_channel}:${key}`;
|
|
7
|
+
let sync_instance = null;
|
|
8
|
+
const base_atom = atom(initialValue);
|
|
9
|
+
base_atom.onMount = (setAtom) => {
|
|
10
|
+
if (!isBrowser) return;
|
|
11
|
+
const instance = createTabSync({
|
|
12
|
+
channel: instance_channel,
|
|
13
|
+
initial: { value: initialValue },
|
|
14
|
+
transport: options?.transport,
|
|
15
|
+
debug: options?.debug,
|
|
16
|
+
onError: options?.onError
|
|
17
|
+
});
|
|
18
|
+
sync_instance = instance;
|
|
19
|
+
options?.onSyncReady?.(instance);
|
|
20
|
+
const unsub = instance.on("value", (remote_value, meta) => {
|
|
21
|
+
if (!meta.isLocal) {
|
|
22
|
+
setAtom(remote_value);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return () => {
|
|
26
|
+
unsub();
|
|
27
|
+
instance.destroy();
|
|
28
|
+
sync_instance = null;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const sync_atom = atom(
|
|
32
|
+
(get) => get(base_atom),
|
|
33
|
+
(get, set, update) => {
|
|
34
|
+
const next_value = typeof update === "function" ? update(get(base_atom)) : update;
|
|
35
|
+
set(base_atom, next_value);
|
|
36
|
+
sync_instance?.set("value", next_value);
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
return sync_atom;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { atomWithTabSync };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { C as ChangeMeta } from './instance-hvEUHx6i.cjs';
|
|
2
|
+
|
|
3
|
+
interface MiddlewareContext<TState extends Record<string, unknown>> {
|
|
4
|
+
readonly key: keyof TState;
|
|
5
|
+
readonly value: unknown;
|
|
6
|
+
readonly previousValue: unknown;
|
|
7
|
+
readonly meta: ChangeMeta;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Return `false` to reject the change, `{ value }` to transform it,
|
|
11
|
+
* or `void`/`undefined` to pass through unchanged.
|
|
12
|
+
*/
|
|
13
|
+
type MiddlewareResult = {
|
|
14
|
+
value: unknown;
|
|
15
|
+
} | false;
|
|
16
|
+
interface Middleware<TState extends Record<string, unknown> = Record<string, unknown>> {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
/** Intercept local `set` / `patch` calls before they are applied. */
|
|
19
|
+
onSet?: (ctx: MiddlewareContext<TState>) => MiddlewareResult | void;
|
|
20
|
+
/** Called after any state change (local or remote) has been committed. */
|
|
21
|
+
afterChange?: (key: keyof TState, value: unknown, meta: ChangeMeta) => void;
|
|
22
|
+
/** Cleanup when the instance is destroyed. */
|
|
23
|
+
onDestroy?: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface PersistOptions<TState extends Record<string, unknown> = Record<string, unknown>> {
|
|
27
|
+
/** Storage key. Default: `'tab-sync:state'` */
|
|
28
|
+
key?: string;
|
|
29
|
+
/** Only persist these keys (whitelist). */
|
|
30
|
+
include?: (keyof TState)[];
|
|
31
|
+
/** Exclude these keys from persistence (blacklist). */
|
|
32
|
+
exclude?: (keyof TState)[];
|
|
33
|
+
/** Custom serializer. Default: `JSON.stringify` */
|
|
34
|
+
serialize?: (state: Partial<TState>) => string;
|
|
35
|
+
/** Custom deserializer. Default: `JSON.parse` */
|
|
36
|
+
deserialize?: (raw: string) => Partial<TState>;
|
|
37
|
+
/** Debounce persistence writes in ms. Default: `100` */
|
|
38
|
+
debounce?: number;
|
|
39
|
+
/** Custom storage backend. Default: `localStorage` */
|
|
40
|
+
storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
41
|
+
/**
|
|
42
|
+
* Schema version for state migration. When the persisted version
|
|
43
|
+
* differs from this value, `migrate` is called.
|
|
44
|
+
*/
|
|
45
|
+
version?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Migration function called when persisted version differs from current.
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* persist: {
|
|
51
|
+
* version: 2,
|
|
52
|
+
* migrate: (oldState, oldVersion) => ({
|
|
53
|
+
* ...oldState,
|
|
54
|
+
* newField: oldVersion < 2 ? 'default' : oldState.newField,
|
|
55
|
+
* }),
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
migrate?: (oldState: Partial<TState>, oldVersion: number) => Partial<TState>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface LeaderOptions {
|
|
63
|
+
/** Heartbeat interval in ms. Default: `2000` */
|
|
64
|
+
heartbeatInterval?: number;
|
|
65
|
+
/** Leader timeout in ms. Default: `6000` */
|
|
66
|
+
leaderTimeout?: number;
|
|
67
|
+
}
|
|
68
|
+
interface TabSyncOptions<TState extends Record<string, unknown> = Record<string, unknown>> {
|
|
69
|
+
/** Initial state used before sync completes. */
|
|
70
|
+
initial?: TState;
|
|
71
|
+
/** Channel name — only tabs sharing the same name communicate. Default: `'tab-sync'` */
|
|
72
|
+
channel?: string;
|
|
73
|
+
/** Force a specific transport layer. Default: auto-detect. */
|
|
74
|
+
transport?: 'broadcast-channel' | 'local-storage';
|
|
75
|
+
/** Custom merge function for LWW conflict resolution. */
|
|
76
|
+
merge?: (localValue: unknown, remoteValue: unknown, key: keyof TState) => unknown;
|
|
77
|
+
/** Enable leader election. Default: `true` */
|
|
78
|
+
leader?: boolean | LeaderOptions;
|
|
79
|
+
/** Heartbeat interval in ms. Default: `2000` */
|
|
80
|
+
heartbeatInterval?: number;
|
|
81
|
+
/** Leader timeout in ms. Default: `6000` */
|
|
82
|
+
leaderTimeout?: number;
|
|
83
|
+
/** Enable debug logging. Default: `false` */
|
|
84
|
+
debug?: boolean;
|
|
85
|
+
/** Persist state across page reloads. `true` uses defaults, or pass options. */
|
|
86
|
+
persist?: PersistOptions<TState> | boolean;
|
|
87
|
+
/** Middleware pipeline for intercepting state changes. */
|
|
88
|
+
middlewares?: Middleware<TState>[];
|
|
89
|
+
/** Error callback for non-fatal errors (storage, channel, etc.). */
|
|
90
|
+
onError?: (error: Error) => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type { LeaderOptions as L, Middleware as M, PersistOptions as P, TabSyncOptions as T, MiddlewareContext as a, MiddlewareResult as b };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { C as ChangeMeta } from './instance-hvEUHx6i.js';
|
|
2
|
+
|
|
3
|
+
interface MiddlewareContext<TState extends Record<string, unknown>> {
|
|
4
|
+
readonly key: keyof TState;
|
|
5
|
+
readonly value: unknown;
|
|
6
|
+
readonly previousValue: unknown;
|
|
7
|
+
readonly meta: ChangeMeta;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Return `false` to reject the change, `{ value }` to transform it,
|
|
11
|
+
* or `void`/`undefined` to pass through unchanged.
|
|
12
|
+
*/
|
|
13
|
+
type MiddlewareResult = {
|
|
14
|
+
value: unknown;
|
|
15
|
+
} | false;
|
|
16
|
+
interface Middleware<TState extends Record<string, unknown> = Record<string, unknown>> {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
/** Intercept local `set` / `patch` calls before they are applied. */
|
|
19
|
+
onSet?: (ctx: MiddlewareContext<TState>) => MiddlewareResult | void;
|
|
20
|
+
/** Called after any state change (local or remote) has been committed. */
|
|
21
|
+
afterChange?: (key: keyof TState, value: unknown, meta: ChangeMeta) => void;
|
|
22
|
+
/** Cleanup when the instance is destroyed. */
|
|
23
|
+
onDestroy?: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface PersistOptions<TState extends Record<string, unknown> = Record<string, unknown>> {
|
|
27
|
+
/** Storage key. Default: `'tab-sync:state'` */
|
|
28
|
+
key?: string;
|
|
29
|
+
/** Only persist these keys (whitelist). */
|
|
30
|
+
include?: (keyof TState)[];
|
|
31
|
+
/** Exclude these keys from persistence (blacklist). */
|
|
32
|
+
exclude?: (keyof TState)[];
|
|
33
|
+
/** Custom serializer. Default: `JSON.stringify` */
|
|
34
|
+
serialize?: (state: Partial<TState>) => string;
|
|
35
|
+
/** Custom deserializer. Default: `JSON.parse` */
|
|
36
|
+
deserialize?: (raw: string) => Partial<TState>;
|
|
37
|
+
/** Debounce persistence writes in ms. Default: `100` */
|
|
38
|
+
debounce?: number;
|
|
39
|
+
/** Custom storage backend. Default: `localStorage` */
|
|
40
|
+
storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
41
|
+
/**
|
|
42
|
+
* Schema version for state migration. When the persisted version
|
|
43
|
+
* differs from this value, `migrate` is called.
|
|
44
|
+
*/
|
|
45
|
+
version?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Migration function called when persisted version differs from current.
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* persist: {
|
|
51
|
+
* version: 2,
|
|
52
|
+
* migrate: (oldState, oldVersion) => ({
|
|
53
|
+
* ...oldState,
|
|
54
|
+
* newField: oldVersion < 2 ? 'default' : oldState.newField,
|
|
55
|
+
* }),
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
migrate?: (oldState: Partial<TState>, oldVersion: number) => Partial<TState>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface LeaderOptions {
|
|
63
|
+
/** Heartbeat interval in ms. Default: `2000` */
|
|
64
|
+
heartbeatInterval?: number;
|
|
65
|
+
/** Leader timeout in ms. Default: `6000` */
|
|
66
|
+
leaderTimeout?: number;
|
|
67
|
+
}
|
|
68
|
+
interface TabSyncOptions<TState extends Record<string, unknown> = Record<string, unknown>> {
|
|
69
|
+
/** Initial state used before sync completes. */
|
|
70
|
+
initial?: TState;
|
|
71
|
+
/** Channel name — only tabs sharing the same name communicate. Default: `'tab-sync'` */
|
|
72
|
+
channel?: string;
|
|
73
|
+
/** Force a specific transport layer. Default: auto-detect. */
|
|
74
|
+
transport?: 'broadcast-channel' | 'local-storage';
|
|
75
|
+
/** Custom merge function for LWW conflict resolution. */
|
|
76
|
+
merge?: (localValue: unknown, remoteValue: unknown, key: keyof TState) => unknown;
|
|
77
|
+
/** Enable leader election. Default: `true` */
|
|
78
|
+
leader?: boolean | LeaderOptions;
|
|
79
|
+
/** Heartbeat interval in ms. Default: `2000` */
|
|
80
|
+
heartbeatInterval?: number;
|
|
81
|
+
/** Leader timeout in ms. Default: `6000` */
|
|
82
|
+
leaderTimeout?: number;
|
|
83
|
+
/** Enable debug logging. Default: `false` */
|
|
84
|
+
debug?: boolean;
|
|
85
|
+
/** Persist state across page reloads. `true` uses defaults, or pass options. */
|
|
86
|
+
persist?: PersistOptions<TState> | boolean;
|
|
87
|
+
/** Middleware pipeline for intercepting state changes. */
|
|
88
|
+
middlewares?: Middleware<TState>[];
|
|
89
|
+
/** Error callback for non-fatal errors (storage, channel, etc.). */
|
|
90
|
+
onError?: (error: Error) => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type { LeaderOptions as L, Middleware as M, PersistOptions as P, TabSyncOptions as T, MiddlewareContext as a, MiddlewareResult as b };
|