@peerbit/pubsub 4.1.4-3f16953 → 4.1.4-4783d05

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/src/index.ts CHANGED
@@ -36,6 +36,7 @@ import {
36
36
  MessageHeader,
37
37
  NotStartedError,
38
38
  type PriorityOptions,
39
+ type RouteHint,
39
40
  SilentDelivery,
40
41
  type WithExtraSigners,
41
42
  deliveryModeHasReceiver,
@@ -228,6 +229,8 @@ export class TopicControlPlane
228
229
  public peerToTopic: Map<string, Set<string>>;
229
230
  // Local topic -> reference count.
230
231
  public subscriptions: Map<string, { counter: number }>;
232
+ // Local topics requested via debounced subscribe, not yet applied in `subscriptions`.
233
+ private pendingSubscriptions: Set<string>;
231
234
  public lastSubscriptionMessages: Map<string, Map<string, bigint>> = new Map();
232
235
  public dispatchEventOnSelfPublish: boolean;
233
236
  public readonly topicRootControlPlane: TopicRootControlPlane;
@@ -289,6 +292,7 @@ export class TopicControlPlane
289
292
  ) {
290
293
  super(components, ["/peerbit/topic-control-plane/2.0.0"], props);
291
294
  this.subscriptions = new Map();
295
+ this.pendingSubscriptions = new Set();
292
296
  this.topics = new Map();
293
297
  this.peerToTopic = new Map();
294
298
 
@@ -448,6 +452,7 @@ export class TopicControlPlane
448
452
  this.autoCandidatesGossipUntil = 0;
449
453
 
450
454
  this.subscriptions.clear();
455
+ this.pendingSubscriptions.clear();
451
456
  this.topics.clear();
452
457
  this.peerToTopic.clear();
453
458
  this.lastSubscriptionMessages.clear();
@@ -760,11 +765,24 @@ export class TopicControlPlane
760
765
  const subscriptions: string[] = [];
761
766
  if (topics) {
762
767
  for (const topic of topics) {
763
- if (this.subscriptions.get(topic)) subscriptions.push(topic);
768
+ if (
769
+ this.subscriptions.get(topic) ||
770
+ this.pendingSubscriptions.has(topic)
771
+ ) {
772
+ subscriptions.push(topic);
773
+ }
764
774
  }
765
775
  return subscriptions;
766
776
  }
767
- for (const [topic] of this.subscriptions) subscriptions.push(topic);
777
+ const seen = new Set<string>();
778
+ for (const [topic] of this.subscriptions) {
779
+ subscriptions.push(topic);
780
+ seen.add(topic);
781
+ }
782
+ for (const topic of this.pendingSubscriptions) {
783
+ if (seen.has(topic)) continue;
784
+ subscriptions.push(topic);
785
+ }
768
786
  return subscriptions;
769
787
  }
770
788
 
@@ -1098,6 +1116,10 @@ export class TopicControlPlane
1098
1116
  }
1099
1117
 
1100
1118
  async subscribe(topic: string) {
1119
+ this.pendingSubscriptions.add(topic);
1120
+ // `subscribe()` is debounced; start tracking immediately to avoid dropping
1121
+ // inbound subscription traffic during the debounce window.
1122
+ this.initializeTopic(topic);
1101
1123
  return this.debounceSubscribeAggregator.add({ key: topic });
1102
1124
  }
1103
1125
 
@@ -1111,10 +1133,12 @@ export class TopicControlPlane
1111
1133
  let prev = this.subscriptions.get(topic);
1112
1134
  if (prev) {
1113
1135
  prev.counter += counter;
1136
+ this.pendingSubscriptions.delete(topic);
1114
1137
  continue;
1115
1138
  }
1116
1139
  this.subscriptions.set(topic, { counter });
1117
1140
  this.initializeTopic(topic);
1141
+ this.pendingSubscriptions.delete(topic);
1118
1142
 
1119
1143
  const shardTopic = this.getShardTopicForUserTopic(topic);
1120
1144
  byShard.set(shardTopic, [...(byShard.get(shardTopic) ?? []), topic]);
@@ -1156,8 +1180,13 @@ export class TopicControlPlane
1156
1180
  data?: Uint8Array;
1157
1181
  },
1158
1182
  ) {
1183
+ this.pendingSubscriptions.delete(topic);
1184
+
1159
1185
  if (this.debounceSubscribeAggregator.has(topic)) {
1160
1186
  this.debounceSubscribeAggregator.delete(topic);
1187
+ if (!this.subscriptions.has(topic)) {
1188
+ this.untrackTopic(topic);
1189
+ }
1161
1190
  return false;
1162
1191
  }
1163
1192
 
@@ -1252,6 +1281,39 @@ export class TopicControlPlane
1252
1281
  return ret;
1253
1282
  }
1254
1283
 
1284
+ /**
1285
+ * Returns best-effort route hints for a target peer by combining:
1286
+ * - DirectStream ACK-learned routes
1287
+ * - Fanout route tokens for the topic's shard overlay
1288
+ */
1289
+ getUnifiedRouteHints(topic: string, targetHash: string): RouteHint[] {
1290
+ const hints: RouteHint[] = [];
1291
+ const directHint = this.getBestRouteHint(targetHash);
1292
+ if (directHint) {
1293
+ hints.push(directHint);
1294
+ }
1295
+
1296
+ const topicString = topic.toString();
1297
+ const shardTopic = topicString.startsWith(this.shardTopicPrefix)
1298
+ ? topicString
1299
+ : this.getShardTopicForUserTopic(topicString);
1300
+ const shard = this.fanoutChannels.get(shardTopic);
1301
+ if (!shard) {
1302
+ return hints;
1303
+ }
1304
+
1305
+ const fanoutHint = this.fanout.getRouteHint(
1306
+ shardTopic,
1307
+ shard.root,
1308
+ targetHash,
1309
+ );
1310
+ if (fanoutHint) {
1311
+ hints.push(fanoutHint);
1312
+ }
1313
+
1314
+ return hints;
1315
+ }
1316
+
1255
1317
  async requestSubscribers(
1256
1318
  topic: string | string[],
1257
1319
  to?: PublicSignKey,
@@ -1736,7 +1798,7 @@ export class TopicControlPlane
1736
1798
 
1737
1799
  if (pubsubMessage instanceof PubSubData) {
1738
1800
  const wantsTopic = pubsubMessage.topics.some((t) =>
1739
- this.subscriptions.has(t),
1801
+ this.subscriptions.has(t) || this.pendingSubscriptions.has(t),
1740
1802
  );
1741
1803
  isForMe = pubsubMessage.strict ? isForMe && wantsTopic : wantsTopic;
1742
1804
  }