use-everywhere 0.4.0 → 0.5.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/chunk-YOXUZYKA.js +187 -0
- package/dist/devtools/index.cjs +36 -4
- package/dist/devtools/index.js +1 -1
- package/dist/index.cjs +128 -28
- package/dist/index.d.cts +59 -8
- package/dist/index.d.ts +59 -8
- package/dist/index.js +27 -20
- package/package.json +14 -10
- package/dist/chunk-PNFJW7QK.js +0 -92
|
@@ -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
|
+
};
|
package/dist/devtools/index.cjs
CHANGED
|
@@ -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
|
|
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
|
-
() =>
|
|
103
|
+
() => NO_PEERS2
|
|
72
104
|
);
|
|
73
105
|
}
|
|
74
106
|
|
package/dist/devtools/index.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -43,13 +43,76 @@ module.exports = __toCommonJS(src_exports);
|
|
|
43
43
|
// src/use-shared-state.ts
|
|
44
44
|
var import_react = require("react");
|
|
45
45
|
|
|
46
|
+
// src/dev.ts
|
|
47
|
+
var inDev = false;
|
|
48
|
+
try {
|
|
49
|
+
inDev = process.env.NODE_ENV !== "production";
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
var warned = /* @__PURE__ */ new Set();
|
|
53
|
+
function devWarn(message) {
|
|
54
|
+
if (!inDev || warned.has(message)) return;
|
|
55
|
+
warned.add(message);
|
|
56
|
+
console.warn(message);
|
|
57
|
+
}
|
|
58
|
+
var seenInitials = /* @__PURE__ */ new Map();
|
|
59
|
+
function warnOnInitialMismatch(storeName, key, initial) {
|
|
60
|
+
if (!inDev) return;
|
|
61
|
+
const id = `${storeName} ${key}`;
|
|
62
|
+
if (!seenInitials.has(id)) {
|
|
63
|
+
seenInitials.set(id, initial);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const first = seenInitials.get(id);
|
|
67
|
+
const comparable = (v) => v === null || typeof v !== "object";
|
|
68
|
+
if (comparable(first) && comparable(initial) && !Object.is(first, initial)) {
|
|
69
|
+
devWarn(
|
|
70
|
+
`[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.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
46
75
|
// src/registry.ts
|
|
47
76
|
var import_core = require("@use-everywhere/core");
|
|
77
|
+
|
|
78
|
+
// src/server-stubs.ts
|
|
79
|
+
var SERVER_CLIENT_ID = "";
|
|
80
|
+
var EMPTY = Object.freeze({});
|
|
81
|
+
var NO_PEERS = Object.freeze([]);
|
|
82
|
+
var noop = () => {
|
|
83
|
+
};
|
|
84
|
+
var unsubscribe = () => noop;
|
|
85
|
+
var INERT = Object.freeze({
|
|
86
|
+
clientId: SERVER_CLIENT_ID,
|
|
87
|
+
// store
|
|
88
|
+
state: EMPTY,
|
|
89
|
+
getVersions: () => EMPTY,
|
|
90
|
+
set: noop,
|
|
91
|
+
subscribeKey: unsubscribe,
|
|
92
|
+
registerKey: noop,
|
|
93
|
+
// store + leader share getSnapshot; presence has getPeers
|
|
94
|
+
getSnapshot: () => EMPTY,
|
|
95
|
+
getPeers: () => NO_PEERS,
|
|
96
|
+
// leader
|
|
97
|
+
resign: noop,
|
|
98
|
+
setEligible: noop,
|
|
99
|
+
// common
|
|
100
|
+
subscribe: unsubscribe,
|
|
101
|
+
close: noop
|
|
102
|
+
});
|
|
103
|
+
var createServerStore = () => INERT;
|
|
104
|
+
var createServerPresence = () => INERT;
|
|
105
|
+
var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
|
|
106
|
+
var createServerLeader = () => ({ ...INERT, getSnapshot: () => NO_LEADER });
|
|
107
|
+
var createServerChannel = (name) => ({ ...INERT, name, post: noop, on: unsubscribe });
|
|
108
|
+
|
|
109
|
+
// src/registry.ts
|
|
48
110
|
var stores = /* @__PURE__ */ new Map();
|
|
49
111
|
var presences = /* @__PURE__ */ new Map();
|
|
50
112
|
var channels = /* @__PURE__ */ new Map();
|
|
51
113
|
var leaders = /* @__PURE__ */ new Map();
|
|
52
114
|
var storeConfig = /* @__PURE__ */ new Map();
|
|
115
|
+
var isServer = () => typeof window === "undefined";
|
|
53
116
|
var scopeOptions = {
|
|
54
117
|
everywhere: {},
|
|
55
118
|
tabs: { accept: (meta) => meta.kind !== "worker" },
|
|
@@ -58,12 +121,19 @@ var scopeOptions = {
|
|
|
58
121
|
function getSharedStore(name = import_core.DEFAULT_NAME, scope = "everywhere") {
|
|
59
122
|
return getStore(name, scope);
|
|
60
123
|
}
|
|
124
|
+
function configSignature(options) {
|
|
125
|
+
const persist = options?.persist;
|
|
126
|
+
if (!persist) return "none";
|
|
127
|
+
return `persist:${persist.keys?.join(",") ?? "*"}:${persist.debounceMs ?? "default"}`;
|
|
128
|
+
}
|
|
61
129
|
function configureStore(name, scope, options) {
|
|
62
130
|
const key = `${scope} ${name}`;
|
|
63
131
|
if (stores.has(key)) {
|
|
64
|
-
|
|
65
|
-
|
|
132
|
+
if (configSignature(storeConfig.get(key)) === configSignature(options)) return;
|
|
133
|
+
devWarn(
|
|
134
|
+
`[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
135
|
);
|
|
136
|
+
return;
|
|
67
137
|
}
|
|
68
138
|
storeConfig.set(key, options);
|
|
69
139
|
}
|
|
@@ -71,7 +141,7 @@ function getStore(name, scope = "everywhere") {
|
|
|
71
141
|
const key = `${scope} ${name}`;
|
|
72
142
|
let store = stores.get(key);
|
|
73
143
|
if (!store) {
|
|
74
|
-
store = (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
|
|
144
|
+
store = isServer() ? createServerStore() : (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
|
|
75
145
|
stores.set(key, store);
|
|
76
146
|
}
|
|
77
147
|
return store;
|
|
@@ -79,7 +149,7 @@ function getStore(name, scope = "everywhere") {
|
|
|
79
149
|
function getPresence(name) {
|
|
80
150
|
let presence = presences.get(name);
|
|
81
151
|
if (!presence) {
|
|
82
|
-
presence = (0, import_core.createPresence)(name);
|
|
152
|
+
presence = isServer() ? createServerPresence() : (0, import_core.createPresence)(name);
|
|
83
153
|
presences.set(name, presence);
|
|
84
154
|
}
|
|
85
155
|
return presence;
|
|
@@ -87,15 +157,30 @@ function getPresence(name) {
|
|
|
87
157
|
function getLeader(name, options) {
|
|
88
158
|
let leader = leaders.get(name);
|
|
89
159
|
if (!leader) {
|
|
90
|
-
leader = (0, import_core.createLeader)(name, options);
|
|
160
|
+
leader = isServer() ? createServerLeader() : (0, import_core.createLeader)(name, options);
|
|
91
161
|
leaders.set(name, leader);
|
|
162
|
+
if (options) leaderOptions.set(name, options);
|
|
163
|
+
} else if (options) {
|
|
164
|
+
warnOnLeaderOptionConflict(name, options);
|
|
92
165
|
}
|
|
93
166
|
return leader;
|
|
94
167
|
}
|
|
168
|
+
var leaderOptions = /* @__PURE__ */ new Map();
|
|
169
|
+
function warnOnLeaderOptionConflict(name, options) {
|
|
170
|
+
const first = leaderOptions.get(name);
|
|
171
|
+
for (const key of ["heartbeatMs", "leaseMs"]) {
|
|
172
|
+
const requested = options[key];
|
|
173
|
+
if (requested !== void 0 && requested !== first?.[key]) {
|
|
174
|
+
devWarn(
|
|
175
|
+
`[use-everywhere] leader "${name}": ${key} ignored \u2014 the first useLeader/getLeader call fixes the election timings for this tab.`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
95
180
|
function getChannel(name) {
|
|
96
181
|
let channel = channels.get(name);
|
|
97
182
|
if (!channel) {
|
|
98
|
-
channel = (0, import_core.createChannel)(name);
|
|
183
|
+
channel = isServer() ? createServerChannel(name) : (0, import_core.createChannel)(name);
|
|
99
184
|
channels.set(name, channel);
|
|
100
185
|
}
|
|
101
186
|
return channel;
|
|
@@ -103,8 +188,10 @@ function getChannel(name) {
|
|
|
103
188
|
|
|
104
189
|
// src/use-shared-state.ts
|
|
105
190
|
function useSharedState(key, initial, options) {
|
|
106
|
-
const
|
|
191
|
+
const storeName = options?.store ?? import_core.DEFAULT_NAME;
|
|
192
|
+
const store = getStore(storeName, options?.scope ?? "everywhere");
|
|
107
193
|
store.registerKey(key, initial);
|
|
194
|
+
warnOnInitialMismatch(storeName, key, initial);
|
|
108
195
|
const value = (0, import_react.useSyncExternalStore)(
|
|
109
196
|
(0, import_react.useCallback)((onChange) => store.subscribeKey(key, onChange), [store, key]),
|
|
110
197
|
() => store.getSnapshot()[key],
|
|
@@ -167,22 +254,31 @@ function defineStore(name, options = {}) {
|
|
|
167
254
|
|
|
168
255
|
// src/use-peers.ts
|
|
169
256
|
var import_react3 = require("react");
|
|
170
|
-
var
|
|
257
|
+
var NO_PEERS2 = Object.freeze([]);
|
|
171
258
|
function usePeers(options) {
|
|
172
259
|
const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME);
|
|
173
260
|
return (0, import_react3.useSyncExternalStore)(
|
|
174
261
|
(0, import_react3.useCallback)((onChange) => presence.subscribe(onChange), [presence]),
|
|
175
262
|
() => presence.getPeers(),
|
|
176
|
-
() =>
|
|
263
|
+
() => NO_PEERS2
|
|
177
264
|
);
|
|
178
265
|
}
|
|
179
266
|
function useClientId(options) {
|
|
180
|
-
|
|
267
|
+
const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME);
|
|
268
|
+
return (0, import_react3.useSyncExternalStore)(
|
|
269
|
+
// The id never changes for the life of a presence engine, so there is
|
|
270
|
+
// nothing to subscribe to — but uSES still needs the post-hydration read,
|
|
271
|
+
// which it performs when it attaches this subscription.
|
|
272
|
+
(0, import_react3.useCallback)(() => () => {
|
|
273
|
+
}, [presence]),
|
|
274
|
+
() => presence.clientId,
|
|
275
|
+
() => SERVER_CLIENT_ID
|
|
276
|
+
);
|
|
181
277
|
}
|
|
182
278
|
|
|
183
279
|
// src/use-leader.ts
|
|
184
280
|
var import_react4 = require("react");
|
|
185
|
-
var
|
|
281
|
+
var NO_LEADER2 = Object.freeze({ leaderId: null, isLeader: false });
|
|
186
282
|
function useLeader(options) {
|
|
187
283
|
const leader = getLeader(options?.name ?? import_core.DEFAULT_NAME, options);
|
|
188
284
|
const eligible = options?.eligible;
|
|
@@ -193,7 +289,7 @@ function useLeader(options) {
|
|
|
193
289
|
return (0, import_react4.useSyncExternalStore)(
|
|
194
290
|
(0, import_react4.useCallback)((onChange) => leader.subscribe(onChange), [leader]),
|
|
195
291
|
() => leader.getSnapshot(),
|
|
196
|
-
() =>
|
|
292
|
+
() => NO_LEADER2
|
|
197
293
|
);
|
|
198
294
|
}
|
|
199
295
|
function useIsLeader(options) {
|
|
@@ -214,31 +310,36 @@ function useLeaderEffect(effect, options) {
|
|
|
214
310
|
// src/use-opened-window.ts
|
|
215
311
|
var import_react5 = require("react");
|
|
216
312
|
var import_core2 = require("@use-everywhere/core");
|
|
313
|
+
var IDLE = Object.freeze({
|
|
314
|
+
status: "idle",
|
|
315
|
+
result: void 0,
|
|
316
|
+
error: void 0
|
|
317
|
+
});
|
|
217
318
|
function useOpenedWindow(factory) {
|
|
218
|
-
const [
|
|
219
|
-
const [result, setResult] = (0, import_react5.useState)(void 0);
|
|
220
|
-
const [error, setError] = (0, import_react5.useState)(void 0);
|
|
319
|
+
const [state, setState] = (0, import_react5.useState)(IDLE);
|
|
221
320
|
const current = (0, import_react5.useRef)(null);
|
|
222
321
|
const factoryRef = (0, import_react5.useRef)(factory);
|
|
223
|
-
|
|
322
|
+
(0, import_react5.useEffect)(() => {
|
|
323
|
+
factoryRef.current = factory;
|
|
324
|
+
});
|
|
224
325
|
const open = (0, import_react5.useCallback)(() => {
|
|
225
326
|
current.current?.close();
|
|
226
327
|
let opened;
|
|
227
328
|
try {
|
|
228
329
|
opened = factoryRef.current();
|
|
229
330
|
} catch (err) {
|
|
230
|
-
|
|
231
|
-
setError(err);
|
|
331
|
+
setState({ status: "error", result: void 0, error: err });
|
|
232
332
|
return;
|
|
233
333
|
}
|
|
234
334
|
current.current = opened;
|
|
235
|
-
|
|
236
|
-
setResult(void 0);
|
|
237
|
-
setError(void 0);
|
|
335
|
+
setState({ status: "opening", result: void 0, error: void 0 });
|
|
238
336
|
const fresh = () => current.current === opened;
|
|
239
337
|
opened.ready.then(
|
|
240
338
|
() => {
|
|
241
|
-
if (fresh())
|
|
339
|
+
if (!fresh()) return;
|
|
340
|
+
setState(
|
|
341
|
+
(s) => s.status === "opening" ? { status: "connected", result: void 0, error: void 0 } : s
|
|
342
|
+
);
|
|
242
343
|
},
|
|
243
344
|
() => {
|
|
244
345
|
}
|
|
@@ -246,14 +347,13 @@ function useOpenedWindow(factory) {
|
|
|
246
347
|
);
|
|
247
348
|
opened.result.then(
|
|
248
349
|
(value) => {
|
|
249
|
-
if (
|
|
250
|
-
setResult(value);
|
|
251
|
-
setStatus("done");
|
|
350
|
+
if (fresh()) setState({ status: "done", result: value, error: void 0 });
|
|
252
351
|
},
|
|
253
352
|
(err) => {
|
|
254
353
|
if (!fresh()) return;
|
|
255
|
-
|
|
256
|
-
|
|
354
|
+
setState(
|
|
355
|
+
err instanceof import_core2.WindowClosedError ? { status: "closed-early", result: void 0, error: err } : { status: "error", result: void 0, error: err }
|
|
356
|
+
);
|
|
257
357
|
}
|
|
258
358
|
);
|
|
259
359
|
}, []);
|
|
@@ -261,7 +361,7 @@ function useOpenedWindow(factory) {
|
|
|
261
361
|
current.current?.post(type, payload);
|
|
262
362
|
}, []);
|
|
263
363
|
const close = (0, import_react5.useCallback)(() => current.current?.close(), []);
|
|
264
|
-
return {
|
|
364
|
+
return { ...state, open, post, close };
|
|
265
365
|
}
|
|
266
366
|
|
|
267
367
|
// src/index.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow } from '@use-everywhere/core';
|
|
1
|
+
import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow, WindowClosedError } from '@use-everywhere/core';
|
|
2
2
|
export * from '@use-everywhere/core';
|
|
3
3
|
export { DEFAULT_NAME } from '@use-everywhere/core';
|
|
4
4
|
|
|
@@ -21,6 +21,12 @@ interface UseSharedStateOptions {
|
|
|
21
21
|
* this origin. Late-joining tabs hydrate to the current value; concurrent
|
|
22
22
|
* writes converge last-writer-wins. Pass options.scope to delimit how much
|
|
23
23
|
* is shared ('everywhere' | 'tabs' | 'tab').
|
|
24
|
+
*
|
|
25
|
+
* Note the convergence rule is last-writer-wins per key, not per operation:
|
|
26
|
+
* two tabs running `set(n => n + 1)` at the same moment agree on one value,
|
|
27
|
+
* and one of the increments is lost. For counters and other accumulating
|
|
28
|
+
* writes, drive them from a single tab (useLeaderEffect) until an op-based
|
|
29
|
+
* primitive lands.
|
|
24
30
|
*/
|
|
25
31
|
declare function useSharedState<T>(key: string, initial: T, options?: UseSharedStateOptions): [T, (next: T | ((prev: T) => T)) => void];
|
|
26
32
|
|
|
@@ -105,7 +111,16 @@ declare function defineStore<S extends Record<string, unknown> = Record<string,
|
|
|
105
111
|
declare function usePeers(options?: {
|
|
106
112
|
name?: string;
|
|
107
113
|
}): readonly Peer[];
|
|
108
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* This client's own id on the presence bus (matches patch origin ids).
|
|
116
|
+
*
|
|
117
|
+
* Read through useSyncExternalStore rather than returned straight from render,
|
|
118
|
+
* because the id is minted per environment: a server would render one value and
|
|
119
|
+
* the browser a different one, and any component that puts it in the DOM would
|
|
120
|
+
* mismatch on hydration. The server snapshot is a constant empty string, which
|
|
121
|
+
* React also uses for the client's hydrating render, so markup matches; the
|
|
122
|
+
* real id arrives in the commit straight after. Treat `''` as "not known yet".
|
|
123
|
+
*/
|
|
109
124
|
declare function useClientId(options?: {
|
|
110
125
|
name?: string;
|
|
111
126
|
}): string;
|
|
@@ -130,23 +145,59 @@ declare function useIsLeader(options?: UseLeaderOptions): boolean;
|
|
|
130
145
|
declare function useLeaderEffect(effect: () => void | (() => void), options?: UseLeaderOptions): void;
|
|
131
146
|
|
|
132
147
|
type OpenedWindowStatus = 'idle' | 'opening' | 'connected' | 'done' | 'closed-early' | 'error';
|
|
133
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The flow's state as a discriminated union, so `status` narrows `result` and
|
|
150
|
+
* `error` for you. Three independent fields could not: `result` was `R |
|
|
151
|
+
* undefined` even in the 'done' branch, which is why reading it needed a
|
|
152
|
+
* non-null assertion at every call site.
|
|
153
|
+
*/
|
|
154
|
+
type OpenedWindowState<R> = {
|
|
155
|
+
status: 'idle';
|
|
156
|
+
result: undefined;
|
|
157
|
+
error: undefined;
|
|
158
|
+
} | {
|
|
159
|
+
status: 'opening';
|
|
160
|
+
result: undefined;
|
|
161
|
+
error: undefined;
|
|
162
|
+
} | {
|
|
163
|
+
status: 'connected';
|
|
164
|
+
result: undefined;
|
|
165
|
+
error: undefined;
|
|
166
|
+
} | {
|
|
167
|
+
status: 'done';
|
|
168
|
+
result: R;
|
|
169
|
+
error: undefined;
|
|
170
|
+
}
|
|
171
|
+
/** The child window went away before calling finish(). */
|
|
172
|
+
| {
|
|
173
|
+
status: 'closed-early';
|
|
174
|
+
result: undefined;
|
|
175
|
+
error: WindowClosedError;
|
|
176
|
+
}
|
|
177
|
+
/** The handshake timed out, the popup was blocked, or the factory threw. */
|
|
178
|
+
| {
|
|
179
|
+
status: 'error';
|
|
180
|
+
result: undefined;
|
|
181
|
+
error: unknown;
|
|
182
|
+
};
|
|
183
|
+
interface OpenedWindowControls<Out extends MessageMap, In extends MessageMap, R> {
|
|
134
184
|
/** Call from a click handler (popup blockers require a user gesture). */
|
|
135
185
|
open: () => void;
|
|
136
|
-
status: OpenedWindowStatus;
|
|
137
|
-
/** The child's finish() value, once status is 'done'. */
|
|
138
|
-
result: R | undefined;
|
|
139
|
-
error: unknown;
|
|
140
186
|
/** Post to the child; no-op while nothing is open. */
|
|
141
187
|
post: OpenedWindow<Out, In, R>['post'];
|
|
142
188
|
close: () => void;
|
|
143
189
|
}
|
|
190
|
+
type UseOpenedWindow<Out extends MessageMap, In extends MessageMap, R> = OpenedWindowState<R> & OpenedWindowControls<Out, In, R>;
|
|
144
191
|
|
|
145
192
|
/**
|
|
146
193
|
* Drive an openWindow() flow from a component: the factory is called on
|
|
147
194
|
* open(), and the child window's lifecycle is folded into render state.
|
|
148
195
|
* Reopening replaces the previous window; a stale window's outcome is ignored.
|
|
196
|
+
*
|
|
197
|
+
* The child window deliberately outlives an unmount: closing a payment page
|
|
198
|
+
* because a route changed would lose the user's in-flight transaction. Call
|
|
199
|
+
* close() if the component owns the window's lifetime.
|
|
149
200
|
*/
|
|
150
201
|
declare function useOpenedWindow<Out extends MessageMap, In extends MessageMap, R = unknown>(factory: () => OpenedWindow<Out, In, R>): UseOpenedWindow<Out, In, R>;
|
|
151
202
|
|
|
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 };
|
|
203
|
+
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,4 +1,4 @@
|
|
|
1
|
-
import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow } from '@use-everywhere/core';
|
|
1
|
+
import { SharedStore, LeaderOptions, Leader, MessageMap, Channel, MessageMeta, PersistAdapter, Peer, LeaderSnapshot, OpenedWindow, WindowClosedError } from '@use-everywhere/core';
|
|
2
2
|
export * from '@use-everywhere/core';
|
|
3
3
|
export { DEFAULT_NAME } from '@use-everywhere/core';
|
|
4
4
|
|
|
@@ -21,6 +21,12 @@ interface UseSharedStateOptions {
|
|
|
21
21
|
* this origin. Late-joining tabs hydrate to the current value; concurrent
|
|
22
22
|
* writes converge last-writer-wins. Pass options.scope to delimit how much
|
|
23
23
|
* is shared ('everywhere' | 'tabs' | 'tab').
|
|
24
|
+
*
|
|
25
|
+
* Note the convergence rule is last-writer-wins per key, not per operation:
|
|
26
|
+
* two tabs running `set(n => n + 1)` at the same moment agree on one value,
|
|
27
|
+
* and one of the increments is lost. For counters and other accumulating
|
|
28
|
+
* writes, drive them from a single tab (useLeaderEffect) until an op-based
|
|
29
|
+
* primitive lands.
|
|
24
30
|
*/
|
|
25
31
|
declare function useSharedState<T>(key: string, initial: T, options?: UseSharedStateOptions): [T, (next: T | ((prev: T) => T)) => void];
|
|
26
32
|
|
|
@@ -105,7 +111,16 @@ declare function defineStore<S extends Record<string, unknown> = Record<string,
|
|
|
105
111
|
declare function usePeers(options?: {
|
|
106
112
|
name?: string;
|
|
107
113
|
}): readonly Peer[];
|
|
108
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* This client's own id on the presence bus (matches patch origin ids).
|
|
116
|
+
*
|
|
117
|
+
* Read through useSyncExternalStore rather than returned straight from render,
|
|
118
|
+
* because the id is minted per environment: a server would render one value and
|
|
119
|
+
* the browser a different one, and any component that puts it in the DOM would
|
|
120
|
+
* mismatch on hydration. The server snapshot is a constant empty string, which
|
|
121
|
+
* React also uses for the client's hydrating render, so markup matches; the
|
|
122
|
+
* real id arrives in the commit straight after. Treat `''` as "not known yet".
|
|
123
|
+
*/
|
|
109
124
|
declare function useClientId(options?: {
|
|
110
125
|
name?: string;
|
|
111
126
|
}): string;
|
|
@@ -130,23 +145,59 @@ declare function useIsLeader(options?: UseLeaderOptions): boolean;
|
|
|
130
145
|
declare function useLeaderEffect(effect: () => void | (() => void), options?: UseLeaderOptions): void;
|
|
131
146
|
|
|
132
147
|
type OpenedWindowStatus = 'idle' | 'opening' | 'connected' | 'done' | 'closed-early' | 'error';
|
|
133
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The flow's state as a discriminated union, so `status` narrows `result` and
|
|
150
|
+
* `error` for you. Three independent fields could not: `result` was `R |
|
|
151
|
+
* undefined` even in the 'done' branch, which is why reading it needed a
|
|
152
|
+
* non-null assertion at every call site.
|
|
153
|
+
*/
|
|
154
|
+
type OpenedWindowState<R> = {
|
|
155
|
+
status: 'idle';
|
|
156
|
+
result: undefined;
|
|
157
|
+
error: undefined;
|
|
158
|
+
} | {
|
|
159
|
+
status: 'opening';
|
|
160
|
+
result: undefined;
|
|
161
|
+
error: undefined;
|
|
162
|
+
} | {
|
|
163
|
+
status: 'connected';
|
|
164
|
+
result: undefined;
|
|
165
|
+
error: undefined;
|
|
166
|
+
} | {
|
|
167
|
+
status: 'done';
|
|
168
|
+
result: R;
|
|
169
|
+
error: undefined;
|
|
170
|
+
}
|
|
171
|
+
/** The child window went away before calling finish(). */
|
|
172
|
+
| {
|
|
173
|
+
status: 'closed-early';
|
|
174
|
+
result: undefined;
|
|
175
|
+
error: WindowClosedError;
|
|
176
|
+
}
|
|
177
|
+
/** The handshake timed out, the popup was blocked, or the factory threw. */
|
|
178
|
+
| {
|
|
179
|
+
status: 'error';
|
|
180
|
+
result: undefined;
|
|
181
|
+
error: unknown;
|
|
182
|
+
};
|
|
183
|
+
interface OpenedWindowControls<Out extends MessageMap, In extends MessageMap, R> {
|
|
134
184
|
/** Call from a click handler (popup blockers require a user gesture). */
|
|
135
185
|
open: () => void;
|
|
136
|
-
status: OpenedWindowStatus;
|
|
137
|
-
/** The child's finish() value, once status is 'done'. */
|
|
138
|
-
result: R | undefined;
|
|
139
|
-
error: unknown;
|
|
140
186
|
/** Post to the child; no-op while nothing is open. */
|
|
141
187
|
post: OpenedWindow<Out, In, R>['post'];
|
|
142
188
|
close: () => void;
|
|
143
189
|
}
|
|
190
|
+
type UseOpenedWindow<Out extends MessageMap, In extends MessageMap, R> = OpenedWindowState<R> & OpenedWindowControls<Out, In, R>;
|
|
144
191
|
|
|
145
192
|
/**
|
|
146
193
|
* Drive an openWindow() flow from a component: the factory is called on
|
|
147
194
|
* open(), and the child window's lifecycle is folded into render state.
|
|
148
195
|
* Reopening replaces the previous window; a stale window's outcome is ignored.
|
|
196
|
+
*
|
|
197
|
+
* The child window deliberately outlives an unmount: closing a payment page
|
|
198
|
+
* because a route changed would lose the user's in-flight transaction. Call
|
|
199
|
+
* close() if the component owns the window's lifetime.
|
|
149
200
|
*/
|
|
150
201
|
declare function useOpenedWindow<Out extends MessageMap, In extends MessageMap, R = unknown>(factory: () => OpenedWindow<Out, In, R>): UseOpenedWindow<Out, In, R>;
|
|
151
202
|
|
|
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 };
|
|
203
|
+
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
|
-
|
|
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
|
|
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 [
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
setError(err);
|
|
133
|
+
setState({ status: "error", result: void 0, error: err });
|
|
127
134
|
return;
|
|
128
135
|
}
|
|
129
136
|
current.current = opened;
|
|
130
|
-
|
|
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())
|
|
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 (
|
|
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
|
-
|
|
151
|
-
|
|
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,7 +163,7 @@ function useOpenedWindow(factory) {
|
|
|
156
163
|
current.current?.post(type, payload);
|
|
157
164
|
}, []);
|
|
158
165
|
const close = useCallback3(() => current.current?.close(), []);
|
|
159
|
-
return {
|
|
166
|
+
return { ...state, open, post, close };
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
// src/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-everywhere",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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>",
|
|
@@ -59,43 +59,43 @@
|
|
|
59
59
|
"name": "everything (import *)",
|
|
60
60
|
"path": "dist/index.js",
|
|
61
61
|
"import": "*",
|
|
62
|
-
"limit": "
|
|
62
|
+
"limit": "6.6 kB"
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"name": "useSharedState",
|
|
66
66
|
"path": "dist/index.js",
|
|
67
67
|
"import": "{ useSharedState }",
|
|
68
|
-
"limit": "
|
|
68
|
+
"limit": "2.85 kB"
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
"name": "useChannel + useMessage + useSend",
|
|
72
72
|
"path": "dist/index.js",
|
|
73
73
|
"import": "{ useChannel, useMessage, useSend }",
|
|
74
|
-
"limit": "1 kB"
|
|
74
|
+
"limit": "1.6 kB"
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
"name": "usePeers + useClientId",
|
|
78
78
|
"path": "dist/index.js",
|
|
79
79
|
"import": "{ usePeers, useClientId }",
|
|
80
|
-
"limit": "1.
|
|
80
|
+
"limit": "1.7 kB"
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"name": "useOpenedWindow + openWindow",
|
|
84
84
|
"path": "dist/index.js",
|
|
85
85
|
"import": "{ useOpenedWindow, openWindow }",
|
|
86
|
-
"limit": "1.
|
|
86
|
+
"limit": "1.5 kB"
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
"name": "useLeader + useLeaderEffect",
|
|
90
90
|
"path": "dist/index.js",
|
|
91
91
|
"import": "{ useLeader, useLeaderEffect }",
|
|
92
|
-
"limit": "
|
|
92
|
+
"limit": "2.15 kB"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"name": "defineStore (persisted)",
|
|
96
96
|
"path": "dist/index.js",
|
|
97
97
|
"import": "{ defineStore, localStorageAdapter }",
|
|
98
|
-
"limit": "
|
|
98
|
+
"limit": "3.3 kB"
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
"name": "Inspector (devtools subpath)",
|
|
@@ -105,18 +105,20 @@
|
|
|
105
105
|
}
|
|
106
106
|
],
|
|
107
107
|
"dependencies": {
|
|
108
|
-
"@use-everywhere/core": "0.
|
|
108
|
+
"@use-everywhere/core": "0.5.0"
|
|
109
109
|
},
|
|
110
110
|
"peerDependencies": {
|
|
111
111
|
"react": ">=18"
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
114
115
|
"@size-limit/preset-small-lib": "^13.0.1",
|
|
115
116
|
"@testing-library/react": "^16.3.2",
|
|
116
117
|
"@types/react": "^19.2.17",
|
|
117
118
|
"@types/react-dom": "^19.2.3",
|
|
118
119
|
"@vitest/coverage-v8": "^4.1.10",
|
|
119
120
|
"happy-dom": "^20.11.1",
|
|
121
|
+
"publint": "^0.3.21",
|
|
120
122
|
"react": "^19.2.8",
|
|
121
123
|
"react-dom": "^19.2.8",
|
|
122
124
|
"size-limit": "^13.0.1",
|
|
@@ -135,6 +137,8 @@
|
|
|
135
137
|
"build": "tsup",
|
|
136
138
|
"test": "vitest run --coverage",
|
|
137
139
|
"typecheck": "tsc --noEmit",
|
|
138
|
-
"size": "size-limit"
|
|
140
|
+
"size": "size-limit",
|
|
141
|
+
"check:exports": "publint --strict && attw --pack .",
|
|
142
|
+
"pack:smoke": "node --import tsx ../tooling/pack-smoke.ts"
|
|
139
143
|
}
|
|
140
144
|
}
|
package/dist/chunk-PNFJW7QK.js
DELETED
|
@@ -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
|
-
};
|