@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.js
CHANGED
|
@@ -7,7 +7,13 @@ function emitBusEvent(name, direction, wire) {
|
|
|
7
7
|
const set = observers.get(name);
|
|
8
8
|
if (!set) return;
|
|
9
9
|
const event = { name, direction, wire };
|
|
10
|
-
for (const fn of set)
|
|
10
|
+
for (const fn of set) {
|
|
11
|
+
try {
|
|
12
|
+
fn(event);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error(`[use-everywhere] a bus observer for "${name}" threw`, error);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
11
17
|
}
|
|
12
18
|
function observeBus(name, fn) {
|
|
13
19
|
let set = observers.get(name);
|
|
@@ -32,12 +38,41 @@ function enableDebug(options = {}) {
|
|
|
32
38
|
});
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
// src/dev.ts
|
|
42
|
+
var inDev = false;
|
|
43
|
+
try {
|
|
44
|
+
inDev = process.env.NODE_ENV !== "production";
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
var warned = /* @__PURE__ */ new Set();
|
|
48
|
+
function devWarn(message) {
|
|
49
|
+
if (!inDev || warned.has(message)) return;
|
|
50
|
+
warned.add(message);
|
|
51
|
+
console.warn(message);
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
// src/ids.ts
|
|
55
|
+
function randomHex(bytes) {
|
|
56
|
+
const crypto = globalThis.crypto;
|
|
57
|
+
if (crypto?.getRandomValues) {
|
|
58
|
+
const buf = crypto.getRandomValues(new Uint8Array(bytes));
|
|
59
|
+
let out2 = "";
|
|
60
|
+
for (const b of buf) out2 += b.toString(16).padStart(2, "0");
|
|
61
|
+
return out2;
|
|
62
|
+
}
|
|
63
|
+
devWarn(
|
|
64
|
+
"[use-everywhere] crypto.getRandomValues is unavailable; falling back to Math.random for ids. Cross-origin window nonces are not cryptographically strong in this environment."
|
|
65
|
+
);
|
|
66
|
+
let out = "";
|
|
67
|
+
while (out.length < bytes * 2)
|
|
68
|
+
out += Math.floor(Math.random() * 256).toString(16).padStart(2, "0");
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
36
71
|
function newClientId() {
|
|
37
|
-
return
|
|
72
|
+
return randomHex(8);
|
|
38
73
|
}
|
|
39
74
|
function newMsgId() {
|
|
40
|
-
return
|
|
75
|
+
return randomHex(16);
|
|
41
76
|
}
|
|
42
77
|
|
|
43
78
|
// src/transport/broadcast-channel-transport.ts
|
|
@@ -117,12 +152,20 @@ function createBus(name, options, onShutdown) {
|
|
|
117
152
|
options.heartbeatMs ?? 2e3
|
|
118
153
|
);
|
|
119
154
|
const sayBye = () => post({ v: 1, scope: "presence", type: "bye", clientId, kind });
|
|
155
|
+
const onPageShow = (event) => {
|
|
156
|
+
if (!event.persisted) return;
|
|
157
|
+
post({ v: 1, scope: "presence", type: "hello", clientId, kind });
|
|
158
|
+
};
|
|
120
159
|
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
121
|
-
if (hasWindow)
|
|
160
|
+
if (hasWindow) {
|
|
161
|
+
addEventListener("pagehide", sayBye);
|
|
162
|
+
addEventListener("pageshow", onPageShow);
|
|
163
|
+
}
|
|
122
164
|
return {
|
|
123
165
|
name,
|
|
124
166
|
clientId,
|
|
125
167
|
kind,
|
|
168
|
+
heartbeatMs: options.heartbeatMs ?? 2e3,
|
|
126
169
|
post,
|
|
127
170
|
subscribe(fn) {
|
|
128
171
|
listeners.add(fn);
|
|
@@ -132,12 +175,16 @@ function createBus(name, options, onShutdown) {
|
|
|
132
175
|
refs++;
|
|
133
176
|
},
|
|
134
177
|
release() {
|
|
178
|
+
if (closed) return;
|
|
135
179
|
refs--;
|
|
136
180
|
if (refs > 0) return;
|
|
137
181
|
sayBye();
|
|
138
182
|
closed = true;
|
|
139
183
|
clearInterval(heartbeat);
|
|
140
|
-
if (hasWindow)
|
|
184
|
+
if (hasWindow) {
|
|
185
|
+
removeEventListener("pagehide", sayBye);
|
|
186
|
+
removeEventListener("pageshow", onPageShow);
|
|
187
|
+
}
|
|
141
188
|
unsubscribe();
|
|
142
189
|
transport.close();
|
|
143
190
|
listeners.clear();
|
|
@@ -159,6 +206,15 @@ function getBus(name, options = {}) {
|
|
|
159
206
|
if (!bus) {
|
|
160
207
|
bus = createBus(name, options, () => registry.delete(name));
|
|
161
208
|
registry.set(name, bus);
|
|
209
|
+
} else {
|
|
210
|
+
if (options.heartbeatMs !== void 0 && options.heartbeatMs !== bus.heartbeatMs) {
|
|
211
|
+
devWarn(
|
|
212
|
+
`[use-everywhere] bus "${name}": heartbeatMs ignored \u2014 the first creator fixes bus options`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (options.kind !== void 0 && options.kind !== bus.kind) {
|
|
216
|
+
devWarn(`[use-everywhere] bus "${name}": kind ignored \u2014 the first creator fixes bus options`);
|
|
217
|
+
}
|
|
162
218
|
}
|
|
163
219
|
bus.acquire();
|
|
164
220
|
return bus;
|
|
@@ -175,6 +231,7 @@ function createChannel(name, options = {}) {
|
|
|
175
231
|
const meta = { clientId: wire.clientId, kind: wire.kind, self: false };
|
|
176
232
|
for (const fn of set) fn(wire.payload, meta);
|
|
177
233
|
});
|
|
234
|
+
let closed = false;
|
|
178
235
|
return {
|
|
179
236
|
name,
|
|
180
237
|
clientId: bus.clientId,
|
|
@@ -199,6 +256,8 @@ function createChannel(name, options = {}) {
|
|
|
199
256
|
return () => set.delete(handler);
|
|
200
257
|
},
|
|
201
258
|
close() {
|
|
259
|
+
if (closed) return;
|
|
260
|
+
closed = true;
|
|
202
261
|
unsubscribe();
|
|
203
262
|
handlers.clear();
|
|
204
263
|
bus.release();
|
|
@@ -212,25 +271,48 @@ function newer(a, b) {
|
|
|
212
271
|
}
|
|
213
272
|
|
|
214
273
|
// src/dev-freeze.ts
|
|
215
|
-
var
|
|
274
|
+
var inDev2 = false;
|
|
216
275
|
try {
|
|
217
|
-
|
|
276
|
+
inDev2 = process.env.NODE_ENV !== "production";
|
|
218
277
|
} catch {
|
|
219
278
|
}
|
|
220
279
|
function deepFreeze(value) {
|
|
221
280
|
if (value === null || typeof value !== "object" || Object.isFrozen(value)) return;
|
|
281
|
+
if (ArrayBuffer.isView(value)) return;
|
|
222
282
|
Object.freeze(value);
|
|
283
|
+
if (value instanceof Map) {
|
|
284
|
+
for (const [k, v] of value) {
|
|
285
|
+
deepFreeze(k);
|
|
286
|
+
deepFreeze(v);
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (value instanceof Set) {
|
|
291
|
+
for (const entry of value) deepFreeze(entry);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
223
294
|
for (const key of Object.keys(value)) {
|
|
224
295
|
deepFreeze(value[key]);
|
|
225
296
|
}
|
|
226
297
|
}
|
|
227
298
|
function freezeShared(value) {
|
|
228
|
-
if (
|
|
299
|
+
if (inDev2) deepFreeze(value);
|
|
229
300
|
return value;
|
|
230
301
|
}
|
|
231
302
|
|
|
232
303
|
// src/shared-store.ts
|
|
304
|
+
var liveStores = /* @__PURE__ */ new Map();
|
|
233
305
|
function createSharedStore(name, initial, options = {}) {
|
|
306
|
+
const onSharedBus = !options.transport;
|
|
307
|
+
if (onSharedBus) {
|
|
308
|
+
const live = liveStores.get(name) ?? 0;
|
|
309
|
+
if (live > 0) {
|
|
310
|
+
devWarn(
|
|
311
|
+
`[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.`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
liveStores.set(name, live + 1);
|
|
315
|
+
}
|
|
234
316
|
const bus = getBus(name, options);
|
|
235
317
|
const clientId = bus.clientId;
|
|
236
318
|
const accept = options.accept;
|
|
@@ -284,18 +366,25 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
284
366
|
});
|
|
285
367
|
function setKey(key, value) {
|
|
286
368
|
const version = [(versions[key]?.[0] ?? 0) + 1, clientId];
|
|
369
|
+
try {
|
|
370
|
+
bus.post({
|
|
371
|
+
v: 1,
|
|
372
|
+
scope: "state",
|
|
373
|
+
type: "patch",
|
|
374
|
+
key,
|
|
375
|
+
value,
|
|
376
|
+
version,
|
|
377
|
+
clientId,
|
|
378
|
+
kind: bus.kind
|
|
379
|
+
});
|
|
380
|
+
} catch (error) {
|
|
381
|
+
throw new TypeError(
|
|
382
|
+
`use-everywhere: the value for key "${key}" cannot cross the wire (structured clone failed); the write was not applied. ${String(error)}`,
|
|
383
|
+
{ cause: error }
|
|
384
|
+
);
|
|
385
|
+
}
|
|
287
386
|
versions[key] = version;
|
|
288
387
|
state[key] = freezeShared(value);
|
|
289
|
-
bus.post({
|
|
290
|
-
v: 1,
|
|
291
|
-
scope: "state",
|
|
292
|
-
type: "patch",
|
|
293
|
-
key,
|
|
294
|
-
value,
|
|
295
|
-
version,
|
|
296
|
-
clientId,
|
|
297
|
-
kind: bus.kind
|
|
298
|
-
});
|
|
299
388
|
notify(key, value, { clientId, kind: bus.kind, self: true });
|
|
300
389
|
}
|
|
301
390
|
const proxy = new Proxy(state, {
|
|
@@ -358,7 +447,15 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
358
447
|
addEventListener("pagehide", flushPersist);
|
|
359
448
|
}
|
|
360
449
|
}
|
|
361
|
-
bus.post({ v: 1, scope: "state", type: "hello", clientId, kind: bus.kind });
|
|
450
|
+
const sayHello = () => bus.post({ v: 1, scope: "state", type: "hello", clientId, kind: bus.kind });
|
|
451
|
+
const onPageShow = (event) => {
|
|
452
|
+
if (!event.persisted) return;
|
|
453
|
+
sayHello();
|
|
454
|
+
};
|
|
455
|
+
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
456
|
+
if (hasWindow) addEventListener("pageshow", onPageShow);
|
|
457
|
+
sayHello();
|
|
458
|
+
let storeClosed = false;
|
|
362
459
|
return {
|
|
363
460
|
clientId,
|
|
364
461
|
state: proxy,
|
|
@@ -379,16 +476,27 @@ function createSharedStore(name, initial, options = {}) {
|
|
|
379
476
|
keyListeners.set(key, set);
|
|
380
477
|
}
|
|
381
478
|
set.add(fn);
|
|
382
|
-
return () =>
|
|
479
|
+
return () => {
|
|
480
|
+
set.delete(fn);
|
|
481
|
+
if (set.size === 0) keyListeners.delete(key);
|
|
482
|
+
};
|
|
383
483
|
},
|
|
384
484
|
registerKey(key, initialValue) {
|
|
385
485
|
if (key in versions) return;
|
|
386
486
|
versions[key] = [0, clientId];
|
|
387
|
-
state[key] = initialValue;
|
|
487
|
+
state[key] = freezeShared(initialValue);
|
|
388
488
|
snapshot = Object.freeze({ ...state });
|
|
389
489
|
versionsSnapshot = Object.freeze({ ...versions });
|
|
390
490
|
},
|
|
391
491
|
close() {
|
|
492
|
+
if (storeClosed) return;
|
|
493
|
+
storeClosed = true;
|
|
494
|
+
if (onSharedBus) {
|
|
495
|
+
const live = (liveStores.get(name) ?? 1) - 1;
|
|
496
|
+
if (live > 0) liveStores.set(name, live);
|
|
497
|
+
else liveStores.delete(name);
|
|
498
|
+
}
|
|
499
|
+
if (hasWindow) removeEventListener("pageshow", onPageShow);
|
|
392
500
|
if (flushPersist) {
|
|
393
501
|
flushPersist();
|
|
394
502
|
if (typeof document !== "undefined" && typeof removeEventListener === "function") {
|
|
@@ -437,6 +545,7 @@ function createPresence(name, options = {}) {
|
|
|
437
545
|
},
|
|
438
546
|
Math.max(500, Math.floor(pruneAfterMs / 2))
|
|
439
547
|
);
|
|
548
|
+
let closed = false;
|
|
440
549
|
return {
|
|
441
550
|
clientId: bus.clientId,
|
|
442
551
|
getPeers: () => snapshot,
|
|
@@ -445,6 +554,8 @@ function createPresence(name, options = {}) {
|
|
|
445
554
|
return () => listeners.delete(fn);
|
|
446
555
|
},
|
|
447
556
|
close() {
|
|
557
|
+
if (closed) return;
|
|
558
|
+
closed = true;
|
|
448
559
|
clearInterval(prune);
|
|
449
560
|
unsubscribe();
|
|
450
561
|
listeners.clear();
|
|
@@ -533,10 +644,19 @@ function createLeader(name, options = {}) {
|
|
|
533
644
|
bus.post({ v: 1, scope: "leader", type: "resign", term: resigning, clientId, kind: bus.kind });
|
|
534
645
|
armLease(leaseMs);
|
|
535
646
|
}
|
|
647
|
+
const sayHello = () => bus.post({ v: 1, scope: "leader", type: "hello", clientId, kind: bus.kind });
|
|
536
648
|
const hasWindow = typeof document !== "undefined" && typeof addEventListener === "function";
|
|
537
649
|
const onPageHide = () => resign();
|
|
538
|
-
|
|
539
|
-
|
|
650
|
+
const onPageShow = (event) => {
|
|
651
|
+
if (!event.persisted) return;
|
|
652
|
+
sayHello();
|
|
653
|
+
armLease(heartbeatMs);
|
|
654
|
+
};
|
|
655
|
+
if (hasWindow) {
|
|
656
|
+
addEventListener("pagehide", onPageHide);
|
|
657
|
+
addEventListener("pageshow", onPageShow);
|
|
658
|
+
}
|
|
659
|
+
sayHello();
|
|
540
660
|
armLease(heartbeatMs);
|
|
541
661
|
return {
|
|
542
662
|
clientId,
|
|
@@ -556,11 +676,15 @@ function createLeader(name, options = {}) {
|
|
|
556
676
|
if (leaderId === null) armLease(0);
|
|
557
677
|
},
|
|
558
678
|
close() {
|
|
679
|
+
if (closed) return;
|
|
559
680
|
closed = true;
|
|
560
681
|
resign();
|
|
561
682
|
clearInterval(beat);
|
|
562
683
|
clearTimeout(lease);
|
|
563
|
-
if (hasWindow)
|
|
684
|
+
if (hasWindow) {
|
|
685
|
+
removeEventListener("pagehide", onPageHide);
|
|
686
|
+
removeEventListener("pageshow", onPageShow);
|
|
687
|
+
}
|
|
564
688
|
unsubscribe();
|
|
565
689
|
listeners.clear();
|
|
566
690
|
bus.release();
|
|
@@ -574,44 +698,54 @@ function isPersisted(value) {
|
|
|
574
698
|
const candidate = value;
|
|
575
699
|
return candidate.v === 1 && typeof candidate.state === "object" && typeof candidate.versions === "object";
|
|
576
700
|
}
|
|
577
|
-
function webStorageAdapter(storage, key) {
|
|
578
|
-
const
|
|
701
|
+
function webStorageAdapter(storage, key, options = {}) {
|
|
702
|
+
const report = (error, operation) => {
|
|
579
703
|
try {
|
|
580
|
-
|
|
704
|
+
options.onError?.(error, operation);
|
|
581
705
|
} catch {
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
const resolve = (operation) => {
|
|
709
|
+
try {
|
|
710
|
+
return typeof storage === "function" ? storage() : storage;
|
|
711
|
+
} catch (error) {
|
|
712
|
+
report(error, operation);
|
|
582
713
|
return void 0;
|
|
583
714
|
}
|
|
584
715
|
};
|
|
585
716
|
return {
|
|
586
717
|
read() {
|
|
587
718
|
try {
|
|
588
|
-
const raw = resolve()?.getItem(key);
|
|
719
|
+
const raw = resolve("read")?.getItem(key);
|
|
589
720
|
if (!raw) return void 0;
|
|
590
721
|
const parsed = JSON.parse(raw);
|
|
591
722
|
return isPersisted(parsed) ? parsed : void 0;
|
|
592
|
-
} catch {
|
|
723
|
+
} catch (error) {
|
|
724
|
+
report(error, "read");
|
|
593
725
|
return void 0;
|
|
594
726
|
}
|
|
595
727
|
},
|
|
596
728
|
write(snapshot) {
|
|
597
729
|
try {
|
|
598
|
-
resolve()?.setItem(key, JSON.stringify(snapshot));
|
|
599
|
-
} catch {
|
|
730
|
+
resolve("write")?.setItem(key, JSON.stringify(snapshot));
|
|
731
|
+
} catch (error) {
|
|
732
|
+
report(error, "write");
|
|
600
733
|
}
|
|
601
734
|
},
|
|
602
735
|
remove() {
|
|
603
736
|
try {
|
|
604
|
-
resolve()?.removeItem(key);
|
|
605
|
-
} catch {
|
|
737
|
+
resolve("remove")?.removeItem(key);
|
|
738
|
+
} catch (error) {
|
|
739
|
+
report(error, "remove");
|
|
606
740
|
}
|
|
607
741
|
}
|
|
608
742
|
};
|
|
609
743
|
}
|
|
610
|
-
function localStorageAdapter(key) {
|
|
611
|
-
return webStorageAdapter(() => globalThis.localStorage, key);
|
|
744
|
+
function localStorageAdapter(key, options) {
|
|
745
|
+
return webStorageAdapter(() => globalThis.localStorage, key, options);
|
|
612
746
|
}
|
|
613
|
-
function sessionStorageAdapter(key) {
|
|
614
|
-
return webStorageAdapter(() => globalThis.sessionStorage, key);
|
|
747
|
+
function sessionStorageAdapter(key, options) {
|
|
748
|
+
return webStorageAdapter(() => globalThis.sessionStorage, key, options);
|
|
615
749
|
}
|
|
616
750
|
|
|
617
751
|
// src/errors/handshake-timeout-error.ts
|
|
@@ -738,6 +872,7 @@ function openWindow(url, options) {
|
|
|
738
872
|
}, 400);
|
|
739
873
|
const readyTimer = setTimeout(() => {
|
|
740
874
|
if (isReady || closedSettled) return;
|
|
875
|
+
localWindow.removeEventListener("message", onMessage);
|
|
741
876
|
const err = new HandshakeTimeoutError();
|
|
742
877
|
rejectReady(err);
|
|
743
878
|
if (!resultSettled) {
|
|
@@ -791,6 +926,7 @@ function connectToOpener(options) {
|
|
|
791
926
|
markHandled(ready);
|
|
792
927
|
const onMessage = (event) => {
|
|
793
928
|
if (!allowAnyOrigin && event.origin !== peerOrigin) return;
|
|
929
|
+
if (event.source !== opener) return;
|
|
794
930
|
if (!isWindowWire(event.data, cid)) return;
|
|
795
931
|
const wire = event.data;
|
|
796
932
|
if (wire.t === "ready-ack") {
|
|
@@ -846,64 +982,11 @@ function connectToOpener(options) {
|
|
|
846
982
|
}
|
|
847
983
|
};
|
|
848
984
|
}
|
|
849
|
-
|
|
850
|
-
// src/transport/memory-transport.ts
|
|
851
|
-
var MemoryTransport = class {
|
|
852
|
-
constructor(hub) {
|
|
853
|
-
this.hub = hub;
|
|
854
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
855
|
-
this.closed = false;
|
|
856
|
-
}
|
|
857
|
-
post(data) {
|
|
858
|
-
if (this.closed) return;
|
|
859
|
-
this.hub.broadcast(this, data);
|
|
860
|
-
}
|
|
861
|
-
subscribe(listener) {
|
|
862
|
-
this.listeners.add(listener);
|
|
863
|
-
return () => this.listeners.delete(listener);
|
|
864
|
-
}
|
|
865
|
-
close() {
|
|
866
|
-
this.closed = true;
|
|
867
|
-
this.listeners.clear();
|
|
868
|
-
this.hub.disconnect(this);
|
|
869
|
-
}
|
|
870
|
-
/** @internal */
|
|
871
|
-
deliver(data) {
|
|
872
|
-
if (this.closed) return;
|
|
873
|
-
for (const listener of this.listeners) listener(data);
|
|
874
|
-
}
|
|
875
|
-
};
|
|
876
|
-
|
|
877
|
-
// src/transport/memory-hub.ts
|
|
878
|
-
var MemoryHub = class {
|
|
879
|
-
constructor() {
|
|
880
|
-
this.transports = /* @__PURE__ */ new Set();
|
|
881
|
-
}
|
|
882
|
-
connect() {
|
|
883
|
-
const transport = new MemoryTransport(this);
|
|
884
|
-
this.transports.add(transport);
|
|
885
|
-
return transport;
|
|
886
|
-
}
|
|
887
|
-
/** @internal */
|
|
888
|
-
broadcast(from, data) {
|
|
889
|
-
for (const transport of this.transports) {
|
|
890
|
-
if (transport !== from) {
|
|
891
|
-
queueMicrotask(() => transport.deliver(data));
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
/** @internal */
|
|
896
|
-
disconnect(transport) {
|
|
897
|
-
this.transports.delete(transport);
|
|
898
|
-
}
|
|
899
|
-
};
|
|
900
985
|
export {
|
|
901
986
|
BroadcastChannelTransport,
|
|
902
987
|
CID_PARAM,
|
|
903
988
|
DEFAULT_NAME,
|
|
904
989
|
HandshakeTimeoutError,
|
|
905
|
-
MemoryHub,
|
|
906
|
-
MemoryTransport,
|
|
907
990
|
NoopTransport,
|
|
908
991
|
WindowClosedError,
|
|
909
992
|
connectToOpener,
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/testing.ts
|
|
21
|
+
var testing_exports = {};
|
|
22
|
+
__export(testing_exports, {
|
|
23
|
+
MemoryHub: () => MemoryHub,
|
|
24
|
+
MemoryTransport: () => MemoryTransport
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(testing_exports);
|
|
27
|
+
|
|
28
|
+
// src/transport/memory-transport.ts
|
|
29
|
+
var MemoryTransport = class {
|
|
30
|
+
constructor(hub) {
|
|
31
|
+
this.hub = hub;
|
|
32
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
33
|
+
this.closed = false;
|
|
34
|
+
}
|
|
35
|
+
post(data) {
|
|
36
|
+
if (this.closed) return;
|
|
37
|
+
this.hub.broadcast(this, data);
|
|
38
|
+
}
|
|
39
|
+
subscribe(listener) {
|
|
40
|
+
this.listeners.add(listener);
|
|
41
|
+
return () => this.listeners.delete(listener);
|
|
42
|
+
}
|
|
43
|
+
close() {
|
|
44
|
+
this.closed = true;
|
|
45
|
+
this.listeners.clear();
|
|
46
|
+
this.hub.disconnect(this);
|
|
47
|
+
}
|
|
48
|
+
/** @internal */
|
|
49
|
+
deliver(data) {
|
|
50
|
+
if (this.closed) return;
|
|
51
|
+
for (const listener of this.listeners) listener(data);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/transport/memory-hub.ts
|
|
56
|
+
var MemoryHub = class {
|
|
57
|
+
constructor() {
|
|
58
|
+
this.transports = /* @__PURE__ */ new Set();
|
|
59
|
+
}
|
|
60
|
+
connect() {
|
|
61
|
+
const transport = new MemoryTransport(this);
|
|
62
|
+
this.transports.add(transport);
|
|
63
|
+
return transport;
|
|
64
|
+
}
|
|
65
|
+
/** @internal */
|
|
66
|
+
broadcast(from, data) {
|
|
67
|
+
structuredClone(data);
|
|
68
|
+
for (const transport of this.transports) {
|
|
69
|
+
if (transport !== from) {
|
|
70
|
+
const copy = structuredClone(data);
|
|
71
|
+
queueMicrotask(() => transport.deliver(copy));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** @internal */
|
|
76
|
+
disconnect(transport) {
|
|
77
|
+
this.transports.delete(transport);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
81
|
+
0 && (module.exports = {
|
|
82
|
+
MemoryHub,
|
|
83
|
+
MemoryTransport
|
|
84
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { T as Transport } from './transport.types-tQ1cu6Xm.cjs';
|
|
2
|
+
|
|
3
|
+
/** One simulated client on a MemoryHub. Create via hub.connect(). */
|
|
4
|
+
declare class MemoryTransport implements Transport {
|
|
5
|
+
private hub;
|
|
6
|
+
private listeners;
|
|
7
|
+
private closed;
|
|
8
|
+
constructor(hub: MemoryHub);
|
|
9
|
+
post(data: unknown): void;
|
|
10
|
+
subscribe(listener: (data: unknown) => void): () => void;
|
|
11
|
+
close(): void;
|
|
12
|
+
/** @internal */
|
|
13
|
+
deliver(data: unknown): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* In-memory hub for tests: N transports attached to one hub, each post is
|
|
18
|
+
* delivered to every *other* transport on a microtask (mirrors BroadcastChannel's
|
|
19
|
+
* async, no-self-echo delivery).
|
|
20
|
+
*/
|
|
21
|
+
declare class MemoryHub {
|
|
22
|
+
private transports;
|
|
23
|
+
connect(): MemoryTransport;
|
|
24
|
+
/** @internal */
|
|
25
|
+
broadcast(from: MemoryTransport, data: unknown): void;
|
|
26
|
+
/** @internal */
|
|
27
|
+
disconnect(transport: MemoryTransport): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { MemoryHub, MemoryTransport };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { T as Transport } from './transport.types-tQ1cu6Xm.js';
|
|
2
|
+
|
|
3
|
+
/** One simulated client on a MemoryHub. Create via hub.connect(). */
|
|
4
|
+
declare class MemoryTransport implements Transport {
|
|
5
|
+
private hub;
|
|
6
|
+
private listeners;
|
|
7
|
+
private closed;
|
|
8
|
+
constructor(hub: MemoryHub);
|
|
9
|
+
post(data: unknown): void;
|
|
10
|
+
subscribe(listener: (data: unknown) => void): () => void;
|
|
11
|
+
close(): void;
|
|
12
|
+
/** @internal */
|
|
13
|
+
deliver(data: unknown): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* In-memory hub for tests: N transports attached to one hub, each post is
|
|
18
|
+
* delivered to every *other* transport on a microtask (mirrors BroadcastChannel's
|
|
19
|
+
* async, no-self-echo delivery).
|
|
20
|
+
*/
|
|
21
|
+
declare class MemoryHub {
|
|
22
|
+
private transports;
|
|
23
|
+
connect(): MemoryTransport;
|
|
24
|
+
/** @internal */
|
|
25
|
+
broadcast(from: MemoryTransport, data: unknown): void;
|
|
26
|
+
/** @internal */
|
|
27
|
+
disconnect(transport: MemoryTransport): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { MemoryHub, MemoryTransport };
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/transport/memory-transport.ts
|
|
2
|
+
var MemoryTransport = class {
|
|
3
|
+
constructor(hub) {
|
|
4
|
+
this.hub = hub;
|
|
5
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
6
|
+
this.closed = false;
|
|
7
|
+
}
|
|
8
|
+
post(data) {
|
|
9
|
+
if (this.closed) return;
|
|
10
|
+
this.hub.broadcast(this, data);
|
|
11
|
+
}
|
|
12
|
+
subscribe(listener) {
|
|
13
|
+
this.listeners.add(listener);
|
|
14
|
+
return () => this.listeners.delete(listener);
|
|
15
|
+
}
|
|
16
|
+
close() {
|
|
17
|
+
this.closed = true;
|
|
18
|
+
this.listeners.clear();
|
|
19
|
+
this.hub.disconnect(this);
|
|
20
|
+
}
|
|
21
|
+
/** @internal */
|
|
22
|
+
deliver(data) {
|
|
23
|
+
if (this.closed) return;
|
|
24
|
+
for (const listener of this.listeners) listener(data);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/transport/memory-hub.ts
|
|
29
|
+
var MemoryHub = class {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.transports = /* @__PURE__ */ new Set();
|
|
32
|
+
}
|
|
33
|
+
connect() {
|
|
34
|
+
const transport = new MemoryTransport(this);
|
|
35
|
+
this.transports.add(transport);
|
|
36
|
+
return transport;
|
|
37
|
+
}
|
|
38
|
+
/** @internal */
|
|
39
|
+
broadcast(from, data) {
|
|
40
|
+
structuredClone(data);
|
|
41
|
+
for (const transport of this.transports) {
|
|
42
|
+
if (transport !== from) {
|
|
43
|
+
const copy = structuredClone(data);
|
|
44
|
+
queueMicrotask(() => transport.deliver(copy));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** @internal */
|
|
49
|
+
disconnect(transport) {
|
|
50
|
+
this.transports.delete(transport);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
export {
|
|
54
|
+
MemoryHub,
|
|
55
|
+
MemoryTransport
|
|
56
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
}
|
|
11
|
+
|
|
12
|
+
export type { Transport as T };
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
}
|
|
11
|
+
|
|
12
|
+
export type { Transport as T };
|