@use-everywhere/core 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +176 -95
- package/dist/index.d.cts +16 -42
- package/dist/index.d.ts +16 -42
- package/dist/index.js +173 -90
- package/dist/testing.cjs +84 -0
- package/dist/testing.d.cts +30 -0
- package/dist/testing.d.ts +30 -0
- package/dist/testing.js +56 -0
- package/dist/transport.types-tQ1cu6Xm.d.cts +12 -0
- package/dist/transport.types-tQ1cu6Xm.d.ts +12 -0
- package/package.json +31 -14
package/dist/index.cjs
CHANGED
|
@@ -18,14 +18,12 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
20
|
// src/index.ts
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
23
|
BroadcastChannelTransport: () => BroadcastChannelTransport,
|
|
24
24
|
CID_PARAM: () => CID_PARAM,
|
|
25
25
|
DEFAULT_NAME: () => DEFAULT_NAME,
|
|
26
26
|
HandshakeTimeoutError: () => HandshakeTimeoutError,
|
|
27
|
-
MemoryHub: () => MemoryHub,
|
|
28
|
-
MemoryTransport: () => MemoryTransport,
|
|
29
27
|
NoopTransport: () => NoopTransport,
|
|
30
28
|
WindowClosedError: () => WindowClosedError,
|
|
31
29
|
connectToOpener: () => connectToOpener,
|
|
@@ -44,7 +42,7 @@ __export(index_exports, {
|
|
|
44
42
|
sessionStorageAdapter: () => sessionStorageAdapter,
|
|
45
43
|
webStorageAdapter: () => webStorageAdapter
|
|
46
44
|
});
|
|
47
|
-
module.exports = __toCommonJS(
|
|
45
|
+
module.exports = __toCommonJS(src_exports);
|
|
48
46
|
|
|
49
47
|
// src/defaults.ts
|
|
50
48
|
var DEFAULT_NAME = "use-everywhere";
|
|
@@ -55,7 +53,13 @@ function emitBusEvent(name, direction, wire) {
|
|
|
55
53
|
const set = observers.get(name);
|
|
56
54
|
if (!set) return;
|
|
57
55
|
const event = { name, direction, wire };
|
|
58
|
-
for (const fn of set)
|
|
56
|
+
for (const fn of set) {
|
|
57
|
+
try {
|
|
58
|
+
fn(event);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(`[use-everywhere] a bus observer for "${name}" threw`, error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
59
63
|
}
|
|
60
64
|
function observeBus(name, fn) {
|
|
61
65
|
let set = observers.get(name);
|
|
@@ -80,12 +84,41 @@ function enableDebug(options = {}) {
|
|
|
80
84
|
});
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
// src/dev.ts
|
|
88
|
+
var inDev = false;
|
|
89
|
+
try {
|
|
90
|
+
inDev = process.env.NODE_ENV !== "production";
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
var warned = /* @__PURE__ */ new Set();
|
|
94
|
+
function devWarn(message) {
|
|
95
|
+
if (!inDev || warned.has(message)) return;
|
|
96
|
+
warned.add(message);
|
|
97
|
+
console.warn(message);
|
|
98
|
+
}
|
|
99
|
+
|
|
83
100
|
// src/ids.ts
|
|
101
|
+
function randomHex(bytes) {
|
|
102
|
+
const crypto = globalThis.crypto;
|
|
103
|
+
if (crypto?.getRandomValues) {
|
|
104
|
+
const buf = crypto.getRandomValues(new Uint8Array(bytes));
|
|
105
|
+
let out2 = "";
|
|
106
|
+
for (const b of buf) out2 += b.toString(16).padStart(2, "0");
|
|
107
|
+
return out2;
|
|
108
|
+
}
|
|
109
|
+
devWarn(
|
|
110
|
+
"[use-everywhere] crypto.getRandomValues is unavailable; falling back to Math.random for ids. Cross-origin window nonces are not cryptographically strong in this environment."
|
|
111
|
+
);
|
|
112
|
+
let out = "";
|
|
113
|
+
while (out.length < bytes * 2)
|
|
114
|
+
out += Math.floor(Math.random() * 256).toString(16).padStart(2, "0");
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
84
117
|
function newClientId() {
|
|
85
|
-
return
|
|
118
|
+
return randomHex(8);
|
|
86
119
|
}
|
|
87
120
|
function newMsgId() {
|
|
88
|
-
return
|
|
121
|
+
return randomHex(16);
|
|
89
122
|
}
|
|
90
123
|
|
|
91
124
|
// src/transport/broadcast-channel-transport.ts
|
|
@@ -165,12 +198,20 @@ function createBus(name, options, onShutdown) {
|
|
|
165
198
|
options.heartbeatMs ?? 2e3
|
|
166
199
|
);
|
|
167
200
|
const sayBye = () => post({ v: 1, scope: "presence", type: "bye", clientId, kind });
|
|
201
|
+
const onPageShow = (event) => {
|
|
202
|
+
if (!event.persisted) return;
|
|
203
|
+
post({ v: 1, scope: "presence", type: "hello", clientId, kind });
|
|
204
|
+
};
|
|
168
205
|
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
169
|
-
if (hasWindow)
|
|
206
|
+
if (hasWindow) {
|
|
207
|
+
addEventListener("pagehide", sayBye);
|
|
208
|
+
addEventListener("pageshow", onPageShow);
|
|
209
|
+
}
|
|
170
210
|
return {
|
|
171
211
|
name,
|
|
172
212
|
clientId,
|
|
173
213
|
kind,
|
|
214
|
+
heartbeatMs: options.heartbeatMs ?? 2e3,
|
|
174
215
|
post,
|
|
175
216
|
subscribe(fn) {
|
|
176
217
|
listeners.add(fn);
|
|
@@ -180,12 +221,16 @@ function createBus(name, options, onShutdown) {
|
|
|
180
221
|
refs++;
|
|
181
222
|
},
|
|
182
223
|
release() {
|
|
224
|
+
if (closed) return;
|
|
183
225
|
refs--;
|
|
184
226
|
if (refs > 0) return;
|
|
185
227
|
sayBye();
|
|
186
228
|
closed = true;
|
|
187
229
|
clearInterval(heartbeat);
|
|
188
|
-
if (hasWindow)
|
|
230
|
+
if (hasWindow) {
|
|
231
|
+
removeEventListener("pagehide", sayBye);
|
|
232
|
+
removeEventListener("pageshow", onPageShow);
|
|
233
|
+
}
|
|
189
234
|
unsubscribe();
|
|
190
235
|
transport.close();
|
|
191
236
|
listeners.clear();
|
|
@@ -207,6 +252,15 @@ function getBus(name, options = {}) {
|
|
|
207
252
|
if (!bus) {
|
|
208
253
|
bus = createBus(name, options, () => registry.delete(name));
|
|
209
254
|
registry.set(name, bus);
|
|
255
|
+
} else {
|
|
256
|
+
if (options.heartbeatMs !== void 0 && options.heartbeatMs !== bus.heartbeatMs) {
|
|
257
|
+
devWarn(
|
|
258
|
+
`[use-everywhere] bus "${name}": heartbeatMs ignored \u2014 the first creator fixes bus options`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
if (options.kind !== void 0 && options.kind !== bus.kind) {
|
|
262
|
+
devWarn(`[use-everywhere] bus "${name}": kind ignored \u2014 the first creator fixes bus options`);
|
|
263
|
+
}
|
|
210
264
|
}
|
|
211
265
|
bus.acquire();
|
|
212
266
|
return bus;
|
|
@@ -223,6 +277,7 @@ function createChannel(name, options = {}) {
|
|
|
223
277
|
const meta = { clientId: wire.clientId, kind: wire.kind, self: false };
|
|
224
278
|
for (const fn of set) fn(wire.payload, meta);
|
|
225
279
|
});
|
|
280
|
+
let closed = false;
|
|
226
281
|
return {
|
|
227
282
|
name,
|
|
228
283
|
clientId: bus.clientId,
|
|
@@ -247,6 +302,8 @@ function createChannel(name, options = {}) {
|
|
|
247
302
|
return () => set.delete(handler);
|
|
248
303
|
},
|
|
249
304
|
close() {
|
|
305
|
+
if (closed) return;
|
|
306
|
+
closed = true;
|
|
250
307
|
unsubscribe();
|
|
251
308
|
handlers.clear();
|
|
252
309
|
bus.release();
|
|
@@ -260,25 +317,48 @@ function newer(a, b) {
|
|
|
260
317
|
}
|
|
261
318
|
|
|
262
319
|
// src/dev-freeze.ts
|
|
263
|
-
var
|
|
320
|
+
var inDev2 = false;
|
|
264
321
|
try {
|
|
265
|
-
|
|
322
|
+
inDev2 = process.env.NODE_ENV !== "production";
|
|
266
323
|
} catch {
|
|
267
324
|
}
|
|
268
325
|
function deepFreeze(value) {
|
|
269
326
|
if (value === null || typeof value !== "object" || Object.isFrozen(value)) return;
|
|
327
|
+
if (ArrayBuffer.isView(value)) return;
|
|
270
328
|
Object.freeze(value);
|
|
329
|
+
if (value instanceof Map) {
|
|
330
|
+
for (const [k, v] of value) {
|
|
331
|
+
deepFreeze(k);
|
|
332
|
+
deepFreeze(v);
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (value instanceof Set) {
|
|
337
|
+
for (const entry of value) deepFreeze(entry);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
271
340
|
for (const key of Object.keys(value)) {
|
|
272
341
|
deepFreeze(value[key]);
|
|
273
342
|
}
|
|
274
343
|
}
|
|
275
344
|
function freezeShared(value) {
|
|
276
|
-
if (
|
|
345
|
+
if (inDev2) deepFreeze(value);
|
|
277
346
|
return value;
|
|
278
347
|
}
|
|
279
348
|
|
|
280
349
|
// src/shared-store.ts
|
|
350
|
+
var liveStores = /* @__PURE__ */ new Map();
|
|
281
351
|
function createSharedStore(name, initial, options = {}) {
|
|
352
|
+
const onSharedBus = !options.transport;
|
|
353
|
+
if (onSharedBus) {
|
|
354
|
+
const live = liveStores.get(name) ?? 0;
|
|
355
|
+
if (live > 0) {
|
|
356
|
+
devWarn(
|
|
357
|
+
`[use-everywhere] second shared store for "${name}" in this tab \u2014 stores on one page never hear each other and will diverge. Reuse one per name.`
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
liveStores.set(name, live + 1);
|
|
361
|
+
}
|
|
282
362
|
const bus = getBus(name, options);
|
|
283
363
|
const clientId = bus.clientId;
|
|
284
364
|
const accept = options.accept;
|
|
@@ -332,18 +412,25 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
332
412
|
});
|
|
333
413
|
function setKey(key, value) {
|
|
334
414
|
const version = [(versions[key]?.[0] ?? 0) + 1, clientId];
|
|
415
|
+
try {
|
|
416
|
+
bus.post({
|
|
417
|
+
v: 1,
|
|
418
|
+
scope: "state",
|
|
419
|
+
type: "patch",
|
|
420
|
+
key,
|
|
421
|
+
value,
|
|
422
|
+
version,
|
|
423
|
+
clientId,
|
|
424
|
+
kind: bus.kind
|
|
425
|
+
});
|
|
426
|
+
} catch (error) {
|
|
427
|
+
throw new TypeError(
|
|
428
|
+
`use-everywhere: the value for key "${key}" cannot cross the wire (structured clone failed); the write was not applied. ${String(error)}`,
|
|
429
|
+
{ cause: error }
|
|
430
|
+
);
|
|
431
|
+
}
|
|
335
432
|
versions[key] = version;
|
|
336
433
|
state[key] = freezeShared(value);
|
|
337
|
-
bus.post({
|
|
338
|
-
v: 1,
|
|
339
|
-
scope: "state",
|
|
340
|
-
type: "patch",
|
|
341
|
-
key,
|
|
342
|
-
value,
|
|
343
|
-
version,
|
|
344
|
-
clientId,
|
|
345
|
-
kind: bus.kind
|
|
346
|
-
});
|
|
347
434
|
notify(key, value, { clientId, kind: bus.kind, self: true });
|
|
348
435
|
}
|
|
349
436
|
const proxy = new Proxy(state, {
|
|
@@ -406,7 +493,15 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
406
493
|
addEventListener("pagehide", flushPersist);
|
|
407
494
|
}
|
|
408
495
|
}
|
|
409
|
-
bus.post({ v: 1, scope: "state", type: "hello", clientId, kind: bus.kind });
|
|
496
|
+
const sayHello = () => bus.post({ v: 1, scope: "state", type: "hello", clientId, kind: bus.kind });
|
|
497
|
+
const onPageShow = (event) => {
|
|
498
|
+
if (!event.persisted) return;
|
|
499
|
+
sayHello();
|
|
500
|
+
};
|
|
501
|
+
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
502
|
+
if (hasWindow) addEventListener("pageshow", onPageShow);
|
|
503
|
+
sayHello();
|
|
504
|
+
let storeClosed = false;
|
|
410
505
|
return {
|
|
411
506
|
clientId,
|
|
412
507
|
state: proxy,
|
|
@@ -427,16 +522,27 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
427
522
|
keyListeners.set(key, set);
|
|
428
523
|
}
|
|
429
524
|
set.add(fn);
|
|
430
|
-
return () =>
|
|
525
|
+
return () => {
|
|
526
|
+
set.delete(fn);
|
|
527
|
+
if (set.size === 0) keyListeners.delete(key);
|
|
528
|
+
};
|
|
431
529
|
},
|
|
432
530
|
registerKey(key, initialValue) {
|
|
433
531
|
if (key in versions) return;
|
|
434
532
|
versions[key] = [0, clientId];
|
|
435
|
-
state[key] = initialValue;
|
|
533
|
+
state[key] = freezeShared(initialValue);
|
|
436
534
|
snapshot = Object.freeze({ ...state });
|
|
437
535
|
versionsSnapshot = Object.freeze({ ...versions });
|
|
438
536
|
},
|
|
439
537
|
close() {
|
|
538
|
+
if (storeClosed) return;
|
|
539
|
+
storeClosed = true;
|
|
540
|
+
if (onSharedBus) {
|
|
541
|
+
const live = (liveStores.get(name) ?? 1) - 1;
|
|
542
|
+
if (live > 0) liveStores.set(name, live);
|
|
543
|
+
else liveStores.delete(name);
|
|
544
|
+
}
|
|
545
|
+
if (hasWindow) removeEventListener("pageshow", onPageShow);
|
|
440
546
|
if (flushPersist) {
|
|
441
547
|
flushPersist();
|
|
442
548
|
if (typeof document !== "undefined" && typeof removeEventListener === "function") {
|
|
@@ -485,6 +591,7 @@ function createPresence(name, options = {}) {
|
|
|
485
591
|
},
|
|
486
592
|
Math.max(500, Math.floor(pruneAfterMs / 2))
|
|
487
593
|
);
|
|
594
|
+
let closed = false;
|
|
488
595
|
return {
|
|
489
596
|
clientId: bus.clientId,
|
|
490
597
|
getPeers: () => snapshot,
|
|
@@ -493,6 +600,8 @@ function createPresence(name, options = {}) {
|
|
|
493
600
|
return () => listeners.delete(fn);
|
|
494
601
|
},
|
|
495
602
|
close() {
|
|
603
|
+
if (closed) return;
|
|
604
|
+
closed = true;
|
|
496
605
|
clearInterval(prune);
|
|
497
606
|
unsubscribe();
|
|
498
607
|
listeners.clear();
|
|
@@ -581,10 +690,19 @@ function createLeader(name, options = {}) {
|
|
|
581
690
|
bus.post({ v: 1, scope: "leader", type: "resign", term: resigning, clientId, kind: bus.kind });
|
|
582
691
|
armLease(leaseMs);
|
|
583
692
|
}
|
|
693
|
+
const sayHello = () => bus.post({ v: 1, scope: "leader", type: "hello", clientId, kind: bus.kind });
|
|
584
694
|
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
585
695
|
const onPageHide = () => resign();
|
|
586
|
-
|
|
587
|
-
|
|
696
|
+
const onPageShow = (event) => {
|
|
697
|
+
if (!event.persisted) return;
|
|
698
|
+
sayHello();
|
|
699
|
+
armLease(heartbeatMs);
|
|
700
|
+
};
|
|
701
|
+
if (hasWindow) {
|
|
702
|
+
addEventListener("pagehide", onPageHide);
|
|
703
|
+
addEventListener("pageshow", onPageShow);
|
|
704
|
+
}
|
|
705
|
+
sayHello();
|
|
588
706
|
armLease(heartbeatMs);
|
|
589
707
|
return {
|
|
590
708
|
clientId,
|
|
@@ -604,11 +722,15 @@ function createLeader(name, options = {}) {
|
|
|
604
722
|
if (leaderId === null) armLease(0);
|
|
605
723
|
},
|
|
606
724
|
close() {
|
|
725
|
+
if (closed) return;
|
|
607
726
|
closed = true;
|
|
608
727
|
resign();
|
|
609
728
|
clearInterval(beat);
|
|
610
729
|
clearTimeout(lease);
|
|
611
|
-
if (hasWindow)
|
|
730
|
+
if (hasWindow) {
|
|
731
|
+
removeEventListener("pagehide", onPageHide);
|
|
732
|
+
removeEventListener("pageshow", onPageShow);
|
|
733
|
+
}
|
|
612
734
|
unsubscribe();
|
|
613
735
|
listeners.clear();
|
|
614
736
|
bus.release();
|
|
@@ -622,44 +744,54 @@ function isPersisted(value) {
|
|
|
622
744
|
const candidate = value;
|
|
623
745
|
return candidate.v === 1 && typeof candidate.state === "object" && typeof candidate.versions === "object";
|
|
624
746
|
}
|
|
625
|
-
function webStorageAdapter(storage, key) {
|
|
626
|
-
const
|
|
747
|
+
function webStorageAdapter(storage, key, options = {}) {
|
|
748
|
+
const report = (error, operation) => {
|
|
627
749
|
try {
|
|
628
|
-
|
|
750
|
+
options.onError?.(error, operation);
|
|
629
751
|
} catch {
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
const resolve = (operation) => {
|
|
755
|
+
try {
|
|
756
|
+
return typeof storage === "function" ? storage() : storage;
|
|
757
|
+
} catch (error) {
|
|
758
|
+
report(error, operation);
|
|
630
759
|
return void 0;
|
|
631
760
|
}
|
|
632
761
|
};
|
|
633
762
|
return {
|
|
634
763
|
read() {
|
|
635
764
|
try {
|
|
636
|
-
const raw = resolve()?.getItem(key);
|
|
765
|
+
const raw = resolve("read")?.getItem(key);
|
|
637
766
|
if (!raw) return void 0;
|
|
638
767
|
const parsed = JSON.parse(raw);
|
|
639
768
|
return isPersisted(parsed) ? parsed : void 0;
|
|
640
|
-
} catch {
|
|
769
|
+
} catch (error) {
|
|
770
|
+
report(error, "read");
|
|
641
771
|
return void 0;
|
|
642
772
|
}
|
|
643
773
|
},
|
|
644
774
|
write(snapshot) {
|
|
645
775
|
try {
|
|
646
|
-
resolve()?.setItem(key, JSON.stringify(snapshot));
|
|
647
|
-
} catch {
|
|
776
|
+
resolve("write")?.setItem(key, JSON.stringify(snapshot));
|
|
777
|
+
} catch (error) {
|
|
778
|
+
report(error, "write");
|
|
648
779
|
}
|
|
649
780
|
},
|
|
650
781
|
remove() {
|
|
651
782
|
try {
|
|
652
|
-
resolve()?.removeItem(key);
|
|
653
|
-
} catch {
|
|
783
|
+
resolve("remove")?.removeItem(key);
|
|
784
|
+
} catch (error) {
|
|
785
|
+
report(error, "remove");
|
|
654
786
|
}
|
|
655
787
|
}
|
|
656
788
|
};
|
|
657
789
|
}
|
|
658
|
-
function localStorageAdapter(key) {
|
|
659
|
-
return webStorageAdapter(() => globalThis.localStorage, key);
|
|
790
|
+
function localStorageAdapter(key, options) {
|
|
791
|
+
return webStorageAdapter(() => globalThis.localStorage, key, options);
|
|
660
792
|
}
|
|
661
|
-
function sessionStorageAdapter(key) {
|
|
662
|
-
return webStorageAdapter(() => globalThis.sessionStorage, key);
|
|
793
|
+
function sessionStorageAdapter(key, options) {
|
|
794
|
+
return webStorageAdapter(() => globalThis.sessionStorage, key, options);
|
|
663
795
|
}
|
|
664
796
|
|
|
665
797
|
// src/errors/handshake-timeout-error.ts
|
|
@@ -786,6 +918,7 @@ function openWindow(url, options) {
|
|
|
786
918
|
}, 400);
|
|
787
919
|
const readyTimer = setTimeout(() => {
|
|
788
920
|
if (isReady || closedSettled) return;
|
|
921
|
+
localWindow.removeEventListener("message", onMessage);
|
|
789
922
|
const err = new HandshakeTimeoutError();
|
|
790
923
|
rejectReady(err);
|
|
791
924
|
if (!resultSettled) {
|
|
@@ -839,6 +972,7 @@ function connectToOpener(options) {
|
|
|
839
972
|
markHandled(ready);
|
|
840
973
|
const onMessage = (event) => {
|
|
841
974
|
if (!allowAnyOrigin && event.origin !== peerOrigin) return;
|
|
975
|
+
if (event.source !== opener) return;
|
|
842
976
|
if (!isWindowWire(event.data, cid)) return;
|
|
843
977
|
const wire = event.data;
|
|
844
978
|
if (wire.t === "ready-ack") {
|
|
@@ -894,65 +1028,12 @@ function connectToOpener(options) {
|
|
|
894
1028
|
}
|
|
895
1029
|
};
|
|
896
1030
|
}
|
|
897
|
-
|
|
898
|
-
// src/transport/memory-transport.ts
|
|
899
|
-
var MemoryTransport = class {
|
|
900
|
-
constructor(hub) {
|
|
901
|
-
this.hub = hub;
|
|
902
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
903
|
-
this.closed = false;
|
|
904
|
-
}
|
|
905
|
-
post(data) {
|
|
906
|
-
if (this.closed) return;
|
|
907
|
-
this.hub.broadcast(this, data);
|
|
908
|
-
}
|
|
909
|
-
subscribe(listener) {
|
|
910
|
-
this.listeners.add(listener);
|
|
911
|
-
return () => this.listeners.delete(listener);
|
|
912
|
-
}
|
|
913
|
-
close() {
|
|
914
|
-
this.closed = true;
|
|
915
|
-
this.listeners.clear();
|
|
916
|
-
this.hub.disconnect(this);
|
|
917
|
-
}
|
|
918
|
-
/** @internal */
|
|
919
|
-
deliver(data) {
|
|
920
|
-
if (this.closed) return;
|
|
921
|
-
for (const listener of this.listeners) listener(data);
|
|
922
|
-
}
|
|
923
|
-
};
|
|
924
|
-
|
|
925
|
-
// src/transport/memory-hub.ts
|
|
926
|
-
var MemoryHub = class {
|
|
927
|
-
constructor() {
|
|
928
|
-
this.transports = /* @__PURE__ */ new Set();
|
|
929
|
-
}
|
|
930
|
-
connect() {
|
|
931
|
-
const transport = new MemoryTransport(this);
|
|
932
|
-
this.transports.add(transport);
|
|
933
|
-
return transport;
|
|
934
|
-
}
|
|
935
|
-
/** @internal */
|
|
936
|
-
broadcast(from, data) {
|
|
937
|
-
for (const transport of this.transports) {
|
|
938
|
-
if (transport !== from) {
|
|
939
|
-
queueMicrotask(() => transport.deliver(data));
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
/** @internal */
|
|
944
|
-
disconnect(transport) {
|
|
945
|
-
this.transports.delete(transport);
|
|
946
|
-
}
|
|
947
|
-
};
|
|
948
1031
|
// Annotate the CommonJS export names for ESM import in node:
|
|
949
1032
|
0 && (module.exports = {
|
|
950
1033
|
BroadcastChannelTransport,
|
|
951
1034
|
CID_PARAM,
|
|
952
1035
|
DEFAULT_NAME,
|
|
953
1036
|
HandshakeTimeoutError,
|
|
954
|
-
MemoryHub,
|
|
955
|
-
MemoryTransport,
|
|
956
1037
|
NoopTransport,
|
|
957
1038
|
WindowClosedError,
|
|
958
1039
|
connectToOpener,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Minimal message bus. Implementations: BroadcastChannelTransport (same-origin),
|
|
3
|
-
* NoopTransport (SSR / local-only), MemoryTransport (tests). A transport never
|
|
4
|
-
* echoes a client's own posts back to it.
|
|
5
|
-
*/
|
|
6
|
-
interface Transport {
|
|
7
|
-
post(data: unknown): void;
|
|
8
|
-
subscribe(listener: (data: unknown) => void): () => void;
|
|
9
|
-
close(): void;
|
|
10
|
-
}
|
|
1
|
+
import { T as Transport } from './transport.types-tQ1cu6Xm.cjs';
|
|
11
2
|
|
|
12
3
|
type MessageMap = Record<string, unknown>;
|
|
13
4
|
type PeerKind = 'tab' | 'worker' | (string & {});
|
|
@@ -230,6 +221,15 @@ interface Leader {
|
|
|
230
221
|
declare function createLeader(name: string, options?: LeaderOptions): Leader;
|
|
231
222
|
|
|
232
223
|
type StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
224
|
+
interface WebStorageAdapterOptions {
|
|
225
|
+
/**
|
|
226
|
+
* Called when a storage operation fails: blocked storage, corrupt JSON, a
|
|
227
|
+
* full quota. Persistence stays best-effort either way — this is the
|
|
228
|
+
* observability seam (telemetry, a "your changes may not be saved" notice),
|
|
229
|
+
* not a recovery path. Errors thrown by the callback itself are swallowed.
|
|
230
|
+
*/
|
|
231
|
+
onError?: (error: unknown, operation: 'read' | 'write' | 'remove') => void;
|
|
232
|
+
}
|
|
233
233
|
/**
|
|
234
234
|
* Persist to any Storage-shaped thing.
|
|
235
235
|
*
|
|
@@ -240,13 +240,14 @@ type StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
|
240
240
|
* Behind a thunk, every access happens inside one.
|
|
241
241
|
*
|
|
242
242
|
* Blocked storage, corrupt JSON, a foreign schema, or a full quota all degrade
|
|
243
|
-
* to a silent no-op
|
|
243
|
+
* to a silent no-op — persistence is best-effort; it must never break the
|
|
244
|
+
* store. Pass `onError` to observe the failures anyway.
|
|
244
245
|
*/
|
|
245
|
-
declare function webStorageAdapter(storage: StorageLike | (() => StorageLike | undefined), key: string): PersistAdapter;
|
|
246
|
+
declare function webStorageAdapter(storage: StorageLike | (() => StorageLike | undefined), key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
246
247
|
/** Survives closing every tab. */
|
|
247
|
-
declare function localStorageAdapter(key: string): PersistAdapter;
|
|
248
|
+
declare function localStorageAdapter(key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
248
249
|
/** Survives reloads, but dies with the tab. */
|
|
249
|
-
declare function sessionStorageAdapter(key: string): PersistAdapter;
|
|
250
|
+
declare function sessionStorageAdapter(key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
250
251
|
|
|
251
252
|
interface MessageEventLike {
|
|
252
253
|
data: unknown;
|
|
@@ -415,31 +416,4 @@ declare function isBroadcastChannelAvailable(): boolean;
|
|
|
415
416
|
/** Default factory: real BroadcastChannel when available, otherwise a local no-op. */
|
|
416
417
|
declare function defaultTransport(name: string): Transport;
|
|
417
418
|
|
|
418
|
-
|
|
419
|
-
declare class MemoryTransport implements Transport {
|
|
420
|
-
private hub;
|
|
421
|
-
private listeners;
|
|
422
|
-
private closed;
|
|
423
|
-
constructor(hub: MemoryHub);
|
|
424
|
-
post(data: unknown): void;
|
|
425
|
-
subscribe(listener: (data: unknown) => void): () => void;
|
|
426
|
-
close(): void;
|
|
427
|
-
/** @internal */
|
|
428
|
-
deliver(data: unknown): void;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* In-memory hub for tests: N transports attached to one hub, each post is
|
|
433
|
-
* delivered to every *other* transport on a microtask (mirrors BroadcastChannel's
|
|
434
|
-
* async, no-self-echo delivery).
|
|
435
|
-
*/
|
|
436
|
-
declare class MemoryHub {
|
|
437
|
-
private transports;
|
|
438
|
-
connect(): MemoryTransport;
|
|
439
|
-
/** @internal */
|
|
440
|
-
broadcast(from: MemoryTransport, data: unknown): void;
|
|
441
|
-
/** @internal */
|
|
442
|
-
disconnect(transport: MemoryTransport): void;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
export { BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type Leader, type LeaderOptions, type LeaderSnapshot, MemoryHub, MemoryTransport, type MessageEventLike, type MessageMap, type MessageMeta, NoopTransport, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type Presence, type PresenceOptions, type SharedStore, type SharedStoreOptions, type StorageLike, type Transport, type Version, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
|
|
419
|
+
export { BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type Leader, type LeaderOptions, type LeaderSnapshot, type MessageEventLike, type MessageMap, type MessageMeta, NoopTransport, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type Presence, type PresenceOptions, type SharedStore, type SharedStoreOptions, type StorageLike, Transport, type Version, type WebStorageAdapterOptions, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Minimal message bus. Implementations: BroadcastChannelTransport (same-origin),
|
|
3
|
-
* NoopTransport (SSR / local-only), MemoryTransport (tests). A transport never
|
|
4
|
-
* echoes a client's own posts back to it.
|
|
5
|
-
*/
|
|
6
|
-
interface Transport {
|
|
7
|
-
post(data: unknown): void;
|
|
8
|
-
subscribe(listener: (data: unknown) => void): () => void;
|
|
9
|
-
close(): void;
|
|
10
|
-
}
|
|
1
|
+
import { T as Transport } from './transport.types-tQ1cu6Xm.js';
|
|
11
2
|
|
|
12
3
|
type MessageMap = Record<string, unknown>;
|
|
13
4
|
type PeerKind = 'tab' | 'worker' | (string & {});
|
|
@@ -230,6 +221,15 @@ interface Leader {
|
|
|
230
221
|
declare function createLeader(name: string, options?: LeaderOptions): Leader;
|
|
231
222
|
|
|
232
223
|
type StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
224
|
+
interface WebStorageAdapterOptions {
|
|
225
|
+
/**
|
|
226
|
+
* Called when a storage operation fails: blocked storage, corrupt JSON, a
|
|
227
|
+
* full quota. Persistence stays best-effort either way — this is the
|
|
228
|
+
* observability seam (telemetry, a "your changes may not be saved" notice),
|
|
229
|
+
* not a recovery path. Errors thrown by the callback itself are swallowed.
|
|
230
|
+
*/
|
|
231
|
+
onError?: (error: unknown, operation: 'read' | 'write' | 'remove') => void;
|
|
232
|
+
}
|
|
233
233
|
/**
|
|
234
234
|
* Persist to any Storage-shaped thing.
|
|
235
235
|
*
|
|
@@ -240,13 +240,14 @@ type StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
|
|
|
240
240
|
* Behind a thunk, every access happens inside one.
|
|
241
241
|
*
|
|
242
242
|
* Blocked storage, corrupt JSON, a foreign schema, or a full quota all degrade
|
|
243
|
-
* to a silent no-op
|
|
243
|
+
* to a silent no-op — persistence is best-effort; it must never break the
|
|
244
|
+
* store. Pass `onError` to observe the failures anyway.
|
|
244
245
|
*/
|
|
245
|
-
declare function webStorageAdapter(storage: StorageLike | (() => StorageLike | undefined), key: string): PersistAdapter;
|
|
246
|
+
declare function webStorageAdapter(storage: StorageLike | (() => StorageLike | undefined), key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
246
247
|
/** Survives closing every tab. */
|
|
247
|
-
declare function localStorageAdapter(key: string): PersistAdapter;
|
|
248
|
+
declare function localStorageAdapter(key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
248
249
|
/** Survives reloads, but dies with the tab. */
|
|
249
|
-
declare function sessionStorageAdapter(key: string): PersistAdapter;
|
|
250
|
+
declare function sessionStorageAdapter(key: string, options?: WebStorageAdapterOptions): PersistAdapter;
|
|
250
251
|
|
|
251
252
|
interface MessageEventLike {
|
|
252
253
|
data: unknown;
|
|
@@ -415,31 +416,4 @@ declare function isBroadcastChannelAvailable(): boolean;
|
|
|
415
416
|
/** Default factory: real BroadcastChannel when available, otherwise a local no-op. */
|
|
416
417
|
declare function defaultTransport(name: string): Transport;
|
|
417
418
|
|
|
418
|
-
|
|
419
|
-
declare class MemoryTransport implements Transport {
|
|
420
|
-
private hub;
|
|
421
|
-
private listeners;
|
|
422
|
-
private closed;
|
|
423
|
-
constructor(hub: MemoryHub);
|
|
424
|
-
post(data: unknown): void;
|
|
425
|
-
subscribe(listener: (data: unknown) => void): () => void;
|
|
426
|
-
close(): void;
|
|
427
|
-
/** @internal */
|
|
428
|
-
deliver(data: unknown): void;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* In-memory hub for tests: N transports attached to one hub, each post is
|
|
433
|
-
* delivered to every *other* transport on a microtask (mirrors BroadcastChannel's
|
|
434
|
-
* async, no-self-echo delivery).
|
|
435
|
-
*/
|
|
436
|
-
declare class MemoryHub {
|
|
437
|
-
private transports;
|
|
438
|
-
connect(): MemoryTransport;
|
|
439
|
-
/** @internal */
|
|
440
|
-
broadcast(from: MemoryTransport, data: unknown): void;
|
|
441
|
-
/** @internal */
|
|
442
|
-
disconnect(transport: MemoryTransport): void;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
export { BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type Leader, type LeaderOptions, type LeaderSnapshot, MemoryHub, MemoryTransport, type MessageEventLike, type MessageMap, type MessageMeta, NoopTransport, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type Presence, type PresenceOptions, type SharedStore, type SharedStoreOptions, type StorageLike, type Transport, type Version, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
|
|
419
|
+
export { BroadcastChannelTransport, type BusEvent, type BusObserver, type BusWire, CID_PARAM, type Channel, type CommonOptions, type ConnectToOpenerOptions, DEFAULT_NAME, type DebugOptions, HandshakeTimeoutError, type Leader, type LeaderOptions, type LeaderSnapshot, type MessageEventLike, type MessageMap, type MessageMeta, NoopTransport, type OpenWindowOptions, type OpenedWindow, type OpenerConnection, type Peer, type PeerKind, type PersistAdapter, type PersistOptions, type Persisted, type Presence, type PresenceOptions, type SharedStore, type SharedStoreOptions, type StorageLike, Transport, type Version, type WebStorageAdapterOptions, WindowClosedError, type WindowEventTarget, type WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedStore, defaultTransport, enableDebug, getBusNames, isBroadcastChannelAvailable, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter };
|