@peerbit/shared-log 13.1.3 → 13.1.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/shared-log",
3
- "version": "13.1.3",
3
+ "version": "13.1.4",
4
4
  "description": "Shared log",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -61,28 +61,28 @@
61
61
  "pidusage": "^4.0.1",
62
62
  "pino": "^9.4.0",
63
63
  "uint8arrays": "^5.1.0",
64
+ "@peerbit/blocks": "4.1.2",
64
65
  "@peerbit/any-store": "2.2.9",
66
+ "@peerbit/blocks-interface": "2.0.9",
67
+ "@peerbit/crypto": "3.1.1",
65
68
  "@peerbit/cache": "3.0.0",
66
- "@peerbit/blocks-interface": "2.0.8",
67
69
  "@peerbit/indexer-interface": "3.0.3",
68
- "@peerbit/crypto": "3.1.1",
69
- "@peerbit/blocks": "4.1.1",
70
70
  "@peerbit/indexer-sqlite3": "3.0.6",
71
71
  "@peerbit/logger": "2.0.1",
72
- "@peerbit/program": "6.0.19",
73
- "@peerbit/pubsub-interface": "5.1.1",
74
- "@peerbit/pubsub": "5.2.0",
75
- "@peerbit/rpc": "6.0.23",
72
+ "@peerbit/log": "6.0.25",
73
+ "@peerbit/program": "6.0.20",
76
74
  "@peerbit/riblt": "1.2.0",
75
+ "@peerbit/pubsub": "5.2.1",
76
+ "@peerbit/pubsub-interface": "5.1.1",
77
+ "@peerbit/rpc": "6.0.24",
77
78
  "@peerbit/stream-interface": "6.0.7",
78
- "@peerbit/log": "6.0.24",
79
79
  "@peerbit/time": "3.0.0"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@types/libsodium-wrappers": "^0.7.14",
83
83
  "@types/pidusage": "^2.0.5",
84
84
  "uuid": "^10.0.0",
85
- "@peerbit/test-utils": "3.0.23"
85
+ "@peerbit/test-utils": "3.0.24"
86
86
  },
87
87
  "repository": {
88
88
  "type": "git",
package/src/index.ts CHANGED
@@ -1580,67 +1580,54 @@ export class SharedLog<
1580
1580
  return keys.slice();
1581
1581
  };
1582
1582
 
1583
- // Prefer the bounded peer set we already know from the fanout overlay.
1584
- if (this._fanoutChannel && (topic === this.topic || topic === this.rpc.topic)) {
1585
- const hashes = this._fanoutChannel
1586
- .getPeerHashes({ includeSelf: false })
1587
- .slice(0, maxPeers);
1588
- if (hashes.length === 0) return cache([]);
1589
-
1590
- const keys = await Promise.all(
1591
- hashes.map((hash) => this._resolvePublicKeyFromHash(hash)),
1592
- );
1593
- const uniqueKeys: PublicSignKey[] = [];
1594
- const seen = new Set<string>();
1595
- const selfHash = this.node.identity.publicKey.hashcode();
1596
- for (const key of keys) {
1597
- if (!key) continue;
1598
- const hash = key.hashcode();
1599
- if (hash === selfHash) continue;
1600
- if (seen.has(hash)) continue;
1601
- seen.add(hash);
1602
- uniqueKeys.push(key);
1603
- }
1604
- return cache(uniqueKeys);
1605
- }
1606
-
1607
1583
  const selfHash = this.node.identity.publicKey.hashcode();
1608
- const hashes: string[] = [];
1584
+ const hashes = new Set<string>();
1585
+ const keysByHash = new Map<string, PublicSignKey>();
1586
+ const addHash = (hash: string | undefined) => {
1587
+ if (!hash || hash === selfHash || keysByHash.has(hash)) {
1588
+ return;
1589
+ }
1590
+ hashes.add(hash);
1591
+ };
1592
+ const addKey = (key: PublicSignKey | undefined) => {
1593
+ if (!key) {
1594
+ return;
1595
+ }
1596
+ const hash = key.hashcode();
1597
+ if (hash === selfHash) {
1598
+ return;
1599
+ }
1600
+ hashes.delete(hash);
1601
+ keysByHash.set(hash, key);
1602
+ };
1609
1603
 
