use-everywhere 0.4.1 → 0.6.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 CHANGED
@@ -103,7 +103,7 @@ function PayButton() {
103
103
  }),
104
104
  );
105
105
 
106
- if (pay.status === 'done') return <p>Paid — receipt {pay.result!.receiptId}</p>;
106
+ if (pay.status === 'done') return <p>Paid — receipt {pay.result.receiptId}</p>;
107
107
  if (pay.status === 'closed-early') return <p>Payment window was closed.</p>;
108
108
 
109
109
  return (
@@ -0,0 +1,187 @@
1
+ 'use client';
2
+
3
+ // src/registry.ts
4
+ import {
5
+ createChannel,
6
+ createLeader,
7
+ createPresence,
8
+ createSharedStore,
9
+ DEFAULT_NAME,
10
+ NoopTransport
11
+ } from "@use-everywhere/core";
12
+
13
+ // src/dev.ts
14
+ var inDev = false;
15
+ try {
16
+ inDev = process.env.NODE_ENV !== "production";
17
+ } catch {
18
+ }
19
+ var warned = /* @__PURE__ */ new Set();
20
+ function devWarn(message) {
21
+ if (!inDev || warned.has(message)) return;
22
+ warned.add(message);
23
+ console.warn(message);
24
+ }
25
+ var seenInitials = /* @__PURE__ */ new Map();
26
+ function warnOnInitialMismatch(storeName, key, initial) {
27
+ if (!inDev) return;
28
+ const id = `${storeName} ${key}`;
29
+ if (!seenInitials.has(id)) {
30
+ seenInitials.set(id, initial);
31
+ return;
32
+ }
33
+ const first = seenInitials.get(id);
34
+ const comparable = (v) => v === null || typeof v !== "object";
35
+ if (comparable(first) && comparable(initial) && !Object.is(first, initial)) {
36
+ devWarn(
37
+ `[use-everywhere] useSharedState('${key}') was called with different initial values (${String(first)} and ${String(initial)}). The first registration wins, so the second is ignored. Define the default once \u2014 defineStore, or a shared constant.`
38
+ );
39
+ }
40
+ }
41
+
42
+ // src/server-stubs.ts
43
+ var SERVER_CLIENT_ID = "";
44
+ var EMPTY = Object.freeze({});
45
+ var NO_PEERS = Object.freeze([]);
46
+ var noop = () => {
47
+ };
48
+ var unsubscribe = () => noop;
49
+ var INERT = Object.freeze({
50
+ clientId: SERVER_CLIENT_ID,
51
+ // store
52
+ state: EMPTY,
53
+ getVersions: () => EMPTY,
54
+ set: noop,
55
+ subscribeKey: unsubscribe,
56
+ registerKey: noop,
57
+ // store + leader share getSnapshot; presence has getPeers
58
+ getSnapshot: () => EMPTY,
59
+ getPeers: () => NO_PEERS,
60
+ // leader
61
+ resign: noop,
62
+ setEligible: noop,
63
+ // common
64
+ subscribe: unsubscribe,
65
+ close: noop
66
+ });
67
+ var createServerStore = () => INERT;
68
+ var createServerPresence = () => INERT;
69
+ var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
70
+ var createServerLeader = () => ({ ...INERT, getSnapshot: () => NO_LEADER });
71
+ var createServerChannel = (name) => ({ ...INERT, name, post: noop, on: unsubscribe });
72
+
73
+ // src/registry.ts
74
+ var stores = /* @__PURE__ */ new Map();
75
+ var presences = /* @__PURE__ */ new Map();
76
+ var channels = /* @__PURE__ */ new Map();
77
+ var leaders = /* @__PURE__ */ new Map();
78
+ var storeConfig = /* @__PURE__ */ new Map();
79
+ var isServer = () => typeof window === "undefined";
80
+ var scopeOptions = {
81
+ everywhere: {},
82
+ tabs: { accept: (meta) => meta.kind !== "worker" },
83
+ tab: { transport: () => new NoopTransport() }
84
+ };
85
+ function getSharedStore(name = DEFAULT_NAME, scope = "everywhere") {
86
+ return getStore(name, scope);
87
+ }
88
+ function configSignature(options) {
89
+ const persist = options?.persist;
90
+ if (!persist) return "none";
91
+ return `persist:${persist.keys?.join(",") ?? "*"}:${persist.debounceMs ?? "default"}`;
92
+ }
93
+ function configureStore(name, scope, options) {
94
+ const key = `${scope} ${name}`;
95
+ if (stores.has(key)) {
96
+ if (configSignature(storeConfig.get(key)) === configSignature(options)) return;
97
+ devWarn(
98
+ `[use-everywhere] defineStore('${name}') ran after that store was already created, with different options. The live store keeps the configuration it was built with. Move defineStore to module scope, before any component reads the store.`
99
+ );
100
+ return;
101
+ }
102
+ storeConfig.set(key, options);
103
+ }
104
+ function getStore(name, scope = "everywhere") {
105
+ const key = `${scope} ${name}`;
106
+ let store = stores.get(key);
107
+ if (!store) {
108
+ store = isServer() ? createServerStore() : createSharedStore(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
109
+ stores.set(key, store);
110
+ }
111
+ return store;
112
+ }
113
+ function getPresence(name) {
114
+ let presence = presences.get(name);
115
+ if (!presence) {
116
+ presence = isServer() ? createServerPresence() : createPresence(name);
117
+ presences.set(name, presence);
118
+ }
119
+ return presence;
120
+ }
121
+ function getLeader(name, options) {
122
+ let leader = leaders.get(name);
123
+ if (!leader) {
124
+ leader = isServer() ? createServerLeader() : createLeader(name, options);
125
+ leaders.set(name, leader);
126
+ if (options) leaderOptions.set(name, options);
127
+ } else if (options) {
128
+ warnOnLeaderOptionConflict(name, options);
129
+ }
130
+ return leader;
131
+ }
132
+ var leaderOptions = /* @__PURE__ */ new Map();
133
+ function warnOnLeaderOptionConflict(name, options) {
134
+ const first = leaderOptions.get(name);
135
+ for (const key of ["heartbeatMs", "leaseMs"]) {
136
+ const requested = options[key];
137
+ if (requested !== void 0 && requested !== first?.[key]) {
138
+ devWarn(
139
+ `[use-everywhere] leader "${name}": ${key} ignored \u2014 the first useLeader/getLeader call fixes the election timings for this tab.`
140
+ );
141
+ }
142
+ }
143
+ }
144
+ function getChannel(name) {
145
+ let channel = channels.get(name);
146
+ if (!channel) {
147
+ channel = isServer() ? createServerChannel(name) : createChannel(name);
148
+ channels.set(name, channel);
149
+ }
150
+ return channel;
151
+ }
152
+
153
+ // src/use-peers.ts
154
+ import { useCallback, useSyncExternalStore } from "react";
155
+ var NO_PEERS2 = Object.freeze([]);
156
+ function usePeers(options) {
157
+ const presence = getPresence(options?.name ?? DEFAULT_NAME);
158
+ return useSyncExternalStore(
159
+ useCallback((onChange) => presence.subscribe(onChange), [presence]),
160
+ () => presence.getPeers(),
161
+ () => NO_PEERS2
162
+ );
163
+ }
164
+ function useClientId(options) {
165
+ const presence = getPresence(options?.name ?? DEFAULT_NAME);
166
+ return useSyncExternalStore(
167
+ // The id never changes for the life of a presence engine, so there is
168
+ // nothing to subscribe to — but uSES still needs the post-hydration read,
169
+ // which it performs when it attaches this subscription.
170
+ useCallback(() => () => {
171
+ }, [presence]),
172
+ () => presence.clientId,
173
+ () => SERVER_CLIENT_ID
174
+ );
175
+ }
176
+
177
+ export {
178
+ warnOnInitialMismatch,
179
+ DEFAULT_NAME,
180
+ getSharedStore,
181
+ configureStore,
182
+ getStore,
183
+ getLeader,
184
+ getChannel,
185
+ usePeers,
186
+ useClientId
187
+ };
@@ -31,9 +31,41 @@ var import_core2 = require("@use-everywhere/core");
31
31
 
32
32
  // src/registry.ts
33
33
  var import_core = require("@use-everywhere/core");
34
+
35
+ // src/server-stubs.ts
36
+ var SERVER_CLIENT_ID = "";
37
+ var EMPTY = Object.freeze({});
38
+ var NO_PEERS = Object.freeze([]);
39
+ var noop = () => {
40
+ };
41
+ var unsubscribe = () => noop;
42
+ var INERT = Object.freeze({
43
+ clientId: SERVER_CLIENT_ID,
44
+ // store
45
+ state: EMPTY,
46
+ getVersions: () => EMPTY,
47
+ set: noop,
48
+ subscribeKey: unsubscribe,
49
+ registerKey: noop,
50
+ // store + leader share getSnapshot; presence has getPeers
51
+ getSnapshot: () => EMPTY,
52
+ getPeers: () => NO_PEERS,
53
+ // leader
54
+ resign: noop,
55
+ setEligible: noop,
56
+ // common
57
+ subscribe: unsubscribe,
58
+ close: noop
59
+ });
60
+ var createServerStore = () => INERT;
61
+ var createServerPresence = () => INERT;
62
+ var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
63
+
64
+ // src/registry.ts
34
65
  var stores = /* @__PURE__ */ new Map();
35
66
  var presences = /* @__PURE__ */ new Map();
36
67
  var storeConfig = /* @__PURE__ */ new Map();
68
+ var isServer = () => typeof window === "undefined";
37
69
  var scopeOptions = {
38
70
  everywhere: {},
39
71
  tabs: { accept: (meta) => meta.kind !== "worker" },
@@ -46,7 +78,7 @@ function getStore(name, scope = "everywhere") {
46
78
  const key = `${scope} ${name}`;
47
79
  let store = stores.get(key);
48
80
  if (!store) {
49
- store = (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
81
+ store = isServer() ? createServerStore() : (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
50
82
  stores.set(key, store);
51
83
  }
52
84
  return store;
@@ -54,7 +86,7 @@ function getStore(name, scope = "everywhere") {
54
86
  function getPresence(name) {
55
87
  let presence = presences.get(name);
56
88
  if (!presence) {
57
- presence = (0, import_core.createPresence)(name);
89
+ presence = isServer() ? createServerPresence() : (0, import_core.createPresence)(name);
58
90
  presences.set(name, presence);
59
91
  }
60
92
  return presence;
@@ -62,13 +94,13 @@ function getPresence(name) {
62
94
 
63
95
  // src/use-peers.ts
64
96
  var import_react = require("react");
65
- var NO_PEERS = Object.freeze([]);
97
+ var NO_PEERS2 = Object.freeze([]);
66
98
  function usePeers(options) {
67
99
  const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME);
68
100
  return (0, import_react.useSyncExternalStore)(
69
101
  (0, import_react.useCallback)((onChange) => presence.subscribe(onChange), [presence]),
70
102
  () => presence.getPeers(),
71
- () => NO_PEERS
103
+ () => NO_PEERS2
72
104
  );
73
105
  }
74
106
 
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getSharedStore,
4
4
  usePeers
5
- } from "../chunk-PNFJW7QK.js";
5
+ } from "../chunk-YOXUZYKA.js";
6
6
 
7
7
  // src/devtools/inspector.tsx
8
8
  import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
package/dist/index.cjs CHANGED
@@ -16,17 +16,35 @@ var __copyProps = (to, from, except, desc) => {
16
16
  }
17
17
  return to;
18
18
  };
19
- var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
20
19
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
20
 
22
21
  // src/index.ts
23
22
  var src_exports = {};
24
23
  __export(src_exports, {
24
+ BroadcastChannelTransport: () => import_core3.BroadcastChannelTransport,
25
+ CID_PARAM: () => import_core3.CID_PARAM,
25
26
  DEFAULT_NAME: () => import_core.DEFAULT_NAME,
27
+ HandshakeTimeoutError: () => import_core3.HandshakeTimeoutError,
28
+ NoopTransport: () => import_core3.NoopTransport,
29
+ WindowClosedError: () => import_core3.WindowClosedError,
30
+ connectToOpener: () => import_core3.connectToOpener,
31
+ createChannel: () => import_core3.createChannel,
32
+ createLeader: () => import_core3.createLeader,
33
+ createPresence: () => import_core3.createPresence,
34
+ createSharedStore: () => import_core3.createSharedStore,
35
+ defaultTransport: () => import_core3.defaultTransport,
26
36
  defineChannel: () => defineChannel,
27
37
  defineStore: () => defineStore,
38
+ enableDebug: () => import_core3.enableDebug,
39
+ getBusNames: () => import_core3.getBusNames,
28
40
  getLeader: () => getLeader,
29
41
  getSharedStore: () => getSharedStore,
42
+ isBroadcastChannelAvailable: () => import_core3.isBroadcastChannelAvailable,
43
+ localStorageAdapter: () => import_core3.localStorageAdapter,
44
+ newer: () => import_core3.newer,
45
+ observeBus: () => import_core3.observeBus,
46
+ openWindow: () => import_core3.openWindow,
47
+ sessionStorageAdapter: () => import_core3.sessionStorageAdapter,
30
48
  useChannel: () => useChannel,
31
49
  useClientId: () => useClientId,
32
50
  useIsLeader: () => useIsLeader,
@@ -36,20 +54,84 @@ __export(src_exports, {
36
54
  useOpenedWindow: () => useOpenedWindow,
37
55
  usePeers: () => usePeers,
38
56
  useSend: () => useSend,
39
- useSharedState: () => useSharedState
57
+ useSharedState: () => useSharedState,
58
+ webStorageAdapter: () => import_core3.webStorageAdapter
40
59
  });
41
60
  module.exports = __toCommonJS(src_exports);
42
61
 
43
62
  // src/use-shared-state.ts
44
63
  var import_react = require("react");
45
64
 
65
+ // src/dev.ts
66
+ var inDev = false;
67
+ try {
68
+ inDev = process.env.NODE_ENV !== "production";
69
+ } catch {
70
+ }
71
+ var warned = /* @__PURE__ */ new Set();
72
+ function devWarn(message) {
73
+ if (!inDev || warned.has(message)) return;
74
+ warned.add(message);
75
+ console.warn(message);
76
+ }
77
+ var seenInitials = /* @__PURE__ */ new Map();
78
+ function warnOnInitialMismatch(storeName, key, initial) {
79
+ if (!inDev) return;
80
+ const id = `${storeName} ${key}`;
81
+ if (!seenInitials.has(id)) {
82
+ seenInitials.set(id, initial);
83
+ return;
84
+ }
85
+ const first = seenInitials.get(id);
86
+ const comparable = (v) => v === null || typeof v !== "object";
87
+ if (comparable(first) && comparable(initial) && !Object.is(first, initial)) {
88
+ devWarn(
89
+ `[use-everywhere] useSharedState('${key}') was called with different initial values (${String(first)} and ${String(initial)}). The first registration wins, so the second is ignored. Define the default once \u2014 defineStore, or a shared constant.`
90
+ );
91
+ }
92
+ }
93
+
46
94
  // src/registry.ts
47
95
  var import_core = require("@use-everywhere/core");
96
+
97
+ // src/server-stubs.ts
98
+ var SERVER_CLIENT_ID = "";
99
+ var EMPTY = Object.freeze({});
100
+ var NO_PEERS = Object.freeze([]);
101
+ var noop = () => {
102
+ };
103
+ var unsubscribe = () => noop;
104
+ var INERT = Object.freeze({
105
+ clientId: SERVER_CLIENT_ID,
106
+ // store
107
+ state: EMPTY,
108
+ getVersions: () => EMPTY,
109
+ set: noop,
110
+ subscribeKey: unsubscribe,
111
+ registerKey: noop,
112
+ // store + leader share getSnapshot; presence has getPeers
113
+ getSnapshot: () => EMPTY,
114
+ getPeers: () => NO_PEERS,
115
+ // leader
116
+ resign: noop,
117
+ setEligible: noop,
118
+ // common
119
+ subscribe: unsubscribe,
120
+ close: noop
121
+ });
122
+ var createServerStore = () => INERT;
123
+ var createServerPresence = () => INERT;
124
+ var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
125
+ var createServerLeader = () => ({ ...INERT, getSnapshot: () => NO_LEADER });
126
+ var createServerChannel = (name) => ({ ...INERT, name, post: noop, on: unsubscribe });
127
+
128
+ // src/registry.ts
48
129
  var stores = /* @__PURE__ */ new Map();
49
130
  var presences = /* @__PURE__ */ new Map();
50
131
  var channels = /* @__PURE__ */ new Map();
51
132
  var leaders = /* @__PURE__ */ new Map();
52
133
  var storeConfig = /* @__PURE__ */ new Map();
134
+ var isServer = () => typeof window === "undefined";
53
135
  var scopeOptions = {
54
136
  everywhere: {},
55
137
  tabs: { accept: (meta) => meta.kind !== "worker" },
@@ -58,12 +140,19 @@ var scopeOptions = {
58
140
  function getSharedStore(name = import_core.DEFAULT_NAME, scope = "everywhere") {
59
141
  return getStore(name, scope);
60
142
  }
143
+ function configSignature(options) {
144
+ const persist = options?.persist;
145
+ if (!persist) return "none";
146
+ return `persist:${persist.keys?.join(",") ?? "*"}:${persist.debounceMs ?? "default"}`;
147
+ }
61
148
  function configureStore(name, scope, options) {
62
149
  const key = `${scope} ${name}`;
63
150
  if (stores.has(key)) {
64
- throw new Error(
65
- `defineStore('${name}') ran after that store was already created. Move it to module scope \u2014 configuring a live store would silently hand you one without persistence.`
151
+ if (configSignature(storeConfig.get(key)) === configSignature(options)) return;
152
+ devWarn(
153
+ `[use-everywhere] defineStore('${name}') ran after that store was already created, with different options. The live store keeps the configuration it was built with. Move defineStore to module scope, before any component reads the store.`
66
154
  );
155
+ return;
67
156
  }
68
157
  storeConfig.set(key, options);
69
158
  }
@@ -71,7 +160,7 @@ function getStore(name, scope = "everywhere") {
71
160
  const key = `${scope} ${name}`;
72
161
  let store = stores.get(key);
73
162
  if (!store) {
74
- store = (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
163
+ store = isServer() ? createServerStore() : (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
75
164
  stores.set(key, store);
76
165
  }
77
166
  return store;
@@ -79,7 +168,7 @@ function getStore(name, scope = "everywhere") {
79
168
  function getPresence(name) {
80
169
  let presence = presences.get(name);
81
170
  if (!presence) {
82
- presence = (0, import_core.createPresence)(name);
171
+ presence = isServer() ? createServerPresence() : (0, import_core.createPresence)(name);
83
172
  presences.set(name, presence);
84
173
  }
85
174
  return presence;
@@ -87,15 +176,30 @@ function getPresence(name) {
87
176
  function getLeader(name, options) {
88
177
  let leader = leaders.get(name);
89
178
  if (!leader) {
90
- leader = (0, import_core.createLeader)(name, options);
179
+ leader = isServer() ? createServerLeader() : (0, import_core.createLeader)(name, options);
91
180
  leaders.set(name, leader);
181
+ if (options) leaderOptions.set(name, options);
182
+ } else if (options) {
183
+ warnOnLeaderOptionConflict(name, options);
92
184
  }
93
185
  return leader;
94
186
  }
187
+ var leaderOptions = /* @__PURE__ */ new Map();
188
+ function warnOnLeaderOptionConflict(name, options) {
189
+ const first = leaderOptions.get(name);
190
+ for (const key of ["heartbeatMs", "leaseMs"]) {
191
+ const requested = options[key];
192
+ if (requested !== void 0 && requested !== first?.[key]) {
193
+ devWarn(
194
+ `[use-everywhere] leader "${name}": ${key} ignored \u2014 the first useLeader/getLeader call fixes the election timings for this tab.`
195
+ );
196
+ }
197
+ }
198
+ }
95
199
  function getChannel(name) {
96
200
  let channel = channels.get(name);
97
201
  if (!channel) {
98
- channel = (0, import_core.createChannel)(name);
202
+ channel = isServer() ? createServerChannel(name) : (0, import_core.createChannel)(name);
99
203
  channels.set(name, channel);
100
204
  }
101
205
  return channel;
@@ -103,8 +207,10 @@ function getChannel(name) {
103
207
 
104
208
  // src/use-shared-state.ts
105
209
  function useSharedState(key, initial, options) {
106
- const store = getStore(options?.store ?? import_core.DEFAULT_NAME, options?.scope ?? "everywhere");
210
+ const storeName = options?.store ?? import_core.DEFAULT_NAME;
211
+ const store = getStore(storeName, options?.scope ?? "everywhere");
107
212
  store.registerKey(key, initial);
213
+ warnOnInitialMismatch(storeName, key, initial);
108
214
  const value = (0, import_react.useSyncExternalStore)(
109
215
  (0, import_react.useCallback)((onChange) => store.subscribeKey(key, onChange), [store, key]),
110
216
  () => store.getSnapshot()[key],
@@ -167,22 +273,31 @@ function defineStore(name, options = {}) {
167
273
 
168
274
  // src/use-peers.ts
169
275
  var import_react3 = require("react");
170
- var NO_PEERS = Object.freeze([]);
276
+ var NO_PEERS2 = Object.freeze([]);
171
277
  function usePeers(options) {
172
278
  const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME);
173
279
  return (0, import_react3.useSyncExternalStore)(
174
280
  (0, import_react3.useCallback)((onChange) => presence.subscribe(onChange), [presence]),
175
281
  () => presence.getPeers(),
176
- () => NO_PEERS
282
+ () => NO_PEERS2
177
283
  );
178
284
  }
179
285
  function useClientId(options) {
180
- return getPresence(options?.name ?? import_core.DEFAULT_NAME).clientId;
286
+ const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME);
287
+ return (0, import_react3.useSyncExternalStore)(
288
+ // The id never changes for the life of a presence engine, so there is
289
+ // nothing to subscribe to — but uSES still needs the post-hydration read,
290
+ // which it performs when it attaches this subscription.
291
+ (0, import_react3.useCallback)(() => () => {
292
+ }, [presence]),
293
+ () => presence.clientId,
294
+ () => SERVER_CLIENT_ID
295
+ );
181
296
  }
182
297
 
183
298
  // src/use-leader.ts
184
299
  var import_react4 = require("react");
185
- var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
300
+ var NO_LEADER2 = Object.freeze({ leaderId: null, isLeader: false });
186
301
  function useLeader(options) {
187
302
  const leader = getLeader(options?.name ?? import_core.DEFAULT_NAME, options);
188
303
  const eligible = options?.eligible;
@@ -193,7 +308,7 @@ function useLeader(options) {
193
308
  return (0, import_react4.useSyncExternalStore)(
194
309
  (0, import_react4.useCallback)((onChange) => leader.subscribe(onChange), [leader]),
195
310
  () => leader.getSnapshot(),
196
- () => NO_LEADER
311
+ () => NO_LEADER2
197
312
  );
198
313
  }
199
314
  function useIsLeader(options) {
@@ -214,31 +329,36 @@ function useLeaderEffect(effect, options) {
214
329
  // src/use-opened-window.ts
215
330
  var import_react5 = require("react");
216
331
  var import_core2 = require("@use-everywhere/core");
332
+ var IDLE = Object.freeze({
333
+ status: "idle",
334
+ result: void 0,
335
+ error: void 0
336
+ });
217
337
  function useOpenedWindow(factory) {
218
- const [status, setStatus] = (0, import_react5.useState)("idle");
219
- const [result, setResult] = (0, import_react5.useState)(void 0);
220
- const [error, setError] = (0, import_react5.useState)(void 0);
338
+ const [state, setState] = (0, import_react5.useState)(IDLE);
221
339
  const current = (0, import_react5.useRef)(null);
222
340
  const factoryRef = (0, import_react5.useRef)(factory);
223
- factoryRef.current = factory;
341
+ (0, import_react5.useEffect)(() => {
342
+ factoryRef.current = factory;
343
+ });
224
344
  const open = (0, import_react5.useCallback)(() => {
225
345
  current.current?.close();
226
346
  let opened;
227
347
  try {
228
348
  opened = factoryRef.current();
229
349
  } catch (err) {
230
- setStatus("error");
231
- setError(err);
350
+ setState({ status: "error", result: void 0, error: err });
232
351
  return;
233
352
  }
234
353
  current.current = opened;
235
- setStatus("opening");
236
- setResult(void 0);
237
- setError(void 0);
354
+ setState({ status: "opening", result: void 0, error: void 0 });
238
355
  const fresh = () => current.current === opened;
239
356
  opened.ready.then(
240
357
  () => {
241
- if (fresh()) setStatus((s) => s === "opening" ? "connected" : s);
358
+ if (!fresh()) return;
359
+ setState(
360
+ (s) => s.status === "opening" ? { status: "connected", result: void 0, error: void 0 } : s
361
+ );
242
362
  },
243
363
  () => {
244
364
  }
@@ -246,14 +366,13 @@ function useOpenedWindow(factory) {
246
366
  );
247
367
  opened.result.then(
248
368
  (value) => {
249
- if (!fresh()) return;
250
- setResult(value);
251
- setStatus("done");
369
+ if (fresh()) setState({ status: "done", result: value, error: void 0 });
252
370
  },
253
371
  (err) => {
254
372
  if (!fresh()) return;
255
- setError(err);
256
- setStatus(err instanceof import_core2.WindowClosedError ? "closed-early" : "error");
373
+ setState(
374
+ err instanceof import_core2.WindowClosedError ? { status: "closed-early", result: void 0, error: err } : { status: "error", result: void 0, error: err }
375
+ );
257
376
  }
258
377
  );
259
378
  }, []);
@@ -261,18 +380,37 @@ function useOpenedWindow(factory) {
261
380
  current.current?.post(type, payload);
262
381
  }, []);
263
382
  const close = (0, import_react5.useCallback)(() => current.current?.close(), []);
264
- return { open, status, result, error, post, close };
383
+ return { ...state, open, post, close };
265
384
  }
266
385
 
267
386
  // src/index.ts
268
- __reExport(src_exports, require("@use-everywhere/core"), module.exports);
387
+ var import_core3 = require("@use-everywhere/core");
269
388
  // Annotate the CommonJS export names for ESM import in node:
270
389
  0 && (module.exports = {
390
+ BroadcastChannelTransport,
391
+ CID_PARAM,
271
392
  DEFAULT_NAME,
393
+ HandshakeTimeoutError,
394
+ NoopTransport,
395
+ WindowClosedError,
396
+ connectToOpener,
397
+ createChannel,
398
+ createLeader,
399
+ createPresence,
400
+ createSharedStore,
401
+ defaultTransport,
272
402
  defineChannel,
273
403
  defineStore,
404
+ enableDebug,
405
+ getBusNames,
274
406
  getLeader,
275
407
  getSharedStore,
408
+ isBroadcastChannelAvailable,
409
+ localStorageAdapter,
410
+ newer,
411
+ observeBus,
412
+ openWindow,
413
+ sessionStorageAdapter,
276
414
  useChannel,
277
415
  useClientId,
278
416
  useIsLeader,
@@ -283,5 +421,5 @@ __reExport(src_exports, require("@use-everywhere/core"), module.exports);
283
421
  usePeers,
284
422
  useSend,
285
423
  useSharedState,
286
- ...require("@use-everywhere/core")
424
+ webStorageAdapter
287
425
  });
package/dist/index.d.cts CHANGED
@@ -1,6 +1,5 @@
1
- import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow } from '@use-everywhere/core';
2
- export * from '@use-everywhere/core';
3
- export { DEFAULT_NAME } from '@use-everywhere/core';
1
+ import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow, WindowClosedError } from '@use-everywhere/core';
2
+ export { BroadcastChannelTransport, BusEvent, BusObserver, BusWire, CID_PARAM, Channel, CommonOptions, ConnectToOpenerOptions, DEFAULT_NAME, DebugOptions, HandshakeTimeoutError, Leader, LeaderOptions, LeaderSnapshot, MessageEventLike, MessageMap, MessageMeta, NoopTransport, OpenWindowOptions, OpenedWindow, OpenerConnection, Peer, PeerKind, PersistAdapter, PersistOptions, Persisted, Presence, PresenceOptions, SharedStore, SharedStoreOptions, StorageLike, Transport, Version, WebStorageAdapterOptions, WindowClosedError, WindowEventTarget, WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter } from '@use-everywhere/core';
4
3
 
5
4
  /**
6
5
  * How far a shared value travels:
@@ -21,6 +20,12 @@ interface UseSharedStateOptions {
21
20
  * this origin. Late-joining tabs hydrate to the current value; concurrent
22
21
  * writes converge last-writer-wins. Pass options.scope to delimit how much
23
22
  * is shared ('everywhere' | 'tabs' | 'tab').
23
+ *
24
+ * Note the convergence rule is last-writer-wins per key, not per operation:
25
+ * two tabs running `set(n => n + 1)` at the same moment agree on one value,
26
+ * and one of the increments is lost. For counters and other accumulating
27
+ * writes, drive them from a single tab (useLeaderEffect) until an op-based
28
+ * primitive lands.
24
29
  */
25
30
  declare function useSharedState<T>(key: string, initial: T, options?: UseSharedStateOptions): [T, (next: T | ((prev: T) => T)) => void];
26
31
 
@@ -105,7 +110,16 @@ declare function defineStore<S extends Record<string, unknown> = Record<string,
105
110
  declare function usePeers(options?: {
106
111
  name?: string;
107
112
  }): readonly Peer[];
108
- /** This client's own id on the presence bus (matches patch origin ids). */
113
+ /**
114
+ * This client's own id on the presence bus (matches patch origin ids).
115
+ *
116
+ * Read through useSyncExternalStore rather than returned straight from render,
117
+ * because the id is minted per environment: a server would render one value and
118
+ * the browser a different one, and any component that puts it in the DOM would
119
+ * mismatch on hydration. The server snapshot is a constant empty string, which
120
+ * React also uses for the client's hydrating render, so markup matches; the
121
+ * real id arrives in the commit straight after. Treat `''` as "not known yet".
122
+ */
109
123
  declare function useClientId(options?: {
110
124
  name?: string;
111
125
  }): string;
@@ -130,23 +144,59 @@ declare function useIsLeader(options?: UseLeaderOptions): boolean;
130
144
  declare function useLeaderEffect(effect: () => void | (() => void), options?: UseLeaderOptions): void;
131
145
 
132
146
  type OpenedWindowStatus = 'idle' | 'opening' | 'connected' | 'done' | 'closed-early' | 'error';
133
- interface UseOpenedWindow<Out extends MessageMap, In extends MessageMap, R> {
147
+ /**
148
+ * The flow's state as a discriminated union, so `status` narrows `result` and
149
+ * `error` for you. Three independent fields could not: `result` was `R |
150
+ * undefined` even in the 'done' branch, which is why reading it needed a
151
+ * non-null assertion at every call site.
152
+ */
153
+ type OpenedWindowState<R> = {
154
+ status: 'idle';
155
+ result: undefined;
156
+ error: undefined;
157
+ } | {
158
+ status: 'opening';
159
+ result: undefined;
160
+ error: undefined;
161
+ } | {
162
+ status: 'connected';
163
+ result: undefined;
164
+ error: undefined;
165
+ } | {
166
+ status: 'done';
167
+ result: R;
168
+ error: undefined;
169
+ }
170
+ /** The child window went away before calling finish(). */
171
+ | {
172
+ status: 'closed-early';
173
+ result: undefined;
174
+ error: WindowClosedError;
175
+ }
176
+ /** The handshake timed out, the popup was blocked, or the factory threw. */
177
+ | {
178
+ status: 'error';
179
+ result: undefined;
180
+ error: unknown;
181
+ };
182
+ interface OpenedWindowControls<Out extends MessageMap, In extends MessageMap, R> {
134
183
  /** Call from a click handler (popup blockers require a user gesture). */
135
184
  open: () => void;
136
- status: OpenedWindowStatus;
137
- /** The child's finish() value, once status is 'done'. */
138
- result: R | undefined;
139
- error: unknown;
140
185
  /** Post to the child; no-op while nothing is open. */
141
186
  post: OpenedWindow<Out, In, R>['post'];
142
187
  close: () => void;
143
188
  }
189
+ type UseOpenedWindow<Out extends MessageMap, In extends MessageMap, R> = OpenedWindowState<R> & OpenedWindowControls<Out, In, R>;
144
190
 
145
191
  /**
146
192
  * Drive an openWindow() flow from a component: the factory is called on
147
193
  * open(), and the child window's lifecycle is folded into render state.
148
194
  * Reopening replaces the previous window; a stale window's outcome is ignored.
195
+ *
196
+ * The child window deliberately outlives an unmount: closing a payment page
197
+ * because a route changed would lose the user's in-flight transaction. Call
198
+ * close() if the component owns the window's lifetime.
149
199
  */
150
200
  declare function useOpenedWindow<Out extends MessageMap, In extends MessageMap, R = unknown>(factory: () => OpenedWindow<Out, In, R>): UseOpenedWindow<Out, In, R>;
151
201
 
152
- export { type AnyStore, type ChannelHooks, type DefineStoreOptions, type OpenedWindowStatus, type ShareScope, type StoreHooks, type UseLeaderOptions, type UseOpenedWindow, type UseSharedStateOptions, defineChannel, defineStore, getLeader, getSharedStore, useChannel, useClientId, useIsLeader, useLeader, useLeaderEffect, useMessage, useOpenedWindow, usePeers, useSend, useSharedState };
202
+ export { type AnyStore, type ChannelHooks, type DefineStoreOptions, type OpenedWindowControls, type OpenedWindowState, type OpenedWindowStatus, type ShareScope, type StoreHooks, type UseLeaderOptions, type UseOpenedWindow, type UseSharedStateOptions, defineChannel, defineStore, getLeader, getSharedStore, useChannel, useClientId, useIsLeader, useLeader, useLeaderEffect, useMessage, useOpenedWindow, usePeers, useSend, useSharedState };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow } from '@use-everywhere/core';
2
- export * from '@use-everywhere/core';
3
- export { DEFAULT_NAME } from '@use-everywhere/core';
1
+ import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow, WindowClosedError } from '@use-everywhere/core';
2
+ export { BroadcastChannelTransport, BusEvent, BusObserver, BusWire, CID_PARAM, Channel, CommonOptions, ConnectToOpenerOptions, DEFAULT_NAME, DebugOptions, HandshakeTimeoutError, Leader, LeaderOptions, LeaderSnapshot, MessageEventLike, MessageMap, MessageMeta, NoopTransport, OpenWindowOptions, OpenedWindow, OpenerConnection, Peer, PeerKind, PersistAdapter, PersistOptions, Persisted, Presence, PresenceOptions, SharedStore, SharedStoreOptions, StorageLike, Transport, Version, WebStorageAdapterOptions, WindowClosedError, WindowEventTarget, WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter } from '@use-everywhere/core';
4
3
 
5
4
  /**
6
5
  * How far a shared value travels:
@@ -21,6 +20,12 @@ interface UseSharedStateOptions {
21
20
  * this origin. Late-joining tabs hydrate to the current value; concurrent
22
21
  * writes converge last-writer-wins. Pass options.scope to delimit how much
23
22
  * is shared ('everywhere' | 'tabs' | 'tab').
23
+ *
24
+ * Note the convergence rule is last-writer-wins per key, not per operation:
25
+ * two tabs running `set(n => n + 1)` at the same moment agree on one value,
26
+ * and one of the increments is lost. For counters and other accumulating
27
+ * writes, drive them from a single tab (useLeaderEffect) until an op-based
28
+ * primitive lands.
24
29
  */
25
30
  declare function useSharedState<T>(key: string, initial: T, options?: UseSharedStateOptions): [T, (next: T | ((prev: T) => T)) => void];
26
31
 
@@ -105,7 +110,16 @@ declare function defineStore<S extends Record<string, unknown> = Record<string,
105
110
  declare function usePeers(options?: {
106
111
  name?: string;
107
112
  }): readonly Peer[];
108
- /** This client's own id on the presence bus (matches patch origin ids). */
113
+ /**
114
+ * This client's own id on the presence bus (matches patch origin ids).
115
+ *
116
+ * Read through useSyncExternalStore rather than returned straight from render,
117
+ * because the id is minted per environment: a server would render one value and
118
+ * the browser a different one, and any component that puts it in the DOM would
119
+ * mismatch on hydration. The server snapshot is a constant empty string, which
120
+ * React also uses for the client's hydrating render, so markup matches; the
121
+ * real id arrives in the commit straight after. Treat `''` as "not known yet".
122
+ */
109
123
  declare function useClientId(options?: {
110
124
  name?: string;
111
125
  }): string;
@@ -130,23 +144,59 @@ declare function useIsLeader(options?: UseLeaderOptions): boolean;
130
144
  declare function useLeaderEffect(effect: () => void | (() => void), options?: UseLeaderOptions): void;
131
145
 
132
146
  type OpenedWindowStatus = 'idle' | 'opening' | 'connected' | 'done' | 'closed-early' | 'error';
133
- interface UseOpenedWindow<Out extends MessageMap, In extends MessageMap, R> {
147
+ /**
148
+ * The flow's state as a discriminated union, so `status` narrows `result` and
149
+ * `error` for you. Three independent fields could not: `result` was `R |
150
+ * undefined` even in the 'done' branch, which is why reading it needed a
151
+ * non-null assertion at every call site.
152
+ */
153
+ type OpenedWindowState<R> = {
154
+ status: 'idle';
155
+ result: undefined;
156
+ error: undefined;
157
+ } | {
158
+ status: 'opening';
159
+ result: undefined;
160
+ error: undefined;
161
+ } | {
162
+ status: 'connected';
163
+ result: undefined;
164
+ error: undefined;
165
+ } | {
166
+ status: 'done';
167
+ result: R;
168
+ error: undefined;
169
+ }
170
+ /** The child window went away before calling finish(). */
171
+ | {
172
+ status: 'closed-early';
173
+ result: undefined;
174
+ error: WindowClosedError;
175
+ }
176
+ /** The handshake timed out, the popup was blocked, or the factory threw. */
177
+ | {
178
+ status: 'error';
179
+ result: undefined;
180
+ error: unknown;
181
+ };
182
+ interface OpenedWindowControls<Out extends MessageMap, In extends MessageMap, R> {
134
183
  /** Call from a click handler (popup blockers require a user gesture). */
135
184
  open: () => void;
136
- status: OpenedWindowStatus;
137
- /** The child's finish() value, once status is 'done'. */
138
- result: R | undefined;
139
- error: unknown;
140
185
  /** Post to the child; no-op while nothing is open. */
141
186
  post: OpenedWindow<Out, In, R>['post'];
142
187
  close: () => void;
143
188
  }
189
+ type UseOpenedWindow<Out extends MessageMap, In extends MessageMap, R> = OpenedWindowState<R> & OpenedWindowControls<Out, In, R>;
144
190
 
145
191
  /**
146
192
  * Drive an openWindow() flow from a component: the factory is called on
147
193
  * open(), and the child window's lifecycle is folded into render state.
148
194
  * Reopening replaces the previous window; a stale window's outcome is ignored.
195
+ *
196
+ * The child window deliberately outlives an unmount: closing a payment page
197
+ * because a route changed would lose the user's in-flight transaction. Call
198
+ * close() if the component owns the window's lifetime.
149
199
  */
150
200
  declare function useOpenedWindow<Out extends MessageMap, In extends MessageMap, R = unknown>(factory: () => OpenedWindow<Out, In, R>): UseOpenedWindow<Out, In, R>;
151
201
 
152
- export { type AnyStore, type ChannelHooks, type DefineStoreOptions, type OpenedWindowStatus, type ShareScope, type StoreHooks, type UseLeaderOptions, type UseOpenedWindow, type UseSharedStateOptions, defineChannel, defineStore, getLeader, getSharedStore, useChannel, useClientId, useIsLeader, useLeader, useLeaderEffect, useMessage, useOpenedWindow, usePeers, useSend, useSharedState };
202
+ export { type AnyStore, type ChannelHooks, type DefineStoreOptions, type OpenedWindowControls, type OpenedWindowState, type OpenedWindowStatus, type ShareScope, type StoreHooks, type UseLeaderOptions, type UseOpenedWindow, type UseSharedStateOptions, defineChannel, defineStore, getLeader, getSharedStore, useChannel, useClientId, useIsLeader, useLeader, useLeaderEffect, useMessage, useOpenedWindow, usePeers, useSend, useSharedState };
package/dist/index.js CHANGED
@@ -7,14 +7,17 @@ import {
7
7
  getSharedStore,
8
8
  getStore,
9
9
  useClientId,
10
- usePeers
11
- } from "./chunk-PNFJW7QK.js";
10
+ usePeers,
11
+ warnOnInitialMismatch
12
+ } from "./chunk-YOXUZYKA.js";
12
13
 
13
14
  // src/use-shared-state.ts
14
15
  import { useCallback, useSyncExternalStore } from "react";
15
16
  function useSharedState(key, initial, options) {
16
- const store = getStore(options?.store ?? DEFAULT_NAME, options?.scope ?? "everywhere");
17
+ const storeName = options?.store ?? DEFAULT_NAME;
18
+ const store = getStore(storeName, options?.scope ?? "everywhere");
17
19
  store.registerKey(key, initial);
20
+ warnOnInitialMismatch(storeName, key, initial);
18
21
  const value = useSyncExternalStore(
19
22
  useCallback((onChange) => store.subscribeKey(key, onChange), [store, key]),
20
23
  () => store.getSnapshot()[key],
@@ -107,33 +110,38 @@ function useLeaderEffect(effect, options) {
107
110
  }
108
111
 
109
112
  // src/use-opened-window.ts
110
- import { useCallback as useCallback3, useRef as useRef3, useState } from "react";
113
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState } from "react";
111
114
  import { WindowClosedError } from "@use-everywhere/core";
115
+ var IDLE = Object.freeze({
116
+ status: "idle",
117
+ result: void 0,
118
+ error: void 0
119
+ });
112
120
  function useOpenedWindow(factory) {
113
- const [status, setStatus] = useState("idle");
114
- const [result, setResult] = useState(void 0);
115
- const [error, setError] = useState(void 0);
121
+ const [state, setState] = useState(IDLE);
116
122
  const current = useRef3(null);
117
123
  const factoryRef = useRef3(factory);
118
- factoryRef.current = factory;
124
+ useEffect3(() => {
125
+ factoryRef.current = factory;
126
+ });
119
127
  const open = useCallback3(() => {
120
128
  current.current?.close();
121
129
  let opened;
122
130
  try {
123
131
  opened = factoryRef.current();
124
132
  } catch (err) {
125
- setStatus("error");
126
- setError(err);
133
+ setState({ status: "error", result: void 0, error: err });
127
134
  return;
128
135
  }
129
136
  current.current = opened;
130
- setStatus("opening");
131
- setResult(void 0);
132
- setError(void 0);
137
+ setState({ status: "opening", result: void 0, error: void 0 });
133
138
  const fresh = () => current.current === opened;
134
139
  opened.ready.then(
135
140
  () => {
136
- if (fresh()) setStatus((s) => s === "opening" ? "connected" : s);
141
+ if (!fresh()) return;
142
+ setState(
143
+ (s) => s.status === "opening" ? { status: "connected", result: void 0, error: void 0 } : s
144
+ );
137
145
  },
138
146
  () => {
139
147
  }
@@ -141,14 +149,13 @@ function useOpenedWindow(factory) {
141
149
  );
142
150
  opened.result.then(
143
151
  (value) => {
144
- if (!fresh()) return;
145
- setResult(value);
146
- setStatus("done");
152
+ if (fresh()) setState({ status: "done", result: value, error: void 0 });
147
153
  },
148
154
  (err) => {
149
155
  if (!fresh()) return;
150
- setError(err);
151
- setStatus(err instanceof WindowClosedError ? "closed-early" : "error");
156
+ setState(
157
+ err instanceof WindowClosedError ? { status: "closed-early", result: void 0, error: err } : { status: "error", result: void 0, error: err }
158
+ );
152
159
  }
153
160
  );
154
161
  }, []);
@@ -156,17 +163,57 @@ function useOpenedWindow(factory) {
156
163
  current.current?.post(type, payload);
157
164
  }, []);
158
165
  const close = useCallback3(() => current.current?.close(), []);
159
- return { open, status, result, error, post, close };
166
+ return { ...state, open, post, close };
160
167
  }
161
168
 
162
169
  // src/index.ts
163
- export * from "@use-everywhere/core";
170
+ import {
171
+ createChannel,
172
+ createSharedStore,
173
+ createPresence,
174
+ createLeader,
175
+ webStorageAdapter,
176
+ localStorageAdapter,
177
+ sessionStorageAdapter,
178
+ openWindow,
179
+ connectToOpener,
180
+ CID_PARAM,
181
+ WindowClosedError as WindowClosedError2,
182
+ HandshakeTimeoutError,
183
+ newer,
184
+ observeBus,
185
+ enableDebug,
186
+ getBusNames,
187
+ BroadcastChannelTransport,
188
+ NoopTransport,
189
+ defaultTransport,
190
+ isBroadcastChannelAvailable
191
+ } from "@use-everywhere/core";
164
192
  export {
193
+ BroadcastChannelTransport,
194
+ CID_PARAM,
165
195
  DEFAULT_NAME,
196
+ HandshakeTimeoutError,
197
+ NoopTransport,
198
+ WindowClosedError2 as WindowClosedError,
199
+ connectToOpener,
200
+ createChannel,
201
+ createLeader,
202
+ createPresence,
203
+ createSharedStore,
204
+ defaultTransport,
166
205
  defineChannel,
167
206
  defineStore,
207
+ enableDebug,
208
+ getBusNames,
168
209
  getLeader,
169
210
  getSharedStore,
211
+ isBroadcastChannelAvailable,
212
+ localStorageAdapter,
213
+ newer,
214
+ observeBus,
215
+ openWindow,
216
+ sessionStorageAdapter,
170
217
  useChannel,
171
218
  useClientId,
172
219
  useIsLeader,
@@ -176,5 +223,6 @@ export {
176
223
  useOpenedWindow,
177
224
  usePeers,
178
225
  useSend,
179
- useSharedState
226
+ useSharedState,
227
+ webStorageAdapter
180
228
  };
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/testing.ts
22
+ var testing_exports = {};
23
+ __export(testing_exports, {
24
+ MemoryHub: () => import_testing.MemoryHub,
25
+ MemoryTransport: () => import_testing.MemoryTransport
26
+ });
27
+ module.exports = __toCommonJS(testing_exports);
28
+ var import_testing = require("@use-everywhere/core/testing");
29
+ // Annotate the CommonJS export names for ESM import in node:
30
+ 0 && (module.exports = {
31
+ MemoryHub,
32
+ MemoryTransport
33
+ });
@@ -0,0 +1 @@
1
+ export { MemoryHub, MemoryTransport } from '@use-everywhere/core/testing';
@@ -0,0 +1 @@
1
+ export { MemoryHub, MemoryTransport } from '@use-everywhere/core/testing';
@@ -0,0 +1,8 @@
1
+ 'use client';
2
+
3
+ // src/testing.ts
4
+ import { MemoryHub, MemoryTransport } from "@use-everywhere/core/testing";
5
+ export {
6
+ MemoryHub,
7
+ MemoryTransport
8
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-everywhere",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "React hooks for state and messages shared across tabs, windows, and workers",
5
5
  "license": "MIT",
6
6
  "author": "Jonatan Kruszewski <jonakrusze@gmail.com>",
@@ -49,6 +49,16 @@
49
49
  "types": "./dist/devtools/index.d.cts",
50
50
  "default": "./dist/devtools/index.cjs"
51
51
  }
52
+ },
53
+ "./testing": {
54
+ "import": {
55
+ "types": "./dist/testing.d.ts",
56
+ "default": "./dist/testing.js"
57
+ },
58
+ "require": {
59
+ "types": "./dist/testing.d.cts",
60
+ "default": "./dist/testing.cjs"
61
+ }
52
62
  }
53
63
  },
54
64
  "files": [
@@ -59,53 +69,59 @@
59
69
  "name": "everything (import *)",
60
70
  "path": "dist/index.js",
61
71
  "import": "*",
62
- "limit": "5.4 kB"
72
+ "limit": "6.6 kB"
63
73
  },
64
74
  {
65
75
  "name": "useSharedState",
66
76
  "path": "dist/index.js",
67
77
  "import": "{ useSharedState }",
68
- "limit": "1.8 kB"
78
+ "limit": "2.85 kB"
69
79
  },
70
80
  {
71
81
  "name": "useChannel + useMessage + useSend",
72
82
  "path": "dist/index.js",
73
83
  "import": "{ useChannel, useMessage, useSend }",
74
- "limit": "1 kB"
84
+ "limit": "1.6 kB"
75
85
  },
76
86
  {
77
87
  "name": "usePeers + useClientId",
78
88
  "path": "dist/index.js",
79
89
  "import": "{ usePeers, useClientId }",
80
- "limit": "1.1 kB"
90
+ "limit": "1.7 kB"
81
91
  },
82
92
  {
83
93
  "name": "useOpenedWindow + openWindow",
84
94
  "path": "dist/index.js",
85
95
  "import": "{ useOpenedWindow, openWindow }",
86
- "limit": "1.4 kB"
96
+ "limit": "1.5 kB"
87
97
  },
88
98
  {
89
99
  "name": "useLeader + useLeaderEffect",
90
100
  "path": "dist/index.js",
91
101
  "import": "{ useLeader, useLeaderEffect }",
92
- "limit": "1.8 kB"
102
+ "limit": "2.15 kB"
93
103
  },
94
104
  {
95
105
  "name": "defineStore (persisted)",
96
106
  "path": "dist/index.js",
97
107
  "import": "{ defineStore, localStorageAdapter }",
98
- "limit": "2.3 kB"
108
+ "limit": "3.3 kB"
99
109
  },
100
110
  {
101
111
  "name": "Inspector (devtools subpath)",
102
112
  "path": "dist/devtools/index.js",
103
113
  "import": "{ Inspector }",
104
114
  "limit": "5 kB"
115
+ },
116
+ {
117
+ "name": "MemoryHub (testing subpath)",
118
+ "path": "dist/testing.js",
119
+ "import": "{ MemoryHub }",
120
+ "limit": "400 B"
105
121
  }
106
122
  ],
107
123
  "dependencies": {
108
- "@use-everywhere/core": "0.4.1"
124
+ "@use-everywhere/core": "0.6.0"
109
125
  },
110
126
  "peerDependencies": {
111
127
  "react": ">=18"
@@ -130,6 +146,9 @@
130
146
  "*": {
131
147
  "devtools": [
132
148
  "./dist/devtools/index.d.ts"
149
+ ],
150
+ "testing": [
151
+ "./dist/testing.d.ts"
133
152
  ]
134
153
  }
135
154
  },
@@ -1,92 +0,0 @@
1
- 'use client';
2
-
3
- // src/registry.ts
4
- import {
5
- createChannel,
6
- createLeader,
7
- createPresence,
8
- createSharedStore,
9
- DEFAULT_NAME,
10
- NoopTransport
11
- } from "@use-everywhere/core";
12
- var stores = /* @__PURE__ */ new Map();
13
- var presences = /* @__PURE__ */ new Map();
14
- var channels = /* @__PURE__ */ new Map();
15
- var leaders = /* @__PURE__ */ new Map();
16
- var storeConfig = /* @__PURE__ */ new Map();
17
- var scopeOptions = {
18
- everywhere: {},
19
- tabs: { accept: (meta) => meta.kind !== "worker" },
20
- tab: { transport: () => new NoopTransport() }
21
- };
22
- function getSharedStore(name = DEFAULT_NAME, scope = "everywhere") {
23
- return getStore(name, scope);
24
- }
25
- function configureStore(name, scope, options) {
26
- const key = `${scope} ${name}`;
27
- if (stores.has(key)) {
28
- throw new Error(
29
- `defineStore('${name}') ran after that store was already created. Move it to module scope \u2014 configuring a live store would silently hand you one without persistence.`
30
- );
31
- }
32
- storeConfig.set(key, options);
33
- }
34
- function getStore(name, scope = "everywhere") {
35
- const key = `${scope} ${name}`;
36
- let store = stores.get(key);
37
- if (!store) {
38
- store = createSharedStore(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
39
- stores.set(key, store);
40
- }
41
- return store;
42
- }
43
- function getPresence(name) {
44
- let presence = presences.get(name);
45
- if (!presence) {
46
- presence = createPresence(name);
47
- presences.set(name, presence);
48
- }
49
- return presence;
50
- }
51
- function getLeader(name, options) {
52
- let leader = leaders.get(name);
53
- if (!leader) {
54
- leader = createLeader(name, options);
55
- leaders.set(name, leader);
56
- }
57
- return leader;
58
- }
59
- function getChannel(name) {
60
- let channel = channels.get(name);
61
- if (!channel) {
62
- channel = createChannel(name);
63
- channels.set(name, channel);
64
- }
65
- return channel;
66
- }
67
-
68
- // src/use-peers.ts
69
- import { useCallback, useSyncExternalStore } from "react";
70
- var NO_PEERS = Object.freeze([]);
71
- function usePeers(options) {
72
- const presence = getPresence(options?.name ?? DEFAULT_NAME);
73
- return useSyncExternalStore(
74
- useCallback((onChange) => presence.subscribe(onChange), [presence]),
75
- () => presence.getPeers(),
76
- () => NO_PEERS
77
- );
78
- }
79
- function useClientId(options) {
80
- return getPresence(options?.name ?? DEFAULT_NAME).clientId;
81
- }
82
-
83
- export {
84
- DEFAULT_NAME,
85
- getSharedStore,
86
- configureStore,
87
- getStore,
88
- getLeader,
89
- getChannel,
90
- usePeers,
91
- useClientId
92
- };