use-everywhere 1.0.0 → 1.0.2

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,33 +1,15 @@
1
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/shared-worker.ts
22
- var shared_worker_exports = {};
23
- __export(shared_worker_exports, {
24
- relay: () => import_shared_worker.relay,
25
- startRelay: () => import_shared_worker.startRelay
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ let _use_everywhere_core_shared_worker = require("@use-everywhere/core/shared-worker");
4
+ Object.defineProperty(exports, "relay", {
5
+ enumerable: true,
6
+ get: function() {
7
+ return _use_everywhere_core_shared_worker.relay;
8
+ }
26
9
  });
27
- module.exports = __toCommonJS(shared_worker_exports);
28
- var import_shared_worker = require("@use-everywhere/core/shared-worker");
29
- // Annotate the CommonJS export names for ESM import in node:
30
- 0 && (module.exports = {
31
- relay,
32
- startRelay
10
+ Object.defineProperty(exports, "startRelay", {
11
+ enumerable: true,
12
+ get: function() {
13
+ return _use_everywhere_core_shared_worker.startRelay;
14
+ }
33
15
  });
@@ -1,2 +1,3 @@
1
- export { Relay, RelayPort, RelayScope, relay, startRelay } from '@use-everywhere/core/shared-worker';
2
- import '@use-everywhere/core/testing';
1
+
2
+ import { Relay, RelayPort, RelayScope, relay, startRelay } from "@use-everywhere/core/shared-worker";
3
+ export { type Relay, type RelayPort, type RelayScope, relay, startRelay };
@@ -1,2 +1,3 @@
1
- export { Relay, RelayPort, RelayScope, relay, startRelay } from '@use-everywhere/core/shared-worker';
2
- import '@use-everywhere/core/testing';
1
+
2
+ import { Relay, RelayPort, RelayScope, relay, startRelay } from "@use-everywhere/core/shared-worker";
3
+ export { type Relay, type RelayPort, type RelayScope, relay, startRelay };
@@ -1,8 +1,3 @@
1
1
  'use client';
2
-
3
- // src/shared-worker.ts
4
- import { startRelay, relay } from "@use-everywhere/core/shared-worker";
5
- export {
6
- relay,
7
- startRelay
8
- };
2
+ import { relay, startRelay } from "@use-everywhere/core/shared-worker";
3
+ export { relay, startRelay };
package/dist/testing.cjs CHANGED
@@ -1,33 +1,15 @@
1
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
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ let _use_everywhere_core_testing = require("@use-everywhere/core/testing");
4
+ Object.defineProperty(exports, "MemoryHub", {
5
+ enumerable: true,
6
+ get: function() {
7
+ return _use_everywhere_core_testing.MemoryHub;
8
+ }
26
9
  });
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
10
+ Object.defineProperty(exports, "MemoryTransport", {
11
+ enumerable: true,
12
+ get: function() {
13
+ return _use_everywhere_core_testing.MemoryTransport;
14
+ }
33
15
  });
@@ -1 +1,3 @@
1
- export { MemoryHub, MemoryTransport } from '@use-everywhere/core/testing';
1
+
2
+ import { MemoryHub, MemoryTransport } from "@use-everywhere/core/testing";
3
+ export { MemoryHub, MemoryTransport };
package/dist/testing.d.ts CHANGED
@@ -1 +1,3 @@
1
- export { MemoryHub, MemoryTransport } from '@use-everywhere/core/testing';
1
+
2
+ import { MemoryHub, MemoryTransport } from "@use-everywhere/core/testing";
3
+ export { MemoryHub, MemoryTransport };
package/dist/testing.js CHANGED
@@ -1,8 +1,3 @@
1
1
  'use client';
2
-
3
- // src/testing.ts
4
2
  import { MemoryHub, MemoryTransport } from "@use-everywhere/core/testing";
5
- export {
6
- MemoryHub,
7
- MemoryTransport
8
- };
3
+ export { MemoryHub, MemoryTransport };
@@ -0,0 +1,315 @@
1
+ 'use client';
2
+ import { useCallback, useEffect, useSyncExternalStore } from "react";
3
+ import { DEFAULT_NAME as DEFAULT_NAME$1, NoopTransport, createChannel, createLeader, createPresence, createSharedReducer, createSharedStore } from "@use-everywhere/core";
4
+ //#region src/dev.ts
5
+ let inDev = false;
6
+ try {
7
+ inDev = process.env.NODE_ENV !== "production";
8
+ } catch {}
9
+ const warned = /* @__PURE__ */ new Set();
10
+ const DOCS = "https://rxova.org/packages/use-everywhere/errors";
11
+ /**
12
+ * Stamp a diagnostic with its code and the page that explains it. Core's twin
13
+ * of this, for the same reason the twin of `devWarn` exists — see above.
14
+ *
15
+ * Codes are permanent, and a retired one is never reused: an old build in
16
+ * somebody's browser is still emitting it. Core owns UE1xxx, this package
17
+ * UE2xxx.
18
+ */
19
+ function diagnostic(code, message) {
20
+ return `[use-everywhere] ${code}: ${message}\n → ${DOCS}/#${code.toLowerCase()}`;
21
+ }
22
+ /** Warn once per distinct message, development only. */
23
+ function devWarn(code, message) {
24
+ if (!inDev) return;
25
+ const line = diagnostic(code, message);
26
+ if (warned.has(line)) return;
27
+ warned.add(line);
28
+ console.warn(line);
29
+ }
30
+ /** The initial each key was first registered with. Populated only in development — dynamic keys would otherwise grow it without bound. */
31
+ const seenInitials = /* @__PURE__ */ new Map();
32
+ /**
33
+ * Catch two callers registering one key with different defaults. The first
34
+ * registration wins and the second is silently discarded, which is the kind of
35
+ * disagreement that surfaces much later as "why is this value not what I set".
36
+ */
37
+ function warnOnInitialMismatch(storeName, key, initial) {
38
+ if (!inDev) return;
39
+ const id = `${storeName} ${key}`;
40
+ if (!seenInitials.has(id)) {
41
+ seenInitials.set(id, initial);
42
+ return;
43
+ }
44
+ const first = seenInitials.get(id);
45
+ const comparable = (v) => v === null || typeof v !== "object";
46
+ if (comparable(first) && comparable(initial) && !Object.is(first, initial)) devWarn("UE2001", `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 — createStoreHooks, or a shared constant.`);
47
+ }
48
+ const EMPTY = Object.freeze({});
49
+ const NO_PEERS$1 = Object.freeze([]);
50
+ const noop = () => {};
51
+ const unsubscribe = () => noop;
52
+ /**
53
+ * One frozen object serving as store, presence and leader. Their surfaces do
54
+ * not collide, and a server render never distinguishes two inert engines by
55
+ * identity — so this is a single shared constant rather than three allocations
56
+ * in every bundle that imports a hook.
57
+ */
58
+ const INERT = Object.freeze({
59
+ clientId: "",
60
+ hydrated: Promise.resolve(),
61
+ state: EMPTY,
62
+ getVersions: () => EMPTY,
63
+ set: noop,
64
+ subscribeKey: unsubscribe,
65
+ registerKey: noop,
66
+ getSnapshot: () => EMPTY,
67
+ getPeers: () => NO_PEERS$1,
68
+ setMetadata: noop,
69
+ resign: noop,
70
+ setEligible: noop,
71
+ subscribe: unsubscribe,
72
+ close: noop
73
+ });
74
+ const createServerStore = () => INERT;
75
+ const createServerPresence = () => INERT;
76
+ /**
77
+ * Leadership needs its own snapshot shape, so it wraps the shared constant.
78
+ * `waitForLeadership` never settles on a server: there is no election to win,
79
+ * and resolving would run leader-only work during a render that is about to be
80
+ * thrown away. A pending promise is the honest answer.
81
+ */
82
+ const NO_LEADER = Object.freeze({
83
+ leaderId: null,
84
+ isLeader: false
85
+ });
86
+ const NEVER = () => new Promise(() => {});
87
+ const createServerLeader = () => ({
88
+ ...INERT,
89
+ strategy: "heartbeat",
90
+ getSnapshot: () => NO_LEADER,
91
+ waitForLeadership: NEVER
92
+ });
93
+ /**
94
+ * A reducer carries its initial value, so it allocates too. `dispatch` is a
95
+ * no-op: a server render has no peers to order anything with, and an action
96
+ * applied here would show a value the browser is about to disagree with.
97
+ */
98
+ const createServerReducer = (initial) => ({
99
+ ...INERT,
100
+ getSnapshot: () => initial,
101
+ dispatch: noop,
102
+ pendingCount: () => 0
103
+ });
104
+ /** Channels carry their name, so this is the one double that allocates. */
105
+ const createServerChannel = (name) => ({
106
+ ...INERT,
107
+ name,
108
+ post: noop,
109
+ on: unsubscribe
110
+ });
111
+ //#endregion
112
+ //#region src/registry.ts
113
+ const stores = /* @__PURE__ */ new Map();
114
+ const presences = /* @__PURE__ */ new Map();
115
+ const channels = /* @__PURE__ */ new Map();
116
+ const leaders = /* @__PURE__ */ new Map();
117
+ const storeConfig = /* @__PURE__ */ new Map();
118
+ /**
119
+ * Checked per call, not captured once: a module evaluated during SSR and a
120
+ * module evaluated in the browser are different module instances, so there is
121
+ * no cache to invalidate — but reading it lazily keeps the bundler from
122
+ * folding the branch away in a build that serves both.
123
+ */
124
+ const isServer = () => typeof window === "undefined";
125
+ /** Per-scope store creation: what leaves this tab and what is let back in. */
126
+ const scopeOptions = {
127
+ everywhere: {},
128
+ tabs: { accept: (meta) => meta.kind !== "worker" },
129
+ tab: { transport: () => new NoopTransport() }
130
+ };
131
+ /** Imperative access to the store behind useSharedState (patch logs, non-React code). */
132
+ function getSharedStore(name = DEFAULT_NAME$1, scope = "everywhere") {
133
+ return getStore(name, scope);
134
+ }
135
+ /**
136
+ * Options to build a store with when it is first needed. Registered by
137
+ * createStoreHooks at module scope and consumed by getStore on creation — which is
138
+ * what lets persistence be declared in one place without constructing anything
139
+ * on import.
140
+ */
141
+ /**
142
+ * A configuration's shape, ignoring the adapter's identity. Hot Module
143
+ * Replacement re-evaluates the defining module and builds a *new* adapter
144
+ * object each time, so identity comparison would call every hot edit a
145
+ * conflict; what actually matters is whether the store would be built
146
+ * differently.
147
+ */
148
+ function configSignature(options) {
149
+ const persist = options?.persist;
150
+ if (!persist) return "none";
151
+ return `persist:${persist.keys?.join(",") ?? "*"}:${persist.debounceMs ?? "default"}:v${persist.version ?? 0}`;
152
+ }
153
+ function configureStore(name, scope, options) {
154
+ const key = `${scope} ${name}`;
155
+ if (stores.has(key)) {
156
+ if (configSignature(storeConfig.get(key)) === configSignature(options)) return;
157
+ if (process.env.NODE_ENV !== "production") devWarn("UE2002", `createStoreHooks('${name}') ran after that store was already created, with different options. The live store keeps the configuration it was built with. Move createStoreHooks to module scope, before any component reads the store.`);
158
+ return;
159
+ }
160
+ storeConfig.set(key, options);
161
+ }
162
+ function getStore(name, scope = "everywhere") {
163
+ const key = `${scope} ${name}`;
164
+ let store = stores.get(key);
165
+ if (!store) {
166
+ store = isServer() ? createServerStore() : createSharedStore(name, {}, {
167
+ ...scopeOptions[scope],
168
+ ...storeConfig.get(key)
169
+ });
170
+ stores.set(key, store);
171
+ }
172
+ return store;
173
+ }
174
+ /**
175
+ * One Presence per name per tab.
176
+ *
177
+ * `includeSelf` is part of the key rather than the options, because it changes
178
+ * what the roster *is*: two components on one name disagreeing about it would
179
+ * otherwise silently get whichever answer was built first.
180
+ */
181
+ function getPresence(name, includeSelf = false) {
182
+ const key = includeSelf ? `self ${name}` : name;
183
+ let presence = presences.get(key);
184
+ if (!presence) {
185
+ presence = isServer() ? createServerPresence() : createPresence(name, { includeSelf });
186
+ presences.set(key, presence);
187
+ }
188
+ return presence;
189
+ }
190
+ /**
191
+ * One Leader per name per tab. Eligibility is deliberately *not* part of the
192
+ * key: two Leaders on one name would share a bus and a clientId, and since a
193
+ * post never loops back locally, neither would ever see the other's claims.
194
+ * Timing options are first-wins, like every other engine here.
195
+ */
196
+ function getLeader(name, options) {
197
+ let leader = leaders.get(name);
198
+ if (!leader) {
199
+ leader = isServer() ? createServerLeader() : createLeader(name, options);
200
+ leaders.set(name, leader);
201
+ if (options) leaderOptions.set(name, options);
202
+ } else if (options) warnOnLeaderOptionConflict(name, options);
203
+ return leader;
204
+ }
205
+ /** What the first caller elected with, so a later caller asking for different timings can be told it was ignored. */
206
+ const leaderOptions = /* @__PURE__ */ new Map();
207
+ function warnOnLeaderOptionConflict(name, options) {
208
+ const first = leaderOptions.get(name);
209
+ for (const key of ["heartbeatMs", "leaseMs"]) {
210
+ const requested = options[key];
211
+ if (requested !== void 0 && requested !== first?.[key]) {
212
+ if (process.env.NODE_ENV !== "production") devWarn("UE2003", `leader "${name}": ${key} ignored — the first useLeader/getLeader call fixes the election timings for this tab.`);
213
+ }
214
+ }
215
+ }
216
+ /**
217
+ * Options to build a channel with when it is first needed. Registered by
218
+ * defineChannel at module scope and consumed by getChannel on creation — the
219
+ * same deferral createStoreHooks uses, so declaring a schema constructs nothing on
220
+ * import.
221
+ */
222
+ const channelConfig = /* @__PURE__ */ new Map();
223
+ function configureChannel(name, options) {
224
+ if (channels.has(name)) {
225
+ const before = Object.keys(channelConfig.get(name)?.schema ?? {}).sort();
226
+ const after = Object.keys(options.schema ?? {}).sort();
227
+ if (before.join() === after.join()) return;
228
+ if (process.env.NODE_ENV !== "production") devWarn("UE2004", `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.`);
229
+ return;
230
+ }
231
+ channelConfig.set(name, options);
232
+ }
233
+ const reducers = /* @__PURE__ */ new Map();
234
+ /**
235
+ * One reducer per name+key per tab, like every other engine here.
236
+ *
237
+ * The reducer is handed this tab's existing `Leader` rather than electing its
238
+ * own: the leader is the sequencer, and a page that already has a seat for this
239
+ * bus must not run a second election to get another one.
240
+ *
241
+ * The first caller's reducer function wins. A hook re-renders with a new
242
+ * function identity every time, and swapping the fold under a history that has
243
+ * already been applied would give this tab a different answer from its peers —
244
+ * which is the one thing an ordered reducer exists to prevent.
245
+ */
246
+ function getReducer(name, key, reducer, initial) {
247
+ const id = `${name} ${key}`;
248
+ let existing = reducers.get(id);
249
+ if (!existing) {
250
+ existing = isServer() ? createServerReducer(initial) : createSharedReducer(name, reducer, initial, {
251
+ key,
252
+ leader: getLeader(name)
253
+ });
254
+ reducers.set(id, existing);
255
+ }
256
+ return existing;
257
+ }
258
+ function getChannel(name) {
259
+ let channel = channels.get(name);
260
+ if (!channel) {
261
+ channel = isServer() ? createServerChannel(name) : createChannel(name, channelConfig.get(name));
262
+ channels.set(name, channel);
263
+ }
264
+ return channel;
265
+ }
266
+ //#endregion
267
+ //#region src/use-peers.ts
268
+ const NO_PEERS = Object.freeze([]);
269
+ /**
270
+ * The tabs/windows/workers currently alive on this origin.
271
+ *
272
+ * Each peer carries whatever it published about itself as `metadata` — see
273
+ * {@link usePresenceMetadata} for publishing this client's.
274
+ */
275
+ function usePeers(options) {
276
+ const presence = getPresence(options?.name ?? DEFAULT_NAME$1, options?.includeSelf ?? false);
277
+ return useSyncExternalStore(useCallback((onChange) => presence.subscribe(onChange), [presence]), () => presence.getPeers(), () => NO_PEERS);
278
+ }
279
+ /**
280
+ * This client's own id on the presence bus (matches patch origin ids).
281
+ *
282
+ * Read through useSyncExternalStore rather than returned straight from render,
283
+ * because the id is minted per environment: a server would render one value and
284
+ * the browser a different one, and any component that puts it in the DOM would
285
+ * mismatch on hydration. The server snapshot is a constant empty string, which
286
+ * React also uses for the client's hydrating render, so markup matches; the
287
+ * real id arrives in the commit straight after. Treat `''` as "not known yet".
288
+ */
289
+ function useClientId(options) {
290
+ const presence = getPresence(options?.name ?? DEFAULT_NAME$1);
291
+ return useSyncExternalStore(useCallback(() => () => {}, [presence]), () => presence.clientId, () => "");
292
+ }
293
+ /**
294
+ * Publish what this client wants peers to know about it — a display name, a tab
295
+ * title, a cursor.
296
+ *
297
+ * Safe to call with a fresh object every render: the value is compared by
298
+ * contents, so an unchanged one announces nothing and re-renders nobody.
299
+ *
300
+ * ```tsx
301
+ * usePresenceMetadata({ name: user.name, editing: currentDocId });
302
+ * ```
303
+ *
304
+ * Published in an effect rather than during render, because announcing is a
305
+ * side effect on every other tab — and a render that React throws away must not
306
+ * be one other tabs already saw.
307
+ */
308
+ function usePresenceMetadata(metadata, options) {
309
+ const presence = getPresence(options?.name ?? DEFAULT_NAME$1, options?.includeSelf ?? false);
310
+ useEffect(() => {
311
+ presence.setMetadata(metadata);
312
+ }, [presence, metadata]);
313
+ }
314
+ //#endregion
315
+ export { configureChannel as a, getLeader as c, getStore as d, warnOnInitialMismatch as f, DEFAULT_NAME$1 as i, getReducer as l, usePeers as n, configureStore as o, usePresenceMetadata as r, getChannel as s, useClientId as t, getSharedStore as u };