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