use-everywhere 0.12.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/{chunk-R7PBPLF7.js → chunk-YPQ6PLD5.js} +2 -2
- package/dist/devtools/index.js +1 -1
- package/dist/index.cjs +22 -22
- package/dist/index.d.cts +17 -17
- package/dist/index.d.ts +17 -17
- package/dist/index.js +17 -17
- package/llms.txt +6 -6
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ function CartBadge() {
|
|
|
57
57
|
const send = shop.useSend();
|
|
58
58
|
|
|
59
59
|
// Fires when any OTHER tab posts 'cart-updated'.
|
|
60
|
-
shop.
|
|
60
|
+
shop.useOnMessage('cart-updated', (payload) => setItems(payload.items));
|
|
61
61
|
|
|
62
62
|
const addToCart = () => {
|
|
63
63
|
setItems(items + 1); // this tab
|
|
@@ -68,7 +68,7 @@ function CartBadge() {
|
|
|
68
68
|
}
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
The standalone hooks — `useChannel(name)`, `
|
|
71
|
+
The standalone hooks — `useChannel(name)`, `useOnMessage(channel, type,
|
|
72
72
|
handler)`, `useSend(channel)` — are the same machinery without the
|
|
73
73
|
module-level binding, for one-off use.
|
|
74
74
|
|
|
@@ -84,20 +84,20 @@ function DuplicateTabWarning() {
|
|
|
84
84
|
}
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
## Cross-origin windows: `
|
|
87
|
+
## Cross-origin windows: `useWindowResult`
|
|
88
88
|
|
|
89
89
|
Open a window on another domain, exchange typed messages, and await its
|
|
90
90
|
result — the whole lifecycle folded into render state.
|
|
91
91
|
|
|
92
92
|
```tsx
|
|
93
|
-
import { openWindow,
|
|
93
|
+
import { openWindow, useWindowResult } from 'use-everywhere';
|
|
94
94
|
|
|
95
95
|
type ToPayment = { order: { orderId: string; amount: string } };
|
|
96
96
|
type FromPayment = { progress: { step: string } };
|
|
97
97
|
type Receipt = { receiptId: string; last4: string };
|
|
98
98
|
|
|
99
99
|
function PayButton() {
|
|
100
|
-
const pay =
|
|
100
|
+
const pay = useWindowResult<ToPayment, FromPayment, Receipt>(() =>
|
|
101
101
|
openWindow('https://pay.example.com/checkout', {
|
|
102
102
|
peerOrigin: 'https://pay.example.com', // required — '*' throws
|
|
103
103
|
}),
|
|
@@ -43,7 +43,7 @@ function warnOnInitialMismatch(storeName, key, initial) {
|
|
|
43
43
|
if (comparable(first) && comparable(initial) && !Object.is(first, initial)) {
|
|
44
44
|
devWarn(
|
|
45
45
|
"UE2001",
|
|
46
|
-
`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
|
|
46
|
+
`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 createStoreHooks, or a shared constant.`
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -127,7 +127,7 @@ function configureStore(name, scope, options) {
|
|
|
127
127
|
if (process.env.NODE_ENV !== "production") {
|
|
128
128
|
devWarn(
|
|
129
129
|
"UE2002",
|
|
130
|
-
`
|
|
130
|
+
`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.`
|
|
131
131
|
);
|
|
132
132
|
}
|
|
133
133
|
return;
|
package/dist/devtools/index.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -37,9 +37,9 @@ __export(src_exports, {
|
|
|
37
37
|
createPresence: () => import_core4.createPresence,
|
|
38
38
|
createSharedReducer: () => import_core4.createSharedReducer,
|
|
39
39
|
createSharedStore: () => import_core4.createSharedStore,
|
|
40
|
+
createStoreHooks: () => createStoreHooks,
|
|
40
41
|
defaultTransport: () => import_core4.defaultTransport,
|
|
41
42
|
defineChannel: () => defineChannel,
|
|
42
|
-
defineStore: () => defineStore,
|
|
43
43
|
enableDebug: () => import_core4.enableDebug,
|
|
44
44
|
getBusNames: () => import_core4.getBusNames,
|
|
45
45
|
getLeader: () => getLeader,
|
|
@@ -65,14 +65,14 @@ __export(src_exports, {
|
|
|
65
65
|
useIsLeader: () => useIsLeader,
|
|
66
66
|
useLeader: () => useLeader,
|
|
67
67
|
useLeaderEffect: () => useLeaderEffect,
|
|
68
|
-
|
|
69
|
-
useOpenedWindow: () => useOpenedWindow,
|
|
68
|
+
useOnMessage: () => useOnMessage,
|
|
70
69
|
usePeers: () => usePeers,
|
|
71
70
|
usePresenceMetadata: () => usePresenceMetadata,
|
|
72
71
|
useSend: () => useSend,
|
|
73
72
|
useSharedReducer: () => useSharedReducer,
|
|
73
|
+
useSharedSelector: () => useSharedSelector,
|
|
74
74
|
useSharedState: () => useSharedState,
|
|
75
|
-
|
|
75
|
+
useWindowResult: () => useWindowResult,
|
|
76
76
|
webStorageAdapter: () => import_core4.webStorageAdapter
|
|
77
77
|
});
|
|
78
78
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -112,7 +112,7 @@ function warnOnInitialMismatch(storeName, key, initial) {
|
|
|
112
112
|
if (comparable(first) && comparable(initial) && !Object.is(first, initial)) {
|
|
113
113
|
devWarn(
|
|
114
114
|
"UE2001",
|
|
115
|
-
`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
|
|
115
|
+
`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 createStoreHooks, or a shared constant.`
|
|
116
116
|
);
|
|
117
117
|
}
|
|
118
118
|
}
|
|
@@ -199,7 +199,7 @@ function configureStore(name, scope, options) {
|
|
|
199
199
|
if (process.env.NODE_ENV !== "production") {
|
|
200
200
|
devWarn(
|
|
201
201
|
"UE2002",
|
|
202
|
-
`
|
|
202
|
+
`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.`
|
|
203
203
|
);
|
|
204
204
|
}
|
|
205
205
|
return;
|
|
@@ -325,7 +325,7 @@ function useSharedReducer(reducer, initial, options) {
|
|
|
325
325
|
|
|
326
326
|
// src/use-shared-selector.ts
|
|
327
327
|
var import_react3 = require("react");
|
|
328
|
-
function
|
|
328
|
+
function useSharedSelector(selector, options) {
|
|
329
329
|
const store = getStore(options?.store ?? import_core.DEFAULT_NAME, options?.scope ?? "everywhere");
|
|
330
330
|
const equal = options?.equal ?? Object.is;
|
|
331
331
|
const selectorRef = (0, import_react3.useRef)(selector);
|
|
@@ -359,12 +359,12 @@ function shallowEqual(a, b) {
|
|
|
359
359
|
return keys.every((key) => Object.is(left[key], right[key]));
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
// src/use-message.ts
|
|
362
|
+
// src/use-on-message.ts
|
|
363
363
|
var import_react4 = require("react");
|
|
364
364
|
function useChannel(name) {
|
|
365
365
|
return getChannel(name);
|
|
366
366
|
}
|
|
367
|
-
function
|
|
367
|
+
function useOnMessage(channel, type, handler, options) {
|
|
368
368
|
const handlerRef = (0, import_react4.useRef)(handler);
|
|
369
369
|
(0, import_react4.useEffect)(() => {
|
|
370
370
|
handlerRef.current = handler;
|
|
@@ -398,16 +398,16 @@ function useAsk(channel) {
|
|
|
398
398
|
function defineChannel(name, options) {
|
|
399
399
|
if (options) configureChannel(name, options);
|
|
400
400
|
const useBoundSend = () => useSend(useChannel(name));
|
|
401
|
-
const useBoundMessage = (type, handler) =>
|
|
401
|
+
const useBoundMessage = (type, handler) => useOnMessage(useChannel(name), type, handler);
|
|
402
402
|
return {
|
|
403
403
|
get: () => getChannel(name),
|
|
404
404
|
useSend: useBoundSend,
|
|
405
|
-
|
|
405
|
+
useOnMessage: useBoundMessage
|
|
406
406
|
};
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
-
// src/
|
|
410
|
-
function
|
|
409
|
+
// src/create-store-hooks.ts
|
|
410
|
+
function createStoreHooks(name, options = {}) {
|
|
411
411
|
const scope = options.scope ?? "everywhere";
|
|
412
412
|
if (options.persist) {
|
|
413
413
|
configureStore(name, scope, {
|
|
@@ -422,7 +422,7 @@ function defineStore(name, options = {}) {
|
|
|
422
422
|
});
|
|
423
423
|
}
|
|
424
424
|
return {
|
|
425
|
-
|
|
425
|
+
store: () => getStore(name, scope),
|
|
426
426
|
useSharedState: (key, initial) => useSharedState(key, initial, { store: name, scope })
|
|
427
427
|
};
|
|
428
428
|
}
|
|
@@ -512,11 +512,11 @@ function createNamespace(namespace) {
|
|
|
512
512
|
const { busName } = core;
|
|
513
513
|
return {
|
|
514
514
|
...core,
|
|
515
|
-
|
|
515
|
+
createStoreHooks: (name, options) => createStoreHooks(busName(name), options),
|
|
516
516
|
defineChannel: (name, options) => defineChannel(busName(name), options),
|
|
517
517
|
getSharedStore: (name, scope) => getSharedStore(busName(name), scope),
|
|
518
518
|
useSharedState: (key, initial, options) => useSharedState(key, initial, { ...options, store: busName(options?.store) }),
|
|
519
|
-
|
|
519
|
+
useSharedSelector: (selector, options) => useSharedSelector(selector, { ...options, store: busName(options?.store) }),
|
|
520
520
|
usePeers: (options) => usePeers({ ...options, name: busName(options?.name) }),
|
|
521
521
|
usePresenceMetadata: (metadata, options) => usePresenceMetadata(metadata, { ...options, name: busName(options?.name) }),
|
|
522
522
|
useHydrated: (options) => useHydrated({ ...options, store: busName(options?.store) }),
|
|
@@ -527,7 +527,7 @@ function createNamespace(namespace) {
|
|
|
527
527
|
};
|
|
528
528
|
}
|
|
529
529
|
|
|
530
|
-
// src/use-
|
|
530
|
+
// src/use-window-result.ts
|
|
531
531
|
var import_react8 = require("react");
|
|
532
532
|
var import_core3 = require("@use-everywhere/core");
|
|
533
533
|
var IDLE = Object.freeze({
|
|
@@ -535,7 +535,7 @@ var IDLE = Object.freeze({
|
|
|
535
535
|
result: void 0,
|
|
536
536
|
error: void 0
|
|
537
537
|
});
|
|
538
|
-
function
|
|
538
|
+
function useWindowResult(factory) {
|
|
539
539
|
const [state, setState] = (0, import_react8.useState)(IDLE);
|
|
540
540
|
const current = (0, import_react8.useRef)(null);
|
|
541
541
|
const factoryRef = (0, import_react8.useRef)(factory);
|
|
@@ -604,9 +604,9 @@ var import_core4 = require("@use-everywhere/core");
|
|
|
604
604
|
createPresence,
|
|
605
605
|
createSharedReducer,
|
|
606
606
|
createSharedStore,
|
|
607
|
+
createStoreHooks,
|
|
607
608
|
defaultTransport,
|
|
608
609
|
defineChannel,
|
|
609
|
-
defineStore,
|
|
610
610
|
enableDebug,
|
|
611
611
|
getBusNames,
|
|
612
612
|
getLeader,
|
|
@@ -632,13 +632,13 @@ var import_core4 = require("@use-everywhere/core");
|
|
|
632
632
|
useIsLeader,
|
|
633
633
|
useLeader,
|
|
634
634
|
useLeaderEffect,
|
|
635
|
-
|
|
636
|
-
useOpenedWindow,
|
|
635
|
+
useOnMessage,
|
|
637
636
|
usePeers,
|
|
638
637
|
usePresenceMetadata,
|
|
639
638
|
useSend,
|
|
640
639
|
useSharedReducer,
|
|
640
|
+
useSharedSelector,
|
|
641
641
|
useSharedState,
|
|
642
|
-
|
|
642
|
+
useWindowResult,
|
|
643
643
|
webStorageAdapter
|
|
644
644
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -91,7 +91,7 @@ interface UseSharedSelectorOptions extends UseSharedStateOptions {
|
|
|
91
91
|
* anything in it.
|
|
92
92
|
*
|
|
93
93
|
* ```tsx
|
|
94
|
-
* const total =
|
|
94
|
+
* const total = useSharedSelector<Cart, number>(
|
|
95
95
|
* (cart) => cart.items.length + cart.saved.length,
|
|
96
96
|
* );
|
|
97
97
|
* ```
|
|
@@ -111,7 +111,7 @@ interface UseSharedSelectorOptions extends UseSharedStateOptions {
|
|
|
111
111
|
* selector runs against `{}`, and its result is what the server-rendered markup
|
|
112
112
|
* shows.
|
|
113
113
|
*/
|
|
114
|
-
declare function
|
|
114
|
+
declare function useSharedSelector<S extends Record<string, unknown>, T>(selector: (state: S) => T, options?: UseSharedSelectorOptions): T;
|
|
115
115
|
/** Shallow equality over an object or array, for selectors that build one. */
|
|
116
116
|
declare function shallowEqual(a: unknown, b: unknown): boolean;
|
|
117
117
|
|
|
@@ -130,7 +130,7 @@ declare function getLeader(name: string, options?: LeaderOptions): Leader;
|
|
|
130
130
|
|
|
131
131
|
/** Get the page-wide typed channel for `name` (one instance per name). */
|
|
132
132
|
declare function useChannel<M extends MessageMap, R extends ReplyMap<M> = Record<never, never>>(name: string): Channel<M, R>;
|
|
133
|
-
interface
|
|
133
|
+
interface UseOnMessageOptions {
|
|
134
134
|
/**
|
|
135
135
|
* Subscribe at all. Default true.
|
|
136
136
|
*
|
|
@@ -146,7 +146,7 @@ interface UseMessageOptions {
|
|
|
146
146
|
* Subscribe to one message type. The handler is kept fresh without
|
|
147
147
|
* resubscribing, so it may close over render state.
|
|
148
148
|
*/
|
|
149
|
-
declare function
|
|
149
|
+
declare function useOnMessage<M extends MessageMap, R extends ReplyMap<M>, K extends keyof M & string>(channel: Channel<M, R>, type: K, handler: (payload: M[K], meta: MessageMeta) => void, options?: UseOnMessageOptions): void;
|
|
150
150
|
/**
|
|
151
151
|
* Answer `ask`s of one type for as long as this component is mounted.
|
|
152
152
|
*
|
|
@@ -173,14 +173,14 @@ interface ChannelHooks<M extends MessageMap> {
|
|
|
173
173
|
useSend: () => Channel<M>['post'];
|
|
174
174
|
/**
|
|
175
175
|
* Subscribe to one message type. Same contract as the standalone
|
|
176
|
-
* `
|
|
176
|
+
* `useOnMessage`: the handler is kept fresh without resubscribing.
|
|
177
177
|
*/
|
|
178
|
-
|
|
178
|
+
useOnMessage: <K extends keyof M & string>(type: K, handler: (payload: M[K], meta: MessageMeta) => void) => void;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
182
|
* Bind a channel name and message map once, at module level, and get fully
|
|
183
|
-
* typed hooks back. Sugar over useChannel/
|
|
183
|
+
* typed hooks back. Sugar over useChannel/useOnMessage/useSend — the same
|
|
184
184
|
* page-wide channel singleton is shared, so mixing bound and standalone
|
|
185
185
|
* hooks for one name is safe.
|
|
186
186
|
*
|
|
@@ -189,7 +189,7 @@ interface ChannelHooks<M extends MessageMap> {
|
|
|
189
189
|
*/
|
|
190
190
|
declare function defineChannel<M extends MessageMap>(name: string, options?: ChannelOptions<M>): ChannelHooks<M>;
|
|
191
191
|
|
|
192
|
-
interface
|
|
192
|
+
interface CreateStoreHooksOptions {
|
|
193
193
|
/** Restore this store from disk on first use, and write it back as it changes. */
|
|
194
194
|
persist?: PersistAdapter;
|
|
195
195
|
/** Persist only these keys. Default: every key that has been written. */
|
|
@@ -212,7 +212,7 @@ interface DefineStoreOptions {
|
|
|
212
212
|
/** A store bound to a name and a shape: typed hooks with no per-call generics. */
|
|
213
213
|
interface StoreHooks<S extends Record<string, unknown>> {
|
|
214
214
|
/** The underlying store instance (the same one the hooks use) — for non-React code. */
|
|
215
|
-
|
|
215
|
+
store: () => AnyStore;
|
|
216
216
|
useSharedState: <K extends keyof S & string>(key: K, initial: S[K]) => [S[K], (next: S[K] | ((prev: S[K]) => S[K])) => void];
|
|
217
217
|
}
|
|
218
218
|
|
|
@@ -222,7 +222,7 @@ interface StoreHooks<S extends Record<string, unknown>> {
|
|
|
222
222
|
* Like defineChannel, this does not construct anything: it registers the
|
|
223
223
|
* options the registry will use when the store is first needed, so importing
|
|
224
224
|
* the module has no side effect. The store stays a singleton per name, so
|
|
225
|
-
* `
|
|
225
|
+
* `createStoreHooks('settings', { persist })` and a bare
|
|
226
226
|
* `useSharedState('theme', 'dark', { store: 'settings' })` elsewhere resolve to
|
|
227
227
|
* the same store, and both get persistence.
|
|
228
228
|
*
|
|
@@ -230,7 +230,7 @@ interface StoreHooks<S extends Record<string, unknown>> {
|
|
|
230
230
|
* so intended usage is automatic; if it does run late it throws rather than
|
|
231
231
|
* quietly handing back a store with no persistence.
|
|
232
232
|
*/
|
|
233
|
-
declare function
|
|
233
|
+
declare function createStoreHooks<S extends Record<string, unknown> = Record<string, unknown>>(name: string, options?: CreateStoreHooksOptions): StoreHooks<S>;
|
|
234
234
|
|
|
235
235
|
interface UsePeersOptions {
|
|
236
236
|
/** Bus name. Default 'use-everywhere'. */
|
|
@@ -337,11 +337,11 @@ declare function useLeaderEffect(effect: () => void | (() => void), options?: Us
|
|
|
337
337
|
* which namespace it lives in.
|
|
338
338
|
*/
|
|
339
339
|
interface ReactNamespace extends Namespace {
|
|
340
|
-
|
|
340
|
+
createStoreHooks<S extends Record<string, unknown> = Record<string, unknown>>(name: string, options?: CreateStoreHooksOptions): StoreHooks<S>;
|
|
341
341
|
defineChannel<M extends MessageMap>(name?: string, options?: ChannelOptions<M>): ChannelHooks<M>;
|
|
342
342
|
getSharedStore(name?: string, scope?: UseSharedStateOptions['scope']): AnyStore;
|
|
343
343
|
useSharedState<T>(key: string, initial: T, options?: UseSharedStateOptions): [T, (next: T | ((prev: T) => T)) => void];
|
|
344
|
-
|
|
344
|
+
useSharedSelector<S extends Record<string, unknown>, T>(selector: (state: S) => T, options?: UseSharedSelectorOptions): T;
|
|
345
345
|
usePeers(options?: UsePeersOptions): readonly Peer[];
|
|
346
346
|
usePresenceMetadata(metadata: unknown, options?: UsePeersOptions): void;
|
|
347
347
|
useHydrated(options?: UseSharedStateOptions): boolean;
|
|
@@ -364,7 +364,7 @@ interface ReactNamespace extends Namespace {
|
|
|
364
364
|
* const [items, setItems] = checkout.useSharedState('items', []);
|
|
365
365
|
* ```
|
|
366
366
|
*
|
|
367
|
-
* Call it **at module scope**, like `
|
|
367
|
+
* Call it **at module scope**, like `createStoreHooks` and `defineChannel`. It
|
|
368
368
|
* builds a small object of bound functions, and rebuilding that on every render
|
|
369
369
|
* would hand React a new `useSharedState` identity each time — harmless for the
|
|
370
370
|
* hooks themselves, which key off the bus name, but pointless work and a
|
|
@@ -418,7 +418,7 @@ interface OpenedWindowControls<Out extends MessageMap, In extends MessageMap, R>
|
|
|
418
418
|
post: OpenedWindow<Out, In, R>['post'];
|
|
419
419
|
close: () => void;
|
|
420
420
|
}
|
|
421
|
-
type
|
|
421
|
+
type UseWindowResult<Out extends MessageMap, In extends MessageMap, R> = OpenedWindowState<R> & OpenedWindowControls<Out, In, R>;
|
|
422
422
|
|
|
423
423
|
/**
|
|
424
424
|
* Drive an openWindow() flow from a component: the factory is called on
|
|
@@ -429,6 +429,6 @@ type UseOpenedWindow<Out extends MessageMap, In extends MessageMap, R> = OpenedW
|
|
|
429
429
|
* because a route changed would lose the user's in-flight transaction. Call
|
|
430
430
|
* close() if the component owns the window's lifetime.
|
|
431
431
|
*/
|
|
432
|
-
declare function
|
|
432
|
+
declare function useWindowResult<Out extends MessageMap, In extends MessageMap, R = unknown>(factory: () => OpenedWindow<Out, In, R>): UseWindowResult<Out, In, R>;
|
|
433
433
|
|
|
434
|
-
export { type AnyStore, type ChannelHooks, type
|
|
434
|
+
export { type AnyStore, type ChannelHooks, type CreateStoreHooksOptions, type OpenedWindowControls, type OpenedWindowState, type OpenedWindowStatus, type ReactNamespace, type ShareScope, type StoreHooks, type UseLeaderOptions, type UseOnMessageOptions, type UsePeersOptions, type UseSharedReducerOptions, type UseSharedSelectorOptions, type UseSharedStateOptions, type UseWindowResult, createNamespace, createStoreHooks, defineChannel, getLeader, getSharedStore, shallowEqual, useAnswer, useAsk, useChannel, useClientId, useHydrated, useIsLeader, useLeader, useLeaderEffect, useOnMessage, usePeers, usePresenceMetadata, useSend, useSharedReducer, useSharedSelector, useSharedState, useWindowResult };
|
package/dist/index.d.ts
CHANGED
|
@@ -91,7 +91,7 @@ interface UseSharedSelectorOptions extends UseSharedStateOptions {
|
|
|
91
91
|
* anything in it.
|
|
92
92
|
*
|
|
93
93
|
* ```tsx
|
|
94
|
-
* const total =
|
|
94
|
+
* const total = useSharedSelector<Cart, number>(
|
|
95
95
|
* (cart) => cart.items.length + cart.saved.length,
|
|
96
96
|
* );
|
|
97
97
|
* ```
|
|
@@ -111,7 +111,7 @@ interface UseSharedSelectorOptions extends UseSharedStateOptions {
|
|
|
111
111
|
* selector runs against `{}`, and its result is what the server-rendered markup
|
|
112
112
|
* shows.
|
|
113
113
|
*/
|
|
114
|
-
declare function
|
|
114
|
+
declare function useSharedSelector<S extends Record<string, unknown>, T>(selector: (state: S) => T, options?: UseSharedSelectorOptions): T;
|
|
115
115
|
/** Shallow equality over an object or array, for selectors that build one. */
|
|
116
116
|
declare function shallowEqual(a: unknown, b: unknown): boolean;
|
|
117
117
|
|
|
@@ -130,7 +130,7 @@ declare function getLeader(name: string, options?: LeaderOptions): Leader;
|
|
|
130
130
|
|
|
131
131
|
/** Get the page-wide typed channel for `name` (one instance per name). */
|
|
132
132
|
declare function useChannel<M extends MessageMap, R extends ReplyMap<M> = Record<never, never>>(name: string): Channel<M, R>;
|
|
133
|
-
interface
|
|
133
|
+
interface UseOnMessageOptions {
|
|
134
134
|
/**
|
|
135
135
|
* Subscribe at all. Default true.
|
|
136
136
|
*
|
|
@@ -146,7 +146,7 @@ interface UseMessageOptions {
|
|
|
146
146
|
* Subscribe to one message type. The handler is kept fresh without
|
|
147
147
|
* resubscribing, so it may close over render state.
|
|
148
148
|
*/
|
|
149
|
-
declare function
|
|
149
|
+
declare function useOnMessage<M extends MessageMap, R extends ReplyMap<M>, K extends keyof M & string>(channel: Channel<M, R>, type: K, handler: (payload: M[K], meta: MessageMeta) => void, options?: UseOnMessageOptions): void;
|
|
150
150
|
/**
|
|
151
151
|
* Answer `ask`s of one type for as long as this component is mounted.
|
|
152
152
|
*
|
|
@@ -173,14 +173,14 @@ interface ChannelHooks<M extends MessageMap> {
|
|
|
173
173
|
useSend: () => Channel<M>['post'];
|
|
174
174
|
/**
|
|
175
175
|
* Subscribe to one message type. Same contract as the standalone
|
|
176
|
-
* `
|
|
176
|
+
* `useOnMessage`: the handler is kept fresh without resubscribing.
|
|
177
177
|
*/
|
|
178
|
-
|
|
178
|
+
useOnMessage: <K extends keyof M & string>(type: K, handler: (payload: M[K], meta: MessageMeta) => void) => void;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
182
|
* Bind a channel name and message map once, at module level, and get fully
|
|
183
|
-
* typed hooks back. Sugar over useChannel/
|
|
183
|
+
* typed hooks back. Sugar over useChannel/useOnMessage/useSend — the same
|
|
184
184
|
* page-wide channel singleton is shared, so mixing bound and standalone
|
|
185
185
|
* hooks for one name is safe.
|
|
186
186
|
*
|
|
@@ -189,7 +189,7 @@ interface ChannelHooks<M extends MessageMap> {
|
|
|
189
189
|
*/
|
|
190
190
|
declare function defineChannel<M extends MessageMap>(name: string, options?: ChannelOptions<M>): ChannelHooks<M>;
|
|
191
191
|
|
|
192
|
-
interface
|
|
192
|
+
interface CreateStoreHooksOptions {
|
|
193
193
|
/** Restore this store from disk on first use, and write it back as it changes. */
|
|
194
194
|
persist?: PersistAdapter;
|
|
195
195
|
/** Persist only these keys. Default: every key that has been written. */
|
|
@@ -212,7 +212,7 @@ interface DefineStoreOptions {
|
|
|
212
212
|
/** A store bound to a name and a shape: typed hooks with no per-call generics. */
|
|
213
213
|
interface StoreHooks<S extends Record<string, unknown>> {
|
|
214
214
|
/** The underlying store instance (the same one the hooks use) — for non-React code. */
|
|
215
|
-
|
|
215
|
+
store: () => AnyStore;
|
|
216
216
|
useSharedState: <K extends keyof S & string>(key: K, initial: S[K]) => [S[K], (next: S[K] | ((prev: S[K]) => S[K])) => void];
|
|
217
217
|
}
|
|
218
218
|
|
|
@@ -222,7 +222,7 @@ interface StoreHooks<S extends Record<string, unknown>> {
|
|
|
222
222
|
* Like defineChannel, this does not construct anything: it registers the
|
|
223
223
|
* options the registry will use when the store is first needed, so importing
|
|
224
224
|
* the module has no side effect. The store stays a singleton per name, so
|
|
225
|
-
* `
|
|
225
|
+
* `createStoreHooks('settings', { persist })` and a bare
|
|
226
226
|
* `useSharedState('theme', 'dark', { store: 'settings' })` elsewhere resolve to
|
|
227
227
|
* the same store, and both get persistence.
|
|
228
228
|
*
|
|
@@ -230,7 +230,7 @@ interface StoreHooks<S extends Record<string, unknown>> {
|
|
|
230
230
|
* so intended usage is automatic; if it does run late it throws rather than
|
|
231
231
|
* quietly handing back a store with no persistence.
|
|
232
232
|
*/
|
|
233
|
-
declare function
|
|
233
|
+
declare function createStoreHooks<S extends Record<string, unknown> = Record<string, unknown>>(name: string, options?: CreateStoreHooksOptions): StoreHooks<S>;
|
|
234
234
|
|
|
235
235
|
interface UsePeersOptions {
|
|
236
236
|
/** Bus name. Default 'use-everywhere'. */
|
|
@@ -337,11 +337,11 @@ declare function useLeaderEffect(effect: () => void | (() => void), options?: Us
|
|
|
337
337
|
* which namespace it lives in.
|
|
338
338
|
*/
|
|
339
339
|
interface ReactNamespace extends Namespace {
|
|
340
|
-
|
|
340
|
+
createStoreHooks<S extends Record<string, unknown> = Record<string, unknown>>(name: string, options?: CreateStoreHooksOptions): StoreHooks<S>;
|
|
341
341
|
defineChannel<M extends MessageMap>(name?: string, options?: ChannelOptions<M>): ChannelHooks<M>;
|
|
342
342
|
getSharedStore(name?: string, scope?: UseSharedStateOptions['scope']): AnyStore;
|
|
343
343
|
useSharedState<T>(key: string, initial: T, options?: UseSharedStateOptions): [T, (next: T | ((prev: T) => T)) => void];
|
|
344
|
-
|
|
344
|
+
useSharedSelector<S extends Record<string, unknown>, T>(selector: (state: S) => T, options?: UseSharedSelectorOptions): T;
|
|
345
345
|
usePeers(options?: UsePeersOptions): readonly Peer[];
|
|
346
346
|
usePresenceMetadata(metadata: unknown, options?: UsePeersOptions): void;
|
|
347
347
|
useHydrated(options?: UseSharedStateOptions): boolean;
|
|
@@ -364,7 +364,7 @@ interface ReactNamespace extends Namespace {
|
|
|
364
364
|
* const [items, setItems] = checkout.useSharedState('items', []);
|
|
365
365
|
* ```
|
|
366
366
|
*
|
|
367
|
-
* Call it **at module scope**, like `
|
|
367
|
+
* Call it **at module scope**, like `createStoreHooks` and `defineChannel`. It
|
|
368
368
|
* builds a small object of bound functions, and rebuilding that on every render
|
|
369
369
|
* would hand React a new `useSharedState` identity each time — harmless for the
|
|
370
370
|
* hooks themselves, which key off the bus name, but pointless work and a
|
|
@@ -418,7 +418,7 @@ interface OpenedWindowControls<Out extends MessageMap, In extends MessageMap, R>
|
|
|
418
418
|
post: OpenedWindow<Out, In, R>['post'];
|
|
419
419
|
close: () => void;
|
|
420
420
|
}
|
|
421
|
-
type
|
|
421
|
+
type UseWindowResult<Out extends MessageMap, In extends MessageMap, R> = OpenedWindowState<R> & OpenedWindowControls<Out, In, R>;
|
|
422
422
|
|
|
423
423
|
/**
|
|
424
424
|
* Drive an openWindow() flow from a component: the factory is called on
|
|
@@ -429,6 +429,6 @@ type UseOpenedWindow<Out extends MessageMap, In extends MessageMap, R> = OpenedW
|
|
|
429
429
|
* because a route changed would lose the user's in-flight transaction. Call
|
|
430
430
|
* close() if the component owns the window's lifetime.
|
|
431
431
|
*/
|
|
432
|
-
declare function
|
|
432
|
+
declare function useWindowResult<Out extends MessageMap, In extends MessageMap, R = unknown>(factory: () => OpenedWindow<Out, In, R>): UseWindowResult<Out, In, R>;
|
|
433
433
|
|
|
434
|
-
export { type AnyStore, type ChannelHooks, type
|
|
434
|
+
export { type AnyStore, type ChannelHooks, type CreateStoreHooksOptions, type OpenedWindowControls, type OpenedWindowState, type OpenedWindowStatus, type ReactNamespace, type ShareScope, type StoreHooks, type UseLeaderOptions, type UseOnMessageOptions, type UsePeersOptions, type UseSharedReducerOptions, type UseSharedSelectorOptions, type UseSharedStateOptions, type UseWindowResult, createNamespace, createStoreHooks, defineChannel, getLeader, getSharedStore, shallowEqual, useAnswer, useAsk, useChannel, useClientId, useHydrated, useIsLeader, useLeader, useLeaderEffect, useOnMessage, usePeers, usePresenceMetadata, useSend, useSharedReducer, useSharedSelector, useSharedState, useWindowResult };
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
usePeers,
|
|
13
13
|
usePresenceMetadata,
|
|
14
14
|
warnOnInitialMismatch
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-YPQ6PLD5.js";
|
|
16
16
|
|
|
17
17
|
// src/use-shared-state.ts
|
|
18
18
|
import { useCallback, useSyncExternalStore } from "react";
|
|
@@ -55,7 +55,7 @@ function useSharedReducer(reducer, initial, options) {
|
|
|
55
55
|
|
|
56
56
|
// src/use-shared-selector.ts
|
|
57
57
|
import { useCallback as useCallback3, useRef, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
58
|
-
function
|
|
58
|
+
function useSharedSelector(selector, options) {
|
|
59
59
|
const store = getStore(options?.store ?? DEFAULT_NAME, options?.scope ?? "everywhere");
|
|
60
60
|
const equal = options?.equal ?? Object.is;
|
|
61
61
|
const selectorRef = useRef(selector);
|
|
@@ -89,12 +89,12 @@ function shallowEqual(a, b) {
|
|
|
89
89
|
return keys.every((key) => Object.is(left[key], right[key]));
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
// src/use-message.ts
|
|
92
|
+
// src/use-on-message.ts
|
|
93
93
|
import { useEffect, useRef as useRef2 } from "react";
|
|
94
94
|
function useChannel(name) {
|
|
95
95
|
return getChannel(name);
|
|
96
96
|
}
|
|
97
|
-
function
|
|
97
|
+
function useOnMessage(channel, type, handler, options) {
|
|
98
98
|
const handlerRef = useRef2(handler);
|
|
99
99
|
useEffect(() => {
|
|
100
100
|
handlerRef.current = handler;
|
|
@@ -128,16 +128,16 @@ function useAsk(channel) {
|
|
|
128
128
|
function defineChannel(name, options) {
|
|
129
129
|
if (options) configureChannel(name, options);
|
|
130
130
|
const useBoundSend = () => useSend(useChannel(name));
|
|
131
|
-
const useBoundMessage = (type, handler) =>
|
|
131
|
+
const useBoundMessage = (type, handler) => useOnMessage(useChannel(name), type, handler);
|
|
132
132
|
return {
|
|
133
133
|
get: () => getChannel(name),
|
|
134
134
|
useSend: useBoundSend,
|
|
135
|
-
|
|
135
|
+
useOnMessage: useBoundMessage
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
// src/
|
|
140
|
-
function
|
|
139
|
+
// src/create-store-hooks.ts
|
|
140
|
+
function createStoreHooks(name, options = {}) {
|
|
141
141
|
const scope = options.scope ?? "everywhere";
|
|
142
142
|
if (options.persist) {
|
|
143
143
|
configureStore(name, scope, {
|
|
@@ -152,7 +152,7 @@ function defineStore(name, options = {}) {
|
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
154
|
return {
|
|
155
|
-
|
|
155
|
+
store: () => getStore(name, scope),
|
|
156
156
|
useSharedState: (key, initial) => useSharedState(key, initial, { store: name, scope })
|
|
157
157
|
};
|
|
158
158
|
}
|
|
@@ -214,11 +214,11 @@ function createNamespace(namespace) {
|
|
|
214
214
|
const { busName } = core;
|
|
215
215
|
return {
|
|
216
216
|
...core,
|
|
217
|
-
|
|
217
|
+
createStoreHooks: (name, options) => createStoreHooks(busName(name), options),
|
|
218
218
|
defineChannel: (name, options) => defineChannel(busName(name), options),
|
|
219
219
|
getSharedStore: (name, scope) => getSharedStore(busName(name), scope),
|
|
220
220
|
useSharedState: (key, initial, options) => useSharedState(key, initial, { ...options, store: busName(options?.store) }),
|
|
221
|
-
|
|
221
|
+
useSharedSelector: (selector, options) => useSharedSelector(selector, { ...options, store: busName(options?.store) }),
|
|
222
222
|
usePeers: (options) => usePeers({ ...options, name: busName(options?.name) }),
|
|
223
223
|
usePresenceMetadata: (metadata, options) => usePresenceMetadata(metadata, { ...options, name: busName(options?.name) }),
|
|
224
224
|
useHydrated: (options) => useHydrated({ ...options, store: busName(options?.store) }),
|
|
@@ -229,7 +229,7 @@ function createNamespace(namespace) {
|
|
|
229
229
|
};
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
// src/use-
|
|
232
|
+
// src/use-window-result.ts
|
|
233
233
|
import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef4, useState as useState2 } from "react";
|
|
234
234
|
import { WindowClosedError } from "@use-everywhere/core";
|
|
235
235
|
var IDLE = Object.freeze({
|
|
@@ -237,7 +237,7 @@ var IDLE = Object.freeze({
|
|
|
237
237
|
result: void 0,
|
|
238
238
|
error: void 0
|
|
239
239
|
});
|
|
240
|
-
function
|
|
240
|
+
function useWindowResult(factory) {
|
|
241
241
|
const [state, setState] = useState2(IDLE);
|
|
242
242
|
const current = useRef4(null);
|
|
243
243
|
const factoryRef = useRef4(factory);
|
|
@@ -336,9 +336,9 @@ export {
|
|
|
336
336
|
createPresence,
|
|
337
337
|
createSharedReducer,
|
|
338
338
|
createSharedStore,
|
|
339
|
+
createStoreHooks,
|
|
339
340
|
defaultTransport,
|
|
340
341
|
defineChannel,
|
|
341
|
-
defineStore,
|
|
342
342
|
enableDebug,
|
|
343
343
|
getBusNames,
|
|
344
344
|
getLeader,
|
|
@@ -364,13 +364,13 @@ export {
|
|
|
364
364
|
useIsLeader,
|
|
365
365
|
useLeader,
|
|
366
366
|
useLeaderEffect,
|
|
367
|
-
|
|
368
|
-
useOpenedWindow,
|
|
367
|
+
useOnMessage,
|
|
369
368
|
usePeers,
|
|
370
369
|
usePresenceMetadata,
|
|
371
370
|
useSend,
|
|
372
371
|
useSharedReducer,
|
|
372
|
+
useSharedSelector,
|
|
373
373
|
useSharedState,
|
|
374
|
-
|
|
374
|
+
useWindowResult,
|
|
375
375
|
webStorageAdapter
|
|
376
376
|
};
|
package/llms.txt
CHANGED
|
@@ -31,7 +31,7 @@ Open it in two tabs. Clicking in one updates both.
|
|
|
31
31
|
|
|
32
32
|
## The three mistakes to avoid
|
|
33
33
|
|
|
34
|
-
**1. Calling `defineChannel` or `
|
|
34
|
+
**1. Calling `defineChannel` or `createStoreHooks` inside a component.** They are
|
|
35
35
|
module-scope factories. Called in a render they build a new bus on every
|
|
36
36
|
render, and the page syncs nothing while looking completely correct. Install
|
|
37
37
|
`eslint-plugin-use-everywhere` — this is the mistake its `define-at-module-scope`
|
|
@@ -65,9 +65,9 @@ and symbols do not survive. Dates, Maps, Sets and typed arrays do. The
|
|
|
65
65
|
| --- | --- | --- |
|
|
66
66
|
| `useSharedState` | hook | "What is the current value?" — `useState` across every tab |
|
|
67
67
|
| `useSharedReducer` | hook | The same, dispatch-shaped |
|
|
68
|
-
| `
|
|
68
|
+
| `useSharedSelector` | hook | Subscribe to a slice of a store with a selector |
|
|
69
69
|
| `useChannel` | hook | The typed event channel for a name |
|
|
70
|
-
| `
|
|
70
|
+
| `useOnMessage` | hook | Run a handler when another tab posts an event |
|
|
71
71
|
| `useSend` | hook | A stable, typed `post` for a channel |
|
|
72
72
|
| `useAsk` | hook | Request/response across tabs |
|
|
73
73
|
| `useAnswer` | hook | Serve the other half of `useAsk` |
|
|
@@ -77,10 +77,10 @@ and symbols do not survive. Dates, Maps, Sets and typed arrays do. The
|
|
|
77
77
|
| `useLeader` | hook | Elect exactly one tab to own a job |
|
|
78
78
|
| `useIsLeader` | hook | Whether this tab currently holds the lease |
|
|
79
79
|
| `useLeaderEffect` | hook | Run an effect only in the leader tab |
|
|
80
|
-
| `
|
|
80
|
+
| `useWindowResult` | hook | The cross-origin window lifecycle as render state |
|
|
81
81
|
| `useHydrated` | hook | Whether the first cross-tab sync has landed (SSR) |
|
|
82
82
|
| `defineChannel` | factory | Bind a channel name + message map once, at module scope |
|
|
83
|
-
| `
|
|
83
|
+
| `createStoreHooks` | factory | The same for a store, and where persistence is enabled |
|
|
84
84
|
| `createNamespace` | factory | Prefix every bus name, for micro-frontends |
|
|
85
85
|
| `Inspector` | component | Devtools panel; import from `use-everywhere/devtools` |
|
|
86
86
|
|
|
@@ -91,7 +91,7 @@ in `@use-everywhere/core` is re-exported from here too — see that package's
|
|
|
91
91
|
## Gotchas
|
|
92
92
|
|
|
93
93
|
- **Shared state never crosses origins.** Two origins are two trust domains. For
|
|
94
|
-
that, open a window explicitly with `
|
|
94
|
+
that, open a window explicitly with `useWindowResult`, which is typed,
|
|
95
95
|
per-message and validated by origin.
|
|
96
96
|
- **Last-writer-wins, per key.** Per-key `[counter, clientId]` clocks give a
|
|
97
97
|
deterministic tie-break, not a merge. Two tabs editing the same key
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-everywhere",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.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>",
|
|
@@ -109,9 +109,9 @@
|
|
|
109
109
|
"limit": "4.24 kB"
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
|
-
"name": "
|
|
112
|
+
"name": "useSharedSelector",
|
|
113
113
|
"path": "dist/index.js",
|
|
114
|
-
"import": "{
|
|
114
|
+
"import": "{ useSharedSelector, shallowEqual }",
|
|
115
115
|
"limit": "4.40 kB"
|
|
116
116
|
},
|
|
117
117
|
{
|
|
@@ -121,9 +121,9 @@
|
|
|
121
121
|
"limit": "4.21 kB"
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
|
-
"name": "useChannel +
|
|
124
|
+
"name": "useChannel + useOnMessage + useSend",
|
|
125
125
|
"path": "dist/index.js",
|
|
126
|
-
"import": "{ useChannel,
|
|
126
|
+
"import": "{ useChannel, useOnMessage, useSend }",
|
|
127
127
|
"limit": "3.07 kB"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
@@ -133,9 +133,9 @@
|
|
|
133
133
|
"limit": "2.77 kB"
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
|
-
"name": "
|
|
136
|
+
"name": "useWindowResult + openWindow",
|
|
137
137
|
"path": "dist/index.js",
|
|
138
|
-
"import": "{
|
|
138
|
+
"import": "{ useWindowResult, openWindow }",
|
|
139
139
|
"limit": "1.47 kB"
|
|
140
140
|
},
|
|
141
141
|
{
|
|
@@ -145,9 +145,9 @@
|
|
|
145
145
|
"limit": "3.65 kB"
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
|
-
"name": "
|
|
148
|
+
"name": "createStoreHooks (persisted)",
|
|
149
149
|
"path": "dist/index.js",
|
|
150
|
-
"import": "{
|
|
150
|
+
"import": "{ createStoreHooks, localStorageAdapter }",
|
|
151
151
|
"limit": "4.67 kB"
|
|
152
152
|
},
|
|
153
153
|
{
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
}
|
|
171
171
|
],
|
|
172
172
|
"dependencies": {
|
|
173
|
-
"@use-everywhere/core": "0.
|
|
173
|
+
"@use-everywhere/core": "1.0.0"
|
|
174
174
|
},
|
|
175
175
|
"peerDependencies": {
|
|
176
176
|
"react": ">=18",
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
"@testing-library/react": "^16.3.2",
|
|
188
188
|
"@types/react": "^19.2.17",
|
|
189
189
|
"@types/react-dom": "^19.2.3",
|
|
190
|
-
"@vitest/coverage-v8": "^4.1.
|
|
190
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
191
191
|
"happy-dom": "^20.11.15",
|
|
192
192
|
"publint": "^0.3.24",
|
|
193
193
|
"react": "^19.2.8",
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
"size-limit": "^13.0.3",
|
|
196
196
|
"tsup": "^8.5.1",
|
|
197
197
|
"typescript": "^6.0.3",
|
|
198
|
-
"vitest": "^4.1.
|
|
198
|
+
"vitest": "^4.1.11"
|
|
199
199
|
},
|
|
200
200
|
"typesVersions": {
|
|
201
201
|
"*": {
|