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,4 +1,4 @@
1
- // src/types.ts
1
+ // src/types/messages.ts
2
2
  var PROTOCOL_VERSION = 1;
3
3
 
4
4
  // src/utils/id.ts
@@ -13,18 +13,56 @@ function generateTabId() {
13
13
  });
14
14
  }
15
15
 
16
- // src/utils/env.ts
17
- var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
18
- var hasDocument = typeof document !== "undefined";
19
- var hasLocalStorage = (() => {
20
- try {
21
- return typeof localStorage !== "undefined" && localStorage !== null;
22
- } catch {
23
- return false;
16
+ // src/utils/errors.ts
17
+ var ErrorCode = {
18
+ CHANNEL_CLOSED: "CHANNEL_CLOSED",
19
+ CHANNEL_SEND_FAILED: "CHANNEL_SEND_FAILED",
20
+ RPC_TIMEOUT: "RPC_TIMEOUT",
21
+ RPC_NO_HANDLER: "RPC_NO_HANDLER",
22
+ RPC_NO_LEADER: "RPC_NO_LEADER",
23
+ RPC_HANDLER_ERROR: "RPC_HANDLER_ERROR",
24
+ RPC_DESTROYED: "RPC_DESTROYED",
25
+ STORAGE_QUOTA_EXCEEDED: "STORAGE_QUOTA_EXCEEDED",
26
+ MIDDLEWARE_REJECTED: "MIDDLEWARE_REJECTED",
27
+ ALREADY_DESTROYED: "ALREADY_DESTROYED"
28
+ };
29
+ var TabSyncError = class _TabSyncError extends Error {
30
+ constructor(message, code, cause) {
31
+ super(message);
32
+ this.code = code;
33
+ this.name = "TabSyncError";
34
+ this.cause = cause;
24
35
  }
25
- })();
26
- var hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
27
- var hasCrypto = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function";
36
+ static timeout(method, ms) {
37
+ return new _TabSyncError(
38
+ `RPC "${method}" timed out after ${ms}ms`,
39
+ ErrorCode.RPC_TIMEOUT
40
+ );
41
+ }
42
+ static noLeader() {
43
+ return new _TabSyncError("No leader available", ErrorCode.RPC_NO_LEADER);
44
+ }
45
+ static noHandler(method) {
46
+ return new _TabSyncError(
47
+ `No handler registered for "${method}"`,
48
+ ErrorCode.RPC_NO_HANDLER
49
+ );
50
+ }
51
+ static destroyed() {
52
+ return new _TabSyncError("Instance has been destroyed", ErrorCode.ALREADY_DESTROYED);
53
+ }
54
+ };
55
+
56
+ // src/utils/logger.ts
57
+ function createLogger(enabled, tabId) {
58
+ if (!enabled) return { log: (() => {
59
+ }) };
60
+ const prefix = `%c[tab-sync:${tabId.slice(0, 8)}]`;
61
+ const style = "color:#818cf8;font-weight:600";
62
+ return {
63
+ log: (label, ...args) => console.log(prefix, style, label, ...args)
64
+ };
65
+ }
28
66
 
29
67
  // src/channels/broadcast.ts
30
68
  var BroadcastChannelTransport = class {
@@ -50,21 +88,36 @@ var BroadcastChannelTransport = class {
50
88
  }
51
89
  };
52
90
 
91
+ // src/utils/env.ts
92
+ var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
93
+ var hasDocument = typeof document !== "undefined";
94
+ var hasLocalStorage = (() => {
95
+ try {
96
+ return typeof localStorage !== "undefined" && localStorage !== null;
97
+ } catch {
98
+ return false;
99
+ }
100
+ })();
101
+ var hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
102
+ var hasCrypto = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function";
103
+
53
104
  // src/channels/storage.ts
54
105
  var KEY_PREFIX = "__tab_sync__";
55
106
  var StorageChannel = class {
56
- constructor(channelName) {
107
+ constructor(channelName, onError) {
57
108
  this.listeners = /* @__PURE__ */ new Set();
58
109
  this.closed = false;
59
110
  this.seq = 0;
60
111
  this.key = `${KEY_PREFIX}${channelName}`;
112
+ this.onError = onError;
61
113
  }
62
114
  postMessage(message) {
63
115
  if (this.closed || !hasLocalStorage) return;
64
116
  try {
65
117
  const wrapped = JSON.stringify({ m: message, s: this.seq++ });
66
118
  localStorage.setItem(this.key, wrapped);
67
- } catch {
119
+ } catch (e) {
120
+ this.onError?.(e instanceof Error ? e : new Error(String(e)));
68
121
  }
69
122
  }
70
123
  onMessage(callback) {
@@ -104,9 +157,9 @@ var StorageChannel = class {
104
157
  };
105
158
 
106
159
  // src/channels/channel.ts
107
- function createChannel(channelName, transport) {
160
+ function createChannel(channelName, transport, onError) {
108
161
  if (transport === "local-storage") {
109
- return new StorageChannel(channelName);
162
+ return new StorageChannel(channelName, onError);
110
163
  }
111
164
  if (transport === "broadcast-channel") {
112
165
  return new BroadcastChannelTransport(channelName);
@@ -114,7 +167,7 @@ function createChannel(channelName, transport) {
114
167
  if (typeof BroadcastChannel !== "undefined") {
115
168
  return new BroadcastChannelTransport(channelName);
116
169
  }
117
- return new StorageChannel(channelName);
170
+ return new StorageChannel(channelName, onError);
118
171
  }
119
172
 
120
173
  // src/utils/timestamp.ts
@@ -356,7 +409,21 @@ var StateManager = class {
356
409
  for (const [key, remote] of Object.entries(state)) {
357
410
  const local = this.state.get(key);
358
411
  if (!local || remote.timestamp > local.timestamp) {
359
- this.state.set(key, { value: remote.value, timestamp: remote.timestamp });
412
+ let finalValue = remote.value;
413
+ if (this.mergeFn && local) {
414
+ finalValue = this.mergeFn(local.value, remote.value, key);
415
+ }
416
+ if (this.interceptRemote) {
417
+ const result = this.interceptRemote(
418
+ key,
419
+ finalValue,
420
+ local?.value,
421
+ meta
422
+ );
423
+ if (result === false) continue;
424
+ if (result && "value" in result) finalValue = result.value;
425
+ }
426
+ this.state.set(key, { value: finalValue, timestamp: remote.timestamp });
360
427
  changedKeys.push(key);
361
428
  }
362
429
  }
@@ -372,15 +439,15 @@ var StateManager = class {
372
439
  }
373
440
  notifyKey(key, value, meta) {
374
441
  const listeners = this.keyListeners.get(key);
375
- if (!listeners) return;
376
- for (const cb of listeners) {
442
+ if (!listeners || listeners.size === 0) return;
443
+ for (const cb of [...listeners]) {
377
444
  cb(value, meta);
378
445
  }
379
446
  }
380
447
  notifyChange(changedKeys, meta) {
381
448
  if (this.changeListeners.size === 0) return;
382
449
  const snapshot = this.getAll();
383
- for (const cb of this.changeListeners) {
450
+ for (const cb of [...this.changeListeners]) {
384
451
  cb(snapshot, changedKeys, meta);
385
452
  }
386
453
  }
@@ -587,6 +654,8 @@ var LeaderElection = class {
587
654
  this.leaderWatchTimer = null;
588
655
  this.lastLeaderHeartbeat = 0;
589
656
  this.electing = false;
657
+ this.generation = 0;
658
+ this.currentClaimId = null;
590
659
  this.leaderCallbacks = /* @__PURE__ */ new Set();
591
660
  this.leaderCleanups = /* @__PURE__ */ new Map();
592
661
  this.send = options.send;
@@ -631,7 +700,7 @@ var LeaderElection = class {
631
700
  this.handleClaim(message.payload, message.senderId);
632
701
  break;
633
702
  case "LEADER_ACK":
634
- this.handleAck(message.senderId);
703
+ this.handleAck(message.payload, message.senderId);
635
704
  break;
636
705
  case "LEADER_HEARTBEAT":
637
706
  this.handleHeartbeat(message.senderId);
@@ -661,11 +730,17 @@ var LeaderElection = class {
661
730
  startElection() {
662
731
  if (this.electing) return;
663
732
  this.electing = true;
733
+ this.generation++;
734
+ this.currentClaimId = generateTabId();
664
735
  this.send({
665
736
  type: "LEADER_CLAIM",
666
737
  senderId: this.tabId,
667
738
  timestamp: monotonic(),
668
- payload: { createdAt: this.tabCreatedAt }
739
+ payload: {
740
+ createdAt: this.tabCreatedAt,
741
+ claimId: this.currentClaimId,
742
+ generation: this.generation
743
+ }
669
744
  });
670
745
  this.electionTimer = setTimeout(() => {
671
746
  this.electionTimer = null;
@@ -681,9 +756,11 @@ var LeaderElection = class {
681
756
  this.electing = false;
682
757
  }
683
758
  }
684
- handleAck(senderId) {
759
+ handleAck(payload, senderId) {
760
+ if (payload.generation < this.generation) return;
685
761
  this.clearElectionTimer();
686
762
  this.electing = false;
763
+ this.generation = Math.max(this.generation, payload.generation);
687
764
  this.setLeader(senderId);
688
765
  }
689
766
  handleHeartbeat(senderId) {
@@ -705,7 +782,10 @@ var LeaderElection = class {
705
782
  type: "LEADER_ACK",
706
783
  senderId: this.tabId,
707
784
  timestamp: monotonic(),
708
- payload: null
785
+ payload: {
786
+ claimId: this.currentClaimId,
787
+ generation: this.generation
788
+ }
709
789
  });
710
790
  this.startHeartbeat();
711
791
  }
@@ -715,7 +795,7 @@ var LeaderElection = class {
715
795
  this.lastLeaderHeartbeat = Date.now();
716
796
  if (this.isLeader() && !wasLeader) {
717
797
  this.startHeartbeat();
718
- for (const cb of this.leaderCallbacks) {
798
+ for (const cb of [...this.leaderCallbacks]) {
719
799
  const cleanup = cb();
720
800
  if (typeof cleanup === "function") {
721
801
  this.leaderCleanups.set(cb, cleanup);
@@ -786,46 +866,6 @@ var LeaderElection = class {
786
866
  }
787
867
  };
788
868
 
789
- // src/utils/errors.ts
790
- var ErrorCode = {
791
- CHANNEL_CLOSED: "CHANNEL_CLOSED",
792
- CHANNEL_SEND_FAILED: "CHANNEL_SEND_FAILED",
793
- RPC_TIMEOUT: "RPC_TIMEOUT",
794
- RPC_NO_HANDLER: "RPC_NO_HANDLER",
795
- RPC_NO_LEADER: "RPC_NO_LEADER",
796
- RPC_HANDLER_ERROR: "RPC_HANDLER_ERROR",
797
- RPC_DESTROYED: "RPC_DESTROYED",
798
- STORAGE_QUOTA_EXCEEDED: "STORAGE_QUOTA_EXCEEDED",
799
- MIDDLEWARE_REJECTED: "MIDDLEWARE_REJECTED",
800
- ALREADY_DESTROYED: "ALREADY_DESTROYED"
801
- };
802
- var TabSyncError = class _TabSyncError extends Error {
803
- constructor(message, code, cause) {
804
- super(message);
805
- this.code = code;
806
- this.name = "TabSyncError";
807
- this.cause = cause;
808
- }
809
- static timeout(method, ms) {
810
- return new _TabSyncError(
811
- `RPC "${method}" timed out after ${ms}ms`,
812
- ErrorCode.RPC_TIMEOUT
813
- );
814
- }
815
- static noLeader() {
816
- return new _TabSyncError("No leader available", ErrorCode.RPC_NO_LEADER);
817
- }
818
- static noHandler(method) {
819
- return new _TabSyncError(
820
- `No handler registered for "${method}"`,
821
- ErrorCode.RPC_NO_HANDLER
822
- );
823
- }
824
- static destroyed() {
825
- return new _TabSyncError("Instance has been destroyed", ErrorCode.ALREADY_DESTROYED);
826
- }
827
- };
828
-
829
869
  // src/core/rpc.ts
830
870
  var DEFAULT_TIMEOUT = 5e3;
831
871
  var RPCHandler = class {
@@ -835,6 +875,7 @@ var RPCHandler = class {
835
875
  this.send = options.send;
836
876
  this.tabId = options.tabId;
837
877
  this.resolveLeaderId = options.resolveLeaderId ?? (() => null);
878
+ this.resolveTabIds = options.resolveTabIds ?? (() => []);
838
879
  this.onError = options.onError ?? (() => {
839
880
  });
840
881
  }
@@ -863,6 +904,15 @@ var RPCHandler = class {
863
904
  });
864
905
  });
865
906
  }
907
+ callAll(method, args, timeout = DEFAULT_TIMEOUT) {
908
+ const tabIds = this.resolveTabIds().filter((id) => id !== this.tabId);
909
+ if (tabIds.length === 0) return Promise.resolve([]);
910
+ return Promise.all(
911
+ tabIds.map(
912
+ (targetId) => this.call(targetId, method, args, timeout).then((result) => ({ tabId: targetId, result })).catch((err) => ({ tabId: targetId, error: err.message }))
913
+ )
914
+ );
915
+ }
866
916
  handle(method, handler) {
867
917
  this.handlers.set(
868
918
  method,
@@ -931,13 +981,32 @@ var RPCHandler = class {
931
981
  }
932
982
  }
933
983
  sendResponse(targetId, callId, result, error) {
934
- this.send({
935
- type: "RPC_RESPONSE",
936
- senderId: this.tabId,
937
- targetId,
938
- timestamp: monotonic(),
939
- payload: { callId, result, error }
940
- });
984
+ try {
985
+ this.send({
986
+ type: "RPC_RESPONSE",
987
+ senderId: this.tabId,
988
+ targetId,
989
+ timestamp: monotonic(),
990
+ payload: { callId, result, error }
991
+ });
992
+ } catch (e) {
993
+ const serErr = new TabSyncError(
994
+ `Failed to serialize RPC response for "${callId}": ${e instanceof Error ? e.message : String(e)}`,
995
+ ErrorCode.CHANNEL_SEND_FAILED,
996
+ e
997
+ );
998
+ this.onError(serErr);
999
+ try {
1000
+ this.send({
1001
+ type: "RPC_RESPONSE",
1002
+ senderId: this.tabId,
1003
+ targetId,
1004
+ timestamp: monotonic(),
1005
+ payload: { callId, result: void 0, error: serErr.message }
1006
+ });
1007
+ } catch {
1008
+ }
1009
+ }
941
1010
  }
942
1011
  };
943
1012
 
@@ -968,7 +1037,8 @@ function destroyMiddleware(middlewares) {
968
1037
  }
969
1038
  }
970
1039
 
971
- // src/core/tab-sync.ts
1040
+ // src/core/persist.ts
1041
+ var DEFAULT_KEY = "tab-sync:state";
972
1042
  function resolvePersistOptions(opt) {
973
1043
  if (!opt) return null;
974
1044
  if (opt === true) return {};
@@ -977,12 +1047,23 @@ function resolvePersistOptions(opt) {
977
1047
  function loadPersistedState(opts) {
978
1048
  const storage = opts.storage ?? (hasLocalStorage ? localStorage : null);
979
1049
  if (!storage) return {};
980
- const key = opts.key ?? "tab-sync:state";
1050
+ const key = opts.key ?? DEFAULT_KEY;
1051
+ const versionKey = `${key}:version`;
981
1052
  const deserialize = opts.deserialize ?? JSON.parse;
982
1053
  try {
983
1054
  const raw = storage.getItem(key);
984
1055
  if (!raw) return {};
985
- const parsed = deserialize(raw);
1056
+ let parsed = deserialize(raw);
1057
+ if (opts.version !== void 0 && opts.migrate) {
1058
+ const rawVersion = storage.getItem(versionKey);
1059
+ const oldVersion = rawVersion ? Number(rawVersion) : 0;
1060
+ if (oldVersion !== opts.version) {
1061
+ parsed = opts.migrate(parsed, oldVersion);
1062
+ const serialize = opts.serialize ?? JSON.stringify;
1063
+ storage.setItem(key, serialize(parsed));
1064
+ storage.setItem(versionKey, String(opts.version));
1065
+ }
1066
+ }
986
1067
  return filterPersistKeys(parsed, opts);
987
1068
  } catch {
988
1069
  return {};
@@ -1006,9 +1087,10 @@ function createPersistSaver(opts, onError) {
1006
1087
  }, flush() {
1007
1088
  }, destroy() {
1008
1089
  } };
1009
- const key = opts.key ?? "tab-sync:state";
1090
+ const key = opts.key ?? DEFAULT_KEY;
1010
1091
  const serialize = opts.serialize ?? JSON.stringify;
1011
1092
  const debounce = opts.debounce ?? 100;
1093
+ const versionKey = `${key}:version`;
1012
1094
  let timer = null;
1013
1095
  let latestState = null;
1014
1096
  function doSave() {
@@ -1016,6 +1098,9 @@ function createPersistSaver(opts, onError) {
1016
1098
  try {
1017
1099
  const filtered = filterPersistKeys({ ...latestState }, opts);
1018
1100
  storage.setItem(key, serialize(filtered));
1101
+ if (opts.version !== void 0) {
1102
+ storage.setItem(versionKey, String(opts.version));
1103
+ }
1019
1104
  } catch (e) {
1020
1105
  onError(e instanceof Error ? e : new Error(String(e)));
1021
1106
  }
@@ -1047,15 +1132,8 @@ function createPersistSaver(opts, onError) {
1047
1132
  }
1048
1133
  };
1049
1134
  }
1050
- function createLogger(enabled, tabId) {
1051
- if (!enabled) return { log: (() => {
1052
- }) };
1053
- const prefix = `%c[tab-sync:${tabId.slice(0, 8)}]`;
1054
- const style = "color:#818cf8;font-weight:600";
1055
- return {
1056
- log: (label, ...args) => console.log(prefix, style, label, ...args)
1057
- };
1058
- }
1135
+
1136
+ // src/core/tab-sync.ts
1059
1137
  function createTabSync(options) {
1060
1138
  const opts = options ?? {};
1061
1139
  const tabId = generateTabId();
@@ -1082,7 +1160,7 @@ function createTabSync(options) {
1082
1160
  initialState = { ...initialState, ...restored };
1083
1161
  }
1084
1162
  }
1085
- const channel = createChannel(channelName, opts.transport);
1163
+ const channel = createChannel(channelName, opts.transport, onError);
1086
1164
  const { log } = createLogger(debug, tabId);
1087
1165
  const send = (message) => {
1088
1166
  log("\u2192", message.type, message.payload);
@@ -1119,6 +1197,7 @@ function createTabSync(options) {
1119
1197
  send,
1120
1198
  tabId,
1121
1199
  resolveLeaderId: () => election?.getLeaderId() ?? null,
1200
+ resolveTabIds: () => registry.getTabs().map((t) => t.id),
1122
1201
  onError
1123
1202
  });
1124
1203
  const unsubChannel = channel.onMessage((message) => {
@@ -1159,7 +1238,11 @@ function createTabSync(options) {
1159
1238
  election?.start();
1160
1239
  let ready = true;
1161
1240
  let destroyed = false;
1241
+ function assertAlive() {
1242
+ if (destroyed) throw TabSyncError.destroyed();
1243
+ }
1162
1244
  function middlewareSet(key, value) {
1245
+ assertAlive();
1163
1246
  if (middlewares.length === 0) {
1164
1247
  stateManager.set(key, value);
1165
1248
  if (persister) persister.save(stateManager.getAll());
@@ -1181,6 +1264,7 @@ function createTabSync(options) {
1181
1264
  if (persister) persister.save(stateManager.getAll());
1182
1265
  }
1183
1266
  function middlewarePatch(partial) {
1267
+ assertAlive();
1184
1268
  if (middlewares.length === 0) {
1185
1269
  stateManager.patch(partial);
1186
1270
  if (persister) persister.save(stateManager.getAll());
@@ -1216,6 +1300,13 @@ function createTabSync(options) {
1216
1300
  getAll: () => stateManager.getAll(),
1217
1301
  set: middlewareSet,
1218
1302
  patch: middlewarePatch,
1303
+ transaction: (fn) => {
1304
+ assertAlive();
1305
+ const current = stateManager.getAll();
1306
+ const result = fn(current);
1307
+ if (result === null) return;
1308
+ middlewarePatch(result);
1309
+ },
1219
1310
  // Subscriptions
1220
1311
  on: (key, callback) => stateManager.on(key, callback),
1221
1312
  once: (key, callback) => {
@@ -1226,15 +1317,30 @@ function createTabSync(options) {
1226
1317
  return unsub;
1227
1318
  },
1228
1319
  onChange: (callback) => stateManager.onChange(callback),
1229
- select: (selector, callback, isEqual = Object.is) => {
1320
+ select: (selector, callback, options2) => {
1321
+ const isEqual = options2?.isEqual ?? Object.is;
1322
+ const debounceMs = options2?.debounce;
1230
1323
  let prev = selector(stateManager.getAll());
1231
- return stateManager.onChange((state, _keys, meta) => {
1324
+ let debounceTimer = null;
1325
+ const unsub = stateManager.onChange((state, _keys, meta) => {
1232
1326
  const next = selector(state);
1233
1327
  if (!isEqual(prev, next)) {
1234
1328
  prev = next;
1235
- callback(next, meta);
1329
+ if (debounceMs !== void 0 && debounceMs > 0) {
1330
+ if (debounceTimer) clearTimeout(debounceTimer);
1331
+ debounceTimer = setTimeout(() => {
1332
+ debounceTimer = null;
1333
+ callback(prev, meta);
1334
+ }, debounceMs);
1335
+ } else {
1336
+ callback(next, meta);
1337
+ }
1236
1338
  }
1237
1339
  });
1340
+ return () => {
1341
+ if (debounceTimer) clearTimeout(debounceTimer);
1342
+ unsub();
1343
+ };
1238
1344
  },
1239
1345
  // Leader
1240
1346
  isLeader: () => election?.isLeader() ?? true,
@@ -1253,6 +1359,13 @@ function createTabSync(options) {
1253
1359
  return registry.getTab(leaderId) ?? null;
1254
1360
  },
1255
1361
  waitForLeader: () => {
1362
+ if (!leaderEnabled) {
1363
+ const selfInfo = registry.getTab(tabId);
1364
+ if (selfInfo) return Promise.resolve(selfInfo);
1365
+ return Promise.reject(
1366
+ new TabSyncError("Leader election is disabled", ErrorCode.RPC_NO_LEADER)
1367
+ );
1368
+ }
1256
1369
  const leader = instance.getLeader();
1257
1370
  if (leader) return Promise.resolve(leader);
1258
1371
  return new Promise((resolve) => {
@@ -1280,8 +1393,18 @@ function createTabSync(options) {
1280
1393
  getTabCount: () => registry.getTabCount(),
1281
1394
  onTabChange: (callback) => registry.onTabChange(callback),
1282
1395
  // RPC
1283
- call: ((target, method, args, timeout) => rpc.call(target, method, args, timeout)),
1284
- handle: ((method, handler) => rpc.handle(method, handler)),
1396
+ call: ((target, method, args, timeout) => {
1397
+ assertAlive();
1398
+ return rpc.call(target, method, args, timeout);
1399
+ }),
1400
+ handle: ((method, handler) => {
1401
+ assertAlive();
1402
+ return rpc.handle(method, handler);
1403
+ }),
1404
+ callAll: ((method, args, timeout) => {
1405
+ assertAlive();
1406
+ return rpc.callAll(method, args, timeout);
1407
+ }),
1285
1408
  // Lifecycle
1286
1409
  destroy: () => {
1287
1410
  if (destroyed) return;
@@ -1306,4 +1429,4 @@ function createTabSync(options) {
1306
1429
  return instance;
1307
1430
  }
1308
1431
 
1309
- export { BroadcastChannelTransport, ErrorCode, LeaderElection, PROTOCOL_VERSION, RPCHandler, StateManager, StorageChannel, TabRegistry, TabSyncError, createBatcher, createChannel, createTabSync, destroyMiddleware, generateTabId, hasBroadcastChannel, hasCrypto, hasDocument, hasLocalStorage, isBrowser, monotonic, notifyMiddleware, runMiddleware };
1432
+ export { BroadcastChannelTransport, ErrorCode, LeaderElection, PROTOCOL_VERSION, RPCHandler, StateManager, StorageChannel, TabRegistry, TabSyncError, createBatcher, createChannel, createLogger, createPersistSaver, createTabSync, destroyMiddleware, generateTabId, hasBroadcastChannel, hasCrypto, hasDocument, hasLocalStorage, isBrowser, loadPersistedState, monotonic, notifyMiddleware, resolvePersistOptions, runMiddleware };