1610
- // Best-effort provider discovery (bounded). This requires bootstrap trackers.
1611
- try {
1612
- const fanoutService = getSharedLogFanoutService(this.node.services);
1613
- if (fanoutService?.queryProviders) {
1614
- const ns = `shared-log|${this.topic}`;
1615
- const seed = hashToSeed32(topic);
1616
- const providers: string[] = await fanoutService.queryProviders(ns, {
1617
- want: maxPeers,
1618
- seed,
1619
- });
1620
- for (const h of providers ?? []) {
1621
- if (!h || h === selfHash) continue;
1622
- hashes.push(h);
1623
- if (hashes.length >= maxPeers) break;
1624
- }
1604
+ // Fanout is a useful hint, but it can lag direct pubsub connectivity. Keep
1605
+ // collecting other local views instead of treating an empty fanout snapshot as
1606
+ // authoritative absence.
1607
+ if (this._fanoutChannel && (topic === this.topic || topic === this.rpc.topic)) {
1608
+ for (const hash of this._fanoutChannel.getPeerHashes({
1609
+ includeSelf: false,
1610
+ })) {
1611
+ addHash(hash);
1612
+ if (hashes.size + keysByHash.size >= maxPeers) break;
1625
1613
  }
1626
- } catch {
1627
- // Best-effort only.
1628
1614
  }
1629
1615
 
1630
- // Next, use already-connected peer streams (bounded and cheap).
1631
- const peerMap: Map<string, unknown> | undefined = (this.node.services.pubsub as any)
1632
- ?.peers;
1633
- if (peerMap?.keys) {
1634
- for (const h of peerMap.keys()) {
1635
- if (!h || h === selfHash) continue;
1636
- hashes.push(h);
1637
- if (hashes.length >= maxPeers) break;
1616
+ // Already-connected peer streams are cheap and are the strongest local signal
1617
+ // when fanout/provider membership is stale.
1618
+ const peerMap: Map<string, { publicKey?: PublicSignKey }> | undefined = (this.node
1619
+ .services.pubsub as any)?.peers;
1620
+ if (peerMap?.entries) {
1621
+ for (const [hash, peer] of peerMap.entries()) {
1622
+ addKey(peer?.publicKey);
1623
+ addHash(hash);
1624
+ if (hashes.size + keysByHash.size >= maxPeers) break;
1638
1625
  }
1639
1626
  }
1640
1627
 
1641
- // Finally, fall back to libp2p connections (e.g. bootstrap peers) without requiring
1642
- // any global topic membership view.
1643
- if (hashes.length < maxPeers) {
1628
+ // Libp2p connections cover bootstrap/direct peers even before a higher-level
1629
+ // topic subscriber snapshot has converged.
1630
+ if (hashes.size + keysByHash.size < maxPeers) {
1644
1631
  const connectionManager = (this.node.services.pubsub as any)?.components
1645
1632
  ?.connectionManager;
1646
1633
  const connections = connectionManager?.getConnections?.() ?? [];
@@ -1648,38 +1635,48 @@ export class SharedLog<
1648
1635
  const peerId = conn?.remotePeer;
1649
1636
  if (!peerId) continue;
1650
1637
  try {
1651
- const h = getPublicKeyFromPeerId(peerId).hashcode();
1652
- if (!h || h === selfHash) continue;
1653
- hashes.push(h);
1654
- if (hashes.length >= maxPeers) break;
1638
+ addKey(getPublicKeyFromPeerId(peerId));
1639
+ if (hashes.size + keysByHash.size >= maxPeers) break;
1655
1640
  } catch {
1656
1641
  // Best-effort only.
1657
1642
  }
1658
1643
  }
1659
1644
  }
1660
1645
 
1661
- if (hashes.length === 0) return cache([]);
1662
-
1663
- const uniqueHashes: string[] = [];
1664
- const seen = new Set<string>();
1665
- for (const h of hashes) {
1666
- if (seen.has(h)) continue;
1667
- seen.add(h);
1668
- uniqueHashes.push(h);
1669
- if (uniqueHashes.length >= maxPeers) break;
1646
+ // Best-effort provider discovery (bounded). This requires bootstrap trackers.
1647
+ if (hashes.size + keysByHash.size < maxPeers) {
1648
+ try {
1649
+ const fanoutService = getSharedLogFanoutService(this.node.services);
1650
+ if (fanoutService?.queryProviders) {
1651
+ const ns = `shared-log|${this.topic}`;
1652
+ const seed = hashToSeed32(topic);
1653
+ const providers: string[] = await fanoutService.queryProviders(ns, {
1654
+ want: maxPeers - keysByHash.size - hashes.size,
1655
+ seed,
1656
+ });
1657
+ for (const hash of providers ?? []) {
1658
+ addHash(hash);
1659
+ if (hashes.size + keysByHash.size >= maxPeers) break;
1660
+ }
1661
+ }
1662
+ } catch {
1663
+ // Best-effort only.
1664
+ }
1670
1665
  }
1671
1666
 
1667
+ if (hashes.size === 0 && keysByHash.size === 0) return cache([]);
1668
+
1669
+ const unresolvedHashes = [...hashes].slice(
1670
+ 0,
1671
+ Math.max(0, maxPeers - keysByHash.size),
1672
+ );
1672
1673
  const keys = await Promise.all(
1673
- uniqueHashes.map((hash) => this._resolvePublicKeyFromHash(hash)),
1674
+ unresolvedHashes.map((hash) => this._resolvePublicKeyFromHash(hash)),
1674
1675
  );
1675
- const uniqueKeys: PublicSignKey[] = [];
1676
1676
  for (const key of keys) {
1677
- if (!key) continue;
1678
- const hash = key.hashcode();
1679
- if (hash === selfHash) continue;
1680
- uniqueKeys.push(key);
1677
+ addKey(key);
1681
1678
  }
1682
- return cache(uniqueKeys);
1679
+ return cache([...keysByHash.values()].slice(0, maxPeers));
1683
1680
  }
1684
1681
 
1685
1682
  private invalidateTopicSubscribersCache(...topics: (string | undefined)[]) {