use-everywhere 0.6.0 → 0.8.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.
@@ -5,6 +5,7 @@ import {
5
5
  createChannel,
6
6
  createLeader,
7
7
  createPresence,
8
+ createSharedReducer,
8
9
  createSharedStore,
9
10
  DEFAULT_NAME,
10
11
  NoopTransport
@@ -49,6 +50,12 @@ var unsubscribe = () => noop;
49
50
  var INERT = Object.freeze({
50
51
  clientId: SERVER_CLIENT_ID,
51
52
  // store
53
+ //
54
+ // Resolved, unlike the leader's `waitForLeadership` below. A server render
55
+ // has nothing to restore and is documented to render defaults, so `await
56
+ // store.hydrated` must proceed — a never-settling promise would hang the
57
+ // render rather than describe it honestly.
58
+ hydrated: Promise.resolve(),
52
59
  state: EMPTY,
53
60
  getVersions: () => EMPTY,
54
61
  set: noop,
@@ -57,6 +64,8 @@ var INERT = Object.freeze({
57
64
  // store + leader share getSnapshot; presence has getPeers
58
65
  getSnapshot: () => EMPTY,
59
66
  getPeers: () => NO_PEERS,
67
+ // presence
68
+ setMetadata: noop,
60
69
  // leader
61
70
  resign: noop,
62
71
  setEligible: noop,
@@ -67,7 +76,20 @@ var INERT = Object.freeze({
67
76
  var createServerStore = () => INERT;
68
77
  var createServerPresence = () => INERT;
69
78
  var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
70
- var createServerLeader = () => ({ ...INERT, getSnapshot: () => NO_LEADER });
79
+ var NEVER = () => new Promise(() => {
80
+ });
81
+ var createServerLeader = () => ({
82
+ ...INERT,
83
+ strategy: "heartbeat",
84
+ getSnapshot: () => NO_LEADER,
85
+ waitForLeadership: NEVER
86
+ });
87
+ var createServerReducer = (initial) => ({
88
+ ...INERT,
89
+ getSnapshot: () => initial,
90
+ dispatch: noop,
91
+ pendingCount: () => 0
92
+ });
71
93
  var createServerChannel = (name) => ({ ...INERT, name, post: noop, on: unsubscribe });
72
94
 
73
95
  // src/registry.ts
@@ -88,15 +110,17 @@ function getSharedStore(name = DEFAULT_NAME, scope = "everywhere") {
88
110
  function configSignature(options) {
89
111
  const persist = options?.persist;
90
112
  if (!persist) return "none";
91
- return `persist:${persist.keys?.join(",") ?? "*"}:${persist.debounceMs ?? "default"}`;
113
+ return `persist:${persist.keys?.join(",") ?? "*"}:${persist.debounceMs ?? "default"}:v${persist.version ?? 0}`;
92
114
  }
93
115
  function configureStore(name, scope, options) {
94
116
  const key = `${scope} ${name}`;
95
117
  if (stores.has(key)) {
96
118
  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
- );
119
+ if (process.env.NODE_ENV !== "production") {
120
+ devWarn(
121
+ `[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.`
122
+ );
123
+ }
100
124
  return;
101
125
  }
102
126
  storeConfig.set(key, options);
@@ -110,11 +134,12 @@ function getStore(name, scope = "everywhere") {
110
134
  }
111
135
  return store;
112
136
  }
113
- function getPresence(name) {
114
- let presence = presences.get(name);
137
+ function getPresence(name, includeSelf = false) {
138
+ const key = includeSelf ? `self ${name}` : name;
139
+ let presence = presences.get(key);
115
140
  if (!presence) {
116
- presence = isServer() ? createServerPresence() : createPresence(name);
117
- presences.set(name, presence);
141
+ presence = isServer() ? createServerPresence() : createPresence(name, { includeSelf });
142
+ presences.set(key, presence);
118
143
  }
119
144
  return presence;
120
145
  }
@@ -135,26 +160,53 @@ function warnOnLeaderOptionConflict(name, options) {
135
160
  for (const key of ["heartbeatMs", "leaseMs"]) {
136
161
  const requested = options[key];
137
162
  if (requested !== void 0 && requested !== first?.[key]) {
163
+ if (process.env.NODE_ENV !== "production") {
164
+ devWarn(
165
+ `[use-everywhere] leader "${name}": ${key} ignored \u2014 the first useLeader/getLeader call fixes the election timings for this tab.`
166
+ );
167
+ }
168
+ }
169
+ }
170
+ }
171
+ var channelConfig = /* @__PURE__ */ new Map();
172
+ function configureChannel(name, options) {
173
+ if (channels.has(name)) {
174
+ const before = Object.keys(channelConfig.get(name)?.schema ?? {}).sort();
175
+ const after = Object.keys(options.schema ?? {}).sort();
176
+ if (before.join() === after.join()) return;
177
+ if (process.env.NODE_ENV !== "production") {
138
178
  devWarn(
139
- `[use-everywhere] leader "${name}": ${key} ignored \u2014 the first useLeader/getLeader call fixes the election timings for this tab.`
179
+ `[use-everywhere] defineChannel('${name}') ran after that channel was already created, with different options. The live channel keeps the configuration it was built with. Move defineChannel to module scope, before any component sends or receives on it.`
140
180
  );
141
181
  }
182
+ return;
142
183
  }
184
+ channelConfig.set(name, options);
185
+ }
186
+ var reducers = /* @__PURE__ */ new Map();
187
+ function getReducer(name, key, reducer, initial) {
188
+ const id = `${name} ${key}`;
189
+ let existing = reducers.get(id);
190
+ if (!existing) {
191
+ existing = isServer() ? createServerReducer(initial) : createSharedReducer(name, reducer, initial, { key, leader: getLeader(name) });
192
+ reducers.set(id, existing);
193
+ }
194
+ return existing;
143
195
  }
144
196
  function getChannel(name) {
145
197
  let channel = channels.get(name);
146
198
  if (!channel) {
147
- channel = isServer() ? createServerChannel(name) : createChannel(name);
199
+ channel = isServer() ? createServerChannel(name) : createChannel(name, channelConfig.get(name));
148
200
  channels.set(name, channel);
149
201
  }
150
202
  return channel;
151
203
  }
152
204
 
153
205
  // src/use-peers.ts
154
- import { useCallback, useSyncExternalStore } from "react";
206
+ import { useCallback, useEffect, useSyncExternalStore } from "react";
155
207
  var NO_PEERS2 = Object.freeze([]);
156
208
  function usePeers(options) {
157
- const presence = getPresence(options?.name ?? DEFAULT_NAME);
209
+ const presence = getPresence(options?.name ?? DEFAULT_NAME, options?.includeSelf ?? false);
158
210
  return useSyncExternalStore(
159
211
  useCallback((onChange) => presence.subscribe(onChange), [presence]),
160
212
  () => presence.getPeers(),
@@ -173,6 +225,12 @@ function useClientId(options) {
173
225
  () => SERVER_CLIENT_ID
174
226
  );
175
227
  }
228
+ function usePresenceMetadata(metadata, options) {
229
+ const presence = getPresence(options?.name ?? DEFAULT_NAME, options?.includeSelf ?? false);
230
+ useEffect(() => {
231
+ presence.setMetadata(metadata);
232
+ }, [presence, metadata]);
233
+ }
176
234
 
177
235
  export {
178
236
  warnOnInitialMismatch,
@@ -181,7 +239,10 @@ export {
181
239
  configureStore,
182
240
  getStore,
183
241
  getLeader,
242
+ configureChannel,
243
+ getReducer,
184
244
  getChannel,
185
245
  usePeers,
186
- useClientId
246
+ useClientId,
247
+ usePresenceMetadata
187
248
  };
@@ -42,6 +42,12 @@ var unsubscribe = () => noop;
42
42
  var INERT = Object.freeze({
43
43
  clientId: SERVER_CLIENT_ID,
44
44
  // store
45
+ //
46
+ // Resolved, unlike the leader's `waitForLeadership` below. A server render
47
+ // has nothing to restore and is documented to render defaults, so `await
48
+ // store.hydrated` must proceed — a never-settling promise would hang the
49
+ // render rather than describe it honestly.
50
+ hydrated: Promise.resolve(),
45
51
  state: EMPTY,
46
52
  getVersions: () => EMPTY,
47
53
  set: noop,
@@ -50,6 +56,8 @@ var INERT = Object.freeze({
50
56
  // store + leader share getSnapshot; presence has getPeers
51
57
  getSnapshot: () => EMPTY,
52
58
  getPeers: () => NO_PEERS,
59
+ // presence
60
+ setMetadata: noop,
53
61
  // leader
54
62
  resign: noop,
55
63
  setEligible: noop,
@@ -83,11 +91,12 @@ function getStore(name, scope = "everywhere") {
83
91
  }
84
92
  return store;
85
93
  }
86
- function getPresence(name) {
87
- let presence = presences.get(name);
94
+ function getPresence(name, includeSelf = false) {
95
+ const key = includeSelf ? `self ${name}` : name;
96
+ let presence = presences.get(key);
88
97
  if (!presence) {
89
- presence = isServer() ? createServerPresence() : (0, import_core.createPresence)(name);
90
- presences.set(name, presence);
98
+ presence = isServer() ? createServerPresence() : (0, import_core.createPresence)(name, { includeSelf });
99
+ presences.set(key, presence);
91
100
  }
92
101
  return presence;
93
102
  }
@@ -96,7 +105,7 @@ function getPresence(name) {
96
105
  var import_react = require("react");
97
106
  var NO_PEERS2 = Object.freeze([]);
98
107
  function usePeers(options) {
99
- const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME);
108
+ const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME, options?.includeSelf ?? false);
100
109
  return (0, import_react.useSyncExternalStore)(
101
110
  (0, import_react.useCallback)((onChange) => presence.subscribe(onChange), [presence]),
102
111
  () => presence.getPeers(),
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getSharedStore,
4
4
  usePeers
5
- } from "../chunk-YOXUZYKA.js";
5
+ } from "../chunk-75TICQ7E.js";
6
6
 
7
7
  // src/devtools/inspector.tsx
8
8
  import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";