@peerbit/pubsub 5.0.2 → 5.0.3-3dcfc85
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/benchmark/fanout-tree-sim-lib.d.ts.map +1 -1
- package/dist/benchmark/fanout-tree-sim-lib.js +28 -4
- package/dist/benchmark/fanout-tree-sim-lib.js.map +1 -1
- package/dist/src/fanout-tree.d.ts +5 -0
- package/dist/src/fanout-tree.d.ts.map +1 -1
- package/dist/src/fanout-tree.js +7 -0
- package/dist/src/fanout-tree.js.map +1 -1
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +205 -13
- package/dist/src/index.js.map +1 -1
- package/package.json +12 -11
- package/src/fanout-tree.ts +14 -0
- package/src/index.ts +260 -14
package/dist/src/index.d.ts
CHANGED
|
@@ -132,6 +132,7 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
132
132
|
private autoCandidatesBroadcastTimers;
|
|
133
133
|
private autoCandidatesGossipInterval?;
|
|
134
134
|
private autoCandidatesGossipUntil;
|
|
135
|
+
private _onFanoutPeerUnreachable?;
|
|
135
136
|
private fanoutChannels;
|
|
136
137
|
constructor(components: TopicControlPlaneComponents, props?: TopicControlPlaneOptions);
|
|
137
138
|
/**
|
|
@@ -181,6 +182,9 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
181
182
|
data?: Uint8Array;
|
|
182
183
|
}): Promise<boolean>;
|
|
183
184
|
private _announceUnsubscribe;
|
|
185
|
+
private announcePeerUnavailable;
|
|
186
|
+
private announcePeerUnavailableOnShard;
|
|
187
|
+
private onFanoutPeerUnreachable;
|
|
184
188
|
getSubscribers(topic: string): PublicSignKey[] | undefined;
|
|
185
189
|
/**
|
|
186
190
|
* Returns best-effort route hints for a target peer by combining:
|
|
@@ -200,8 +204,10 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
200
204
|
}): Promise<Uint8Array | undefined>;
|
|
201
205
|
onPeerSession(key: PublicSignKey, _session: number): void;
|
|
202
206
|
onPeerUnreachable(publicKeyHash: string): void;
|
|
207
|
+
private collectSubscriptionState;
|
|
203
208
|
private removeSubscriptionsBeforeSession;
|
|
204
209
|
private removeSubscriptions;
|
|
210
|
+
private subscriptionStateIsLatest;
|
|
205
211
|
private subscriptionMessageIsLatest;
|
|
206
212
|
private sendFanoutUnicastOrBroadcast;
|
|
207
213
|
private processDirectPubSubMessage;
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,UAAU,EACf,KAAK,MAAM,IAAI,YAAY,EAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAA0B,MAAM,iBAAiB,CAAC;AAExE,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {} from "@libp2p/interface";
|
|
2
2
|
import { PublicSignKey, getPublicKeyFromPeerId } from "@peerbit/crypto";
|
|
3
3
|
import { logger as loggerFn } from "@peerbit/logger";
|
|
4
|
-
import { DataEvent, GetSubscribers, PubSubData, PubSubMessage, PublishEvent, Subscribe, SubscriptionData, SubscriptionEvent, TopicRootCandidates, UnsubcriptionEvent, Unsubscribe, } from "@peerbit/pubsub-interface";
|
|
4
|
+
import { DataEvent, GetSubscribers, PeerUnavailable, PubSubData, PubSubMessage, PublishEvent, Subscribe, SubscriptionData, SubscriptionEvent, TopicRootCandidates, UnsubcriptionEvent, Unsubscribe, } from "@peerbit/pubsub-interface";
|
|
5
5
|
import { DirectStream, dontThrowIfDeliveryError, } from "@peerbit/stream";
|
|
6
6
|
import { AcknowledgeAnyWhere, AcknowledgeDelivery, AnyWhere, DataMessage, DeliveryError, MessageHeader, NotStartedError, SilentDelivery, deliveryModeHasReceiver, getMsgId, } from "@peerbit/stream-interface";
|
|
7
7
|
import { AbortError, TimeoutError, delay } from "@peerbit/time";
|
|
@@ -116,6 +116,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
116
116
|
autoCandidatesBroadcastTimers = [];
|
|
117
117
|
autoCandidatesGossipInterval;
|
|
118
118
|
autoCandidatesGossipUntil = 0;
|
|
119
|
+
_onFanoutPeerUnreachable;
|
|
119
120
|
fanoutChannels = new Map();
|
|
120
121
|
constructor(components, props) {
|
|
121
122
|
super(components, ["/peerbit/topic-control-plane/2.0.0"], props);
|
|
@@ -205,6 +206,10 @@ export class TopicControlPlane extends DirectStream {
|
|
|
205
206
|
}
|
|
206
207
|
async start() {
|
|
207
208
|
await this.fanout.start();
|
|
209
|
+
this._onFanoutPeerUnreachable =
|
|
210
|
+
this._onFanoutPeerUnreachable ||
|
|
211
|
+
this.onFanoutPeerUnreachable.bind(this);
|
|
212
|
+
await this.fanout.addEventListener("fanout:peer-unreachable", this._onFanoutPeerUnreachable);
|
|
208
213
|
await super.start();
|
|
209
214
|
if (this.hostShards) {
|
|
210
215
|
await this.hostShardRootsNow();
|
|
@@ -214,6 +219,9 @@ export class TopicControlPlane extends DirectStream {
|
|
|
214
219
|
}
|
|
215
220
|
}
|
|
216
221
|
async stop() {
|
|
222
|
+
if (this._onFanoutPeerUnreachable) {
|
|
223
|
+
this.fanout.removeEventListener("fanout:peer-unreachable", this._onFanoutPeerUnreachable);
|
|
224
|
+
}
|
|
217
225
|
for (const st of this.fanoutChannels.values()) {
|
|
218
226
|
if (st.idleCloseTimeout)
|
|
219
227
|
clearTimeout(st.idleCloseTimeout);
|
|
@@ -746,6 +754,13 @@ export class TopicControlPlane extends DirectStream {
|
|
|
746
754
|
if (!overlap)
|
|
747
755
|
return;
|
|
748
756
|
}
|
|
757
|
+
else if (pubsubMessage instanceof PeerUnavailable) {
|
|
758
|
+
const relevant = pubsubMessage.topics.length > 0
|
|
759
|
+
? pubsubMessage.topics.some((x) => this.isTrackedTopic(x))
|
|
760
|
+
: [...this.topics.keys()].some((topic) => this.getShardTopicForUserTopic(topic) === t);
|
|
761
|
+
if (!relevant)
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
749
764
|
else {
|
|
750
765
|
return;
|
|
751
766
|
}
|
|
@@ -1042,6 +1057,90 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1042
1057
|
}
|
|
1043
1058
|
}));
|
|
1044
1059
|
}
|
|
1060
|
+
async announcePeerUnavailable(publicKeyHash, batches) {
|
|
1061
|
+
if (!this.started)
|
|
1062
|
+
throw new NotStartedError();
|
|
1063
|
+
const byShard = new Map();
|
|
1064
|
+
for (const batch of batches) {
|
|
1065
|
+
for (const topic of batch.topics) {
|
|
1066
|
+
if (!this.isTrackedTopic(topic)) {
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
const shardTopic = this.getShardTopicForUserTopic(topic);
|
|
1070
|
+
const key = `${shardTopic}:${batch.session}:${batch.timestamp}`;
|
|
1071
|
+
const existing = byShard.get(key);
|
|
1072
|
+
if (existing) {
|
|
1073
|
+
existing.topics.push(topic);
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
byShard.set(key, {
|
|
1077
|
+
session: batch.session,
|
|
1078
|
+
timestamp: batch.timestamp,
|
|
1079
|
+
topics: [topic],
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
await Promise.all([...byShard.entries()].map(async ([key, batch]) => {
|
|
1085
|
+
const [shardTopic] = key.split(":");
|
|
1086
|
+
if (!shardTopic || batch.topics.length === 0) {
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
try {
|
|
1090
|
+
const msg = new PeerUnavailable({
|
|
1091
|
+
publicKeyHash,
|
|
1092
|
+
session: batch.session,
|
|
1093
|
+
timestamp: batch.timestamp,
|
|
1094
|
+
topics: batch.topics,
|
|
1095
|
+
});
|
|
1096
|
+
const embedded = await this.createMessage(toUint8Array(msg.bytes()), {
|
|
1097
|
+
mode: new AnyWhere(),
|
|
1098
|
+
priority: 1,
|
|
1099
|
+
skipRecipientValidation: true,
|
|
1100
|
+
});
|
|
1101
|
+
await this.ensureFanoutChannel(shardTopic, { ephemeral: true });
|
|
1102
|
+
const st = this.fanoutChannels.get(shardTopic);
|
|
1103
|
+
if (st) {
|
|
1104
|
+
void st.channel.publish(toUint8Array(embedded.bytes())).catch(() => { });
|
|
1105
|
+
this.touchFanoutChannel(shardTopic);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
catch {
|
|
1109
|
+
// best-effort
|
|
1110
|
+
}
|
|
1111
|
+
}));
|
|
1112
|
+
}
|
|
1113
|
+
async announcePeerUnavailableOnShard(publicKeyHash, shardTopic) {
|
|
1114
|
+
if (!this.started)
|
|
1115
|
+
throw new NotStartedError();
|
|
1116
|
+
try {
|
|
1117
|
+
const msg = new PeerUnavailable({
|
|
1118
|
+
publicKeyHash,
|
|
1119
|
+
session: 0n,
|
|
1120
|
+
timestamp: 0n,
|
|
1121
|
+
topics: [],
|
|
1122
|
+
});
|
|
1123
|
+
const embedded = await this.createMessage(toUint8Array(msg.bytes()), {
|
|
1124
|
+
mode: new AnyWhere(),
|
|
1125
|
+
priority: 1,
|
|
1126
|
+
skipRecipientValidation: true,
|
|
1127
|
+
});
|
|
1128
|
+
await this.ensureFanoutChannel(shardTopic, { ephemeral: true });
|
|
1129
|
+
const st = this.fanoutChannels.get(shardTopic);
|
|
1130
|
+
if (st) {
|
|
1131
|
+
void st.channel.publish(toUint8Array(embedded.bytes())).catch(() => { });
|
|
1132
|
+
this.touchFanoutChannel(shardTopic);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
catch {
|
|
1136
|
+
// best-effort
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
onFanoutPeerUnreachable(ev) {
|
|
1140
|
+
void this
|
|
1141
|
+
.announcePeerUnavailableOnShard(ev.detail.publicKeyHash, ev.detail.topic)
|
|
1142
|
+
.catch(logErrorIfStarted);
|
|
1143
|
+
}
|
|
1045
1144
|
getSubscribers(topic) {
|
|
1046
1145
|
const t = topic.toString();
|
|
1047
1146
|
const remote = this.topics.get(t);
|
|
@@ -1227,8 +1326,52 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1227
1326
|
onPeerUnreachable(publicKeyHash) {
|
|
1228
1327
|
super.onPeerUnreachable(publicKeyHash);
|
|
1229
1328
|
const key = this.peerKeyHashToPublicKey.get(publicKeyHash);
|
|
1230
|
-
if (key)
|
|
1231
|
-
|
|
1329
|
+
if (!key) {
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
const removed = this.collectSubscriptionState(publicKeyHash);
|
|
1333
|
+
const changed = this.removeSubscriptions(key, "peer-unreachable");
|
|
1334
|
+
if (changed.length === 0) {
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
const changedSet = new Set(changed);
|
|
1338
|
+
const batches = removed
|
|
1339
|
+
.map((batch) => ({
|
|
1340
|
+
...batch,
|
|
1341
|
+
topics: batch.topics.filter((topic) => changedSet.has(topic)),
|
|
1342
|
+
}))
|
|
1343
|
+
.filter((batch) => batch.topics.length > 0);
|
|
1344
|
+
if (batches.length > 0) {
|
|
1345
|
+
void this
|
|
1346
|
+
.announcePeerUnavailable(publicKeyHash, batches)
|
|
1347
|
+
.catch(logErrorIfStarted);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
collectSubscriptionState(peerHash) {
|
|
1351
|
+
const peerTopics = this.peerToTopic.get(peerHash);
|
|
1352
|
+
if (!peerTopics) {
|
|
1353
|
+
return [];
|
|
1354
|
+
}
|
|
1355
|
+
const grouped = new Map();
|
|
1356
|
+
for (const topic of peerTopics) {
|
|
1357
|
+
const existing = this.topics.get(topic)?.get(peerHash);
|
|
1358
|
+
if (!existing) {
|
|
1359
|
+
continue;
|
|
1360
|
+
}
|
|
1361
|
+
const key = `${existing.session}:${existing.timestamp}`;
|
|
1362
|
+
const batch = grouped.get(key);
|
|
1363
|
+
if (batch) {
|
|
1364
|
+
batch.topics.push(topic);
|
|
1365
|
+
}
|
|
1366
|
+
else {
|
|
1367
|
+
grouped.set(key, {
|
|
1368
|
+
session: existing.session,
|
|
1369
|
+
timestamp: existing.timestamp,
|
|
1370
|
+
topics: [topic],
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return [...grouped.values()];
|
|
1232
1375
|
}
|
|
1233
1376
|
removeSubscriptionsBeforeSession(publicKey, session, reason) {
|
|
1234
1377
|
const peerHash = publicKey.hashcode();
|
|
@@ -1279,12 +1422,9 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1279
1422
|
detail: new UnsubcriptionEvent(publicKey, changed, reason),
|
|
1280
1423
|
}));
|
|
1281
1424
|
}
|
|
1425
|
+
return changed;
|
|
1282
1426
|
}
|
|
1283
|
-
|
|
1284
|
-
const subscriber = message.header.signatures.signatures[0].publicKey;
|
|
1285
|
-
const subscriberKey = subscriber.hashcode();
|
|
1286
|
-
const messageSession = message.header.session;
|
|
1287
|
-
const messageTimestamp = message.header.timestamp;
|
|
1427
|
+
subscriptionStateIsLatest(subscriberKey, session, timestamp, relevantTopics) {
|
|
1288
1428
|
for (const topic of relevantTopics) {
|
|
1289
1429
|
const last = this.lastSubscriptionMessages
|
|
1290
1430
|
.get(subscriberKey)
|
|
@@ -1292,11 +1432,12 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1292
1432
|
if (!last) {
|
|
1293
1433
|
continue;
|
|
1294
1434
|
}
|
|
1295
|
-
if (last.session >
|
|
1435
|
+
if (last.session > session) {
|
|
1296
1436
|
return false;
|
|
1297
1437
|
}
|
|
1298
|
-
if (
|
|
1299
|
-
last.
|
|
1438
|
+
if (timestamp !== 0n &&
|
|
1439
|
+
last.session === session &&
|
|
1440
|
+
last.timestamp > timestamp) {
|
|
1300
1441
|
return false;
|
|
1301
1442
|
}
|
|
1302
1443
|
}
|
|
@@ -1307,12 +1448,16 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1307
1448
|
this.lastSubscriptionMessages
|
|
1308
1449
|
.get(subscriberKey)
|
|
1309
1450
|
.set(topic, {
|
|
1310
|
-
session
|
|
1311
|
-
timestamp
|
|
1451
|
+
session,
|
|
1452
|
+
timestamp,
|
|
1312
1453
|
});
|
|
1313
1454
|
}
|
|
1314
1455
|
return true;
|
|
1315
1456
|
}
|
|
1457
|
+
subscriptionMessageIsLatest(message, _pubsubMessage, relevantTopics) {
|
|
1458
|
+
const subscriber = message.header.signatures.signatures[0].publicKey;
|
|
1459
|
+
return this.subscriptionStateIsLatest(subscriber.hashcode(), message.header.session, message.header.timestamp, relevantTopics);
|
|
1460
|
+
}
|
|
1316
1461
|
async sendFanoutUnicastOrBroadcast(shardTopic, targetHash, payload) {
|
|
1317
1462
|
const st = this.fanoutChannels.get(shardTopic);
|
|
1318
1463
|
if (!st)
|
|
@@ -1455,6 +1600,53 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1455
1600
|
}
|
|
1456
1601
|
return;
|
|
1457
1602
|
}
|
|
1603
|
+
if (pubsubMessage instanceof PeerUnavailable) {
|
|
1604
|
+
const peerHash = pubsubMessage.publicKeyHash;
|
|
1605
|
+
// Relay-originated shard deltas are keyed only by shard membership, not by
|
|
1606
|
+
// per-topic subscription watermarks. They are emitted immediately when the
|
|
1607
|
+
// relay loses a child so downstream peers can shed stale membership without
|
|
1608
|
+
// waiting for the slower shared-log liveness sweep.
|
|
1609
|
+
const isShardFastPath = pubsubMessage.topics.length === 0 && pubsubMessage.timestamp === 0n;
|
|
1610
|
+
const relevantTopics = pubsubMessage.topics.length > 0
|
|
1611
|
+
? pubsubMessage.topics.filter((topic) => {
|
|
1612
|
+
if (!this.isTrackedTopic(topic)) {
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
return this.topics.get(topic)?.has(peerHash) ?? false;
|
|
1616
|
+
})
|
|
1617
|
+
: [...this.topics.keys()].filter((topic) => this.getShardTopicForUserTopic(topic) === shardTopic &&
|
|
1618
|
+
(this.topics.get(topic)?.has(peerHash) ?? false));
|
|
1619
|
+
const shouldApply = relevantTopics.length > 0 &&
|
|
1620
|
+
(isShardFastPath ||
|
|
1621
|
+
this.subscriptionStateIsLatest(peerHash, pubsubMessage.session, pubsubMessage.timestamp, relevantTopics));
|
|
1622
|
+
if (shouldApply) {
|
|
1623
|
+
const changed = [];
|
|
1624
|
+
let publicKey;
|
|
1625
|
+
for (const topic of relevantTopics) {
|
|
1626
|
+
const peers = this.topics.get(topic);
|
|
1627
|
+
if (!peers)
|
|
1628
|
+
continue;
|
|
1629
|
+
const existing = peers.get(peerHash);
|
|
1630
|
+
if (!existing)
|
|
1631
|
+
continue;
|
|
1632
|
+
publicKey = publicKey ?? existing.publicKey;
|
|
1633
|
+
if (peers.delete(peerHash)) {
|
|
1634
|
+
changed.push(topic);
|
|
1635
|
+
this.peerToTopic.get(peerHash)?.delete(topic);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
if (!this.peerToTopic.get(peerHash)?.size) {
|
|
1639
|
+
this.peerToTopic.delete(peerHash);
|
|
1640
|
+
this.lastSubscriptionMessages.delete(peerHash);
|
|
1641
|
+
}
|
|
1642
|
+
if (changed.length > 0 && publicKey) {
|
|
1643
|
+
this.dispatchEvent(new CustomEvent("unsubscribe", {
|
|
1644
|
+
detail: new UnsubcriptionEvent(publicKey, changed, "peer-unreachable"),
|
|
1645
|
+
}));
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1458
1650
|
if (pubsubMessage instanceof GetSubscribers) {
|
|
1459
1651
|
const sender = from;
|
|
1460
1652
|
const senderKey = sender.hashcode();
|