use-everywhere 0.7.0 → 0.9.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/dist/index.js CHANGED
@@ -1,15 +1,18 @@
1
1
  'use client';
2
2
  import {
3
3
  DEFAULT_NAME,
4
+ configureChannel,
4
5
  configureStore,
5
6
  getChannel,
6
7
  getLeader,
8
+ getReducer,
7
9
  getSharedStore,
8
10
  getStore,
9
11
  useClientId,
10
12
  usePeers,
13
+ usePresenceMetadata,
11
14
  warnOnInitialMismatch
12
- } from "./chunk-LNJEF4DI.js";
15
+ } from "./chunk-R7PBPLF7.js";
13
16
 
14
17
  // src/use-shared-state.ts
15
18
  import { useCallback, useSyncExternalStore } from "react";
@@ -17,7 +20,9 @@ function useSharedState(key, initial, options) {
17
20
  const storeName = options?.store ?? DEFAULT_NAME;
18
21
  const store = getStore(storeName, options?.scope ?? "everywhere");
19
22
  store.registerKey(key, initial);
20
- warnOnInitialMismatch(storeName, key, initial);
23
+ if (process.env.NODE_ENV !== "production") {
24
+ warnOnInitialMismatch(storeName, key, initial);
25
+ }
21
26
  const value = useSyncExternalStore(
22
27
  useCallback((onChange) => store.subscribeKey(key, onChange), [store, key]),
23
28
  () => store.getSnapshot()[key],
@@ -30,27 +35,98 @@ function useSharedState(key, initial, options) {
30
35
  return [value, setValue];
31
36
  }
32
37
 
38
+ // src/use-shared-reducer.ts
39
+ import { useCallback as useCallback2, useSyncExternalStore as useSyncExternalStore2 } from "react";
40
+ function useSharedReducer(reducer, initial, options) {
41
+ const engine = getReducer(
42
+ options?.name ?? DEFAULT_NAME,
43
+ options?.key ?? "default",
44
+ reducer,
45
+ initial
46
+ );
47
+ const value = useSyncExternalStore2(
48
+ useCallback2((onChange) => engine.subscribe(onChange), [engine]),
49
+ () => engine.getSnapshot(),
50
+ () => initial
51
+ );
52
+ const dispatch = useCallback2((action) => engine.dispatch(action), [engine]);
53
+ return [value, dispatch];
54
+ }
55
+
56
+ // src/use-shared-selector.ts
57
+ import { useCallback as useCallback3, useRef, useSyncExternalStore as useSyncExternalStore3 } from "react";
58
+ function useSharedStore(selector, options) {
59
+ const store = getStore(options?.store ?? DEFAULT_NAME, options?.scope ?? "everywhere");
60
+ const equal = options?.equal ?? Object.is;
61
+ const selectorRef = useRef(selector);
62
+ selectorRef.current = selector;
63
+ const equalRef = useRef(equal);
64
+ equalRef.current = equal;
65
+ const cache = useRef(null);
66
+ const getSelection = useCallback3(() => {
67
+ const source = store.getSnapshot();
68
+ const select = selectorRef.current;
69
+ const previous = cache.current;
70
+ if (previous && previous.source === source && previous.select === select) return previous.value;
71
+ const next = select(source);
72
+ const value = previous && equalRef.current(previous.value, next) ? previous.value : next;
73
+ cache.current = { source, select, value };
74
+ return value;
75
+ }, [store]);
76
+ return useSyncExternalStore3(
77
+ useCallback3((onChange) => store.subscribe(onChange), [store]),
78
+ getSelection,
79
+ getSelection
80
+ );
81
+ }
82
+ function shallowEqual(a, b) {
83
+ if (Object.is(a, b)) return true;
84
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
85
+ const left = a;
86
+ const right = b;
87
+ const keys = Object.keys(left);
88
+ if (keys.length !== Object.keys(right).length) return false;
89
+ return keys.every((key) => Object.is(left[key], right[key]));
90
+ }
91
+
33
92
  // src/use-message.ts
34
- import { useEffect, useRef } from "react";
93
+ import { useEffect, useRef as useRef2 } from "react";
35
94
  function useChannel(name) {
36
95
  return getChannel(name);
37
96
  }
38
- function useMessage(channel, type, handler) {
39
- const handlerRef = useRef(handler);
97
+ function useMessage(channel, type, handler, options) {
98
+ const handlerRef = useRef2(handler);
40
99
  useEffect(() => {
41
100
  handlerRef.current = handler;
42
101
  });
43
- useEffect(
44
- () => channel.on(type, (payload, meta) => handlerRef.current(payload, meta)),
45
- [channel, type]
46
- );
102
+ const enabled = options?.enabled ?? true;
103
+ const once = options?.once ?? false;
104
+ useEffect(() => {
105
+ if (!enabled) return;
106
+ return channel.on(type, (payload, meta) => handlerRef.current(payload, meta), { once });
107
+ }, [channel, type, enabled, once]);
108
+ }
109
+ function useAnswer(channel, type, responder, options) {
110
+ const responderRef = useRef2(responder);
111
+ useEffect(() => {
112
+ responderRef.current = responder;
113
+ });
114
+ const enabled = options?.enabled ?? true;
115
+ useEffect(() => {
116
+ if (!enabled) return;
117
+ return channel.answer(type, (payload, meta) => responderRef.current(payload, meta));
118
+ }, [channel, type, enabled]);
47
119
  }
48
120
  function useSend(channel) {
49
121
  return channel.post;
50
122
  }
123
+ function useAsk(channel) {
124
+ return channel.ask;
125
+ }
51
126
 
52
127
  // src/define-channel.ts
53
- function defineChannel(name) {
128
+ function defineChannel(name, options) {
129
+ if (options) configureChannel(name, options);
54
130
  const useBoundSend = () => useSend(useChannel(name));
55
131
  const useBoundMessage = (type, handler) => useMessage(useChannel(name), type, handler);
56
132
  return {
@@ -68,7 +144,10 @@ function defineStore(name, options = {}) {
68
144
  persist: {
69
145
  adapter: options.persist,
70
146
  ...options.persistKeys ? { keys: options.persistKeys } : {},
71
- ...options.persistDebounceMs === void 0 ? {} : { debounceMs: options.persistDebounceMs }
147
+ ...options.persistDebounceMs === void 0 ? {} : { debounceMs: options.persistDebounceMs },
148
+ ...options.persistVersion === void 0 ? {} : { version: options.persistVersion },
149
+ ...options.migrate ? { migrate: options.migrate } : {},
150
+ ...options.onRestoreError ? { onRestoreError: options.onRestoreError } : {}
72
151
  }
73
152
  });
74
153
  }
@@ -78,18 +157,35 @@ function defineStore(name, options = {}) {
78
157
  };
79
158
  }
80
159
 
160
+ // src/use-hydrated.ts
161
+ import { useEffect as useEffect2, useState } from "react";
162
+ function useHydrated(options) {
163
+ const store = getStore(options?.store ?? DEFAULT_NAME, options?.scope ?? "everywhere");
164
+ const [ready, setReady] = useState(false);
165
+ useEffect2(() => {
166
+ let cancelled = false;
167
+ void store.hydrated.then(() => {
168
+ if (!cancelled) setReady(true);
169
+ });
170
+ return () => {
171
+ cancelled = true;
172
+ };
173
+ }, [store]);
174
+ return ready;
175
+ }
176
+
81
177
  // src/use-leader.ts
82
- import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
178
+ import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore4 } from "react";
83
179
  var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
84
180
  function useLeader(options) {
85
181
  const leader = getLeader(options?.name ?? DEFAULT_NAME, options);
86
182
  const eligible = options?.eligible;
87
- useEffect2(() => {
183
+ useEffect3(() => {
88
184
  if (eligible === void 0) return;
89
185
  leader.setEligible(eligible);
90
186
  }, [leader, eligible]);
91
- return useSyncExternalStore2(
92
- useCallback2((onChange) => leader.subscribe(onChange), [leader]),
187
+ return useSyncExternalStore4(
188
+ useCallback4((onChange) => leader.subscribe(onChange), [leader]),
93
189
  () => leader.getSnapshot(),
94
190
  () => NO_LEADER
95
191
  );
@@ -99,18 +195,42 @@ function useIsLeader(options) {
99
195
  }
100
196
  function useLeaderEffect(effect, options) {
101
197
  const { isLeader } = useLeader(options);
102
- const effectRef = useRef2(effect);
103
- useEffect2(() => {
198
+ const effectRef = useRef3(effect);
199
+ useEffect3(() => {
104
200
  effectRef.current = effect;
105
201
  });
106
- useEffect2(() => {
202
+ useEffect3(() => {
107
203
  if (!isLeader) return;
108
204
  return effectRef.current();
109
205
  }, [isLeader]);
110
206
  }
111
207
 
208
+ // src/namespace.ts
209
+ import {
210
+ createNamespace as createCoreNamespace
211
+ } from "@use-everywhere/core";
212
+ function createNamespace(namespace) {
213
+ const core = createCoreNamespace(namespace);
214
+ const { busName } = core;
215
+ return {
216
+ ...core,
217
+ defineStore: (name, options) => defineStore(busName(name), options),
218
+ defineChannel: (name, options) => defineChannel(busName(name), options),
219
+ getSharedStore: (name, scope) => getSharedStore(busName(name), scope),
220
+ useSharedState: (key, initial, options) => useSharedState(key, initial, { ...options, store: busName(options?.store) }),
221
+ useSharedStore: (selector, options) => useSharedStore(selector, { ...options, store: busName(options?.store) }),
222
+ usePeers: (options) => usePeers({ ...options, name: busName(options?.name) }),
223
+ usePresenceMetadata: (metadata, options) => usePresenceMetadata(metadata, { ...options, name: busName(options?.name) }),
224
+ useHydrated: (options) => useHydrated({ ...options, store: busName(options?.store) }),
225
+ useClientId: (options) => useClientId({ name: busName(options?.name) }),
226
+ useLeader: (options) => useLeader({ ...options, name: busName(options?.name) }),
227
+ useIsLeader: (options) => useIsLeader({ ...options, name: busName(options?.name) }),
228
+ useLeaderEffect: (effect, options) => useLeaderEffect(effect, { ...options, name: busName(options?.name) })
229
+ };
230
+ }
231
+
112
232
  // src/use-opened-window.ts
113
- import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState } from "react";
233
+ import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef4, useState as useState2 } from "react";
114
234
  import { WindowClosedError } from "@use-everywhere/core";
115
235
  var IDLE = Object.freeze({
116
236
  status: "idle",
@@ -118,13 +238,13 @@ var IDLE = Object.freeze({
118
238
  error: void 0
119
239
  });
120
240
  function useOpenedWindow(factory) {
121
- const [state, setState] = useState(IDLE);
122
- const current = useRef3(null);
123
- const factoryRef = useRef3(factory);
124
- useEffect3(() => {
241
+ const [state, setState] = useState2(IDLE);
242
+ const current = useRef4(null);
243
+ const factoryRef = useRef4(factory);
244
+ useEffect4(() => {
125
245
  factoryRef.current = factory;
126
246
  });
127
- const open = useCallback3(() => {
247
+ const open = useCallback5(() => {
128
248
  current.current?.close();
129
249
  let opened;
130
250
  try {
@@ -159,10 +279,10 @@ function useOpenedWindow(factory) {
159
279
  }
160
280
  );
161
281
  }, []);
162
- const post = useCallback3((type, payload) => {
282
+ const post = useCallback5((type, payload) => {
163
283
  current.current?.post(type, payload);
164
284
  }, []);
165
- const close = useCallback3(() => current.current?.close(), []);
285
+ const close = useCallback5(() => current.current?.close(), []);
166
286
  return { ...state, open, post, close };
167
287
  }
168
288
 
@@ -170,11 +290,14 @@ function useOpenedWindow(factory) {
170
290
  import {
171
291
  createChannel,
172
292
  createSharedStore,
293
+ createSharedReducer,
173
294
  createPresence,
174
295
  createLeader,
175
296
  webStorageAdapter,
176
297
  localStorageAdapter,
177
298
  sessionStorageAdapter,
299
+ indexedDbAdapter,
300
+ jsonSerializer,
178
301
  openWindow,
179
302
  connectToOpener,
180
303
  CID_PARAM,
@@ -185,6 +308,8 @@ import {
185
308
  enableDebug,
186
309
  getBusNames,
187
310
  getTransportKind,
311
+ getWireSkew,
312
+ WIRE_VERSION,
188
313
  BroadcastChannelTransport,
189
314
  NoopTransport,
190
315
  StorageTransport,
@@ -199,11 +324,14 @@ export {
199
324
  HandshakeTimeoutError,
200
325
  NoopTransport,
201
326
  StorageTransport,
327
+ WIRE_VERSION,
202
328
  WindowClosedError2 as WindowClosedError,
203
329
  connectToOpener,
204
330
  createChannel,
205
331
  createLeader,
332
+ createNamespace,
206
333
  createPresence,
334
+ createSharedReducer,
207
335
  createSharedStore,
208
336
  defaultTransport,
209
337
  defineChannel,
@@ -213,22 +341,32 @@ export {
213
341
  getLeader,
214
342
  getSharedStore,
215
343
  getTransportKind,
344
+ getWireSkew,
345
+ indexedDbAdapter,
216
346
  isBroadcastChannelAvailable,
217
347
  isStorageEventAvailable,
348
+ jsonSerializer,
218
349
  localStorageAdapter,
219
350
  newer,
220
351
  observeBus,
221
352
  openWindow,
222
353
  sessionStorageAdapter,
354
+ shallowEqual,
355
+ useAnswer,
356
+ useAsk,
223
357
  useChannel,
224
358
  useClientId,
359
+ useHydrated,
225
360
  useIsLeader,
226
361
  useLeader,
227
362
  useLeaderEffect,
228
363
  useMessage,
229
364
  useOpenedWindow,
230
365
  usePeers,
366
+ usePresenceMetadata,
231
367
  useSend,
368
+ useSharedReducer,
232
369
  useSharedState,
370
+ useSharedStore,
233
371
  webStorageAdapter
234
372
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-everywhere",
3
- "version": "0.7.0",
3
+ "version": "0.9.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>",
@@ -69,59 +69,71 @@
69
69
  "name": "everything (import *)",
70
70
  "path": "dist/index.js",
71
71
  "import": "*",
72
- "limit": "8.92 kB"
72
+ "limit": "10.89 kB"
73
73
  },
74
74
  {
75
75
  "name": "useSharedState",
76
76
  "path": "dist/index.js",
77
77
  "import": "{ useSharedState }",
78
- "limit": "3.89 kB"
78
+ "limit": "4.24 kB"
79
+ },
80
+ {
81
+ "name": "useSharedStore (selector)",
82
+ "path": "dist/index.js",
83
+ "import": "{ useSharedStore, shallowEqual }",
84
+ "limit": "4.40 kB"
85
+ },
86
+ {
87
+ "name": "useSharedReducer",
88
+ "path": "dist/index.js",
89
+ "import": "{ useSharedReducer }",
90
+ "limit": "4.21 kB"
79
91
  },
80
92
  {
81
93
  "name": "useChannel + useMessage + useSend",
82
94
  "path": "dist/index.js",
83
95
  "import": "{ useChannel, useMessage, useSend }",
84
- "limit": "2.39 kB"
96
+ "limit": "3.07 kB"
85
97
  },
86
98
  {
87
99
  "name": "usePeers + useClientId",
88
100
  "path": "dist/index.js",
89
101
  "import": "{ usePeers, useClientId }",
90
- "limit": "2.57 kB"
102
+ "limit": "2.77 kB"
91
103
  },
92
104
  {
93
105
  "name": "useOpenedWindow + openWindow",
94
106
  "path": "dist/index.js",
95
107
  "import": "{ useOpenedWindow, openWindow }",
96
- "limit": "1.76 kB"
108
+ "limit": "1.47 kB"
97
109
  },
98
110
  {
99
111
  "name": "useLeader + useLeaderEffect",
100
112
  "path": "dist/index.js",
101
113
  "import": "{ useLeader, useLeaderEffect }",
102
- "limit": "3.84 kB"
114
+ "limit": "3.65 kB"
103
115
  },
104
116
  {
105
117
  "name": "defineStore (persisted)",
106
118
  "path": "dist/index.js",
107
119
  "import": "{ defineStore, localStorageAdapter }",
108
- "limit": "4.38 kB"
120
+ "limit": "4.67 kB"
109
121
  },
110
122
  {
111
123
  "name": "Inspector (devtools subpath)",
112
124
  "path": "dist/devtools/index.js",
113
125
  "import": "{ Inspector }",
114
- "limit": "5.81 kB"
126
+ "limit": "6.70 kB"
115
127
  },
116
128
  {
117
129
  "name": "MemoryHub (testing subpath)",
118
130
  "path": "dist/testing.js",
119
131
  "import": "{ MemoryHub }",
120
- "limit": "400 B"
132
+ "limit": "264 B"
121
133
  }
122
134
  ],
123
135
  "dependencies": {
124
- "@use-everywhere/core": "0.7.0"
136
+ "@use-everywhere/core": "0.9.0"
125
137
  },
126
138
  "peerDependencies": {
127
139
  "react": ">=18"