@peerbit/pubsub 5.2.10 → 5.3.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/benchmark/fanout-tree-parent-upgrade-default-ready.d.ts +3 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.d.ts.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.js +90 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.js.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.d.ts +2 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.d.ts.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.js +712 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-eval.js.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.d.ts +2 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.d.ts.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.js +1591 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.js.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.d.ts +2 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.d.ts.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.js +297 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-prepush.js.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.d.ts +73 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.d.ts.map +1 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.js +183 -0
- package/dist/benchmark/fanout-tree-parent-upgrade-preset.js.map +1 -0
- package/dist/benchmark/fanout-tree-sim-lib.d.ts +101 -0
- package/dist/benchmark/fanout-tree-sim-lib.d.ts.map +1 -1
- package/dist/benchmark/fanout-tree-sim-lib.js +540 -38
- package/dist/benchmark/fanout-tree-sim-lib.js.map +1 -1
- package/dist/benchmark/fanout-tree-sim.d.ts +0 -9
- package/dist/benchmark/fanout-tree-sim.d.ts.map +1 -1
- package/dist/benchmark/fanout-tree-sim.js +369 -28
- package/dist/benchmark/fanout-tree-sim.js.map +1 -1
- package/dist/benchmark/index.js +20 -0
- package/dist/benchmark/index.js.map +1 -1
- package/dist/benchmark/pubsub-topic-sim-lib.d.ts +4 -0
- package/dist/benchmark/pubsub-topic-sim-lib.d.ts.map +1 -1
- package/dist/benchmark/pubsub-topic-sim-lib.js +58 -3
- package/dist/benchmark/pubsub-topic-sim-lib.js.map +1 -1
- package/dist/benchmark/pubsub-topic-sim.js +18 -0
- package/dist/benchmark/pubsub-topic-sim.js.map +1 -1
- package/dist/src/debounced-set.d.ts +1 -1
- package/dist/src/debounced-set.d.ts.map +1 -1
- package/dist/src/debounced-set.js +5 -1
- package/dist/src/debounced-set.js.map +1 -1
- package/dist/src/fanout-tree-codec.d.ts +330 -0
- package/dist/src/fanout-tree-codec.d.ts.map +1 -0
- package/dist/src/fanout-tree-codec.js +1209 -0
- package/dist/src/fanout-tree-codec.js.map +1 -0
- package/dist/src/fanout-tree-parent-upgrade.d.ts +117 -0
- package/dist/src/fanout-tree-parent-upgrade.d.ts.map +1 -0
- package/dist/src/fanout-tree-parent-upgrade.js +135 -0
- package/dist/src/fanout-tree-parent-upgrade.js.map +1 -0
- package/dist/src/fanout-tree.d.ts +241 -73
- package/dist/src/fanout-tree.d.ts.map +1 -1
- package/dist/src/fanout-tree.js +2224 -1050
- package/dist/src/fanout-tree.js.map +1 -1
- package/dist/src/index.d.ts +57 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +293 -41
- package/dist/src/index.js.map +1 -1
- package/dist/src/topic-root-control-plane.d.ts +13 -0
- package/dist/src/topic-root-control-plane.d.ts.map +1 -1
- package/dist/src/topic-root-control-plane.js +45 -0
- package/dist/src/topic-root-control-plane.js.map +1 -1
- package/package.json +6 -6
- package/src/debounced-set.ts +5 -0
- package/src/fanout-tree-codec.ts +1580 -0
- package/src/fanout-tree-parent-upgrade.ts +353 -0
- package/src/fanout-tree.ts +4467 -2204
- package/src/index.ts +392 -56
- package/src/topic-root-control-plane.ts +48 -0
package/dist/src/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { AcknowledgeDelivery, DataMessage, type ExpiresAtOptions, type IdOptions
|
|
|
6
6
|
import type { FanoutTree, FanoutTreeChannelOptions, FanoutTreeJoinOptions } from "./fanout-tree.js";
|
|
7
7
|
import { TopicRootControlPlane } from "./topic-root-control-plane.js";
|
|
8
8
|
export * from "./fanout-tree.js";
|
|
9
|
+
export * as fanoutWire from "./fanout-tree-codec.js";
|
|
10
|
+
export * as fanoutParentUpgrade from "./fanout-tree-parent-upgrade.js";
|
|
9
11
|
export * from "./fanout-channel.js";
|
|
10
12
|
export * from "./topic-root-control-plane.js";
|
|
11
13
|
export { toUint8Array } from "./bytes.js";
|
|
@@ -100,6 +102,14 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
100
102
|
counter: number;
|
|
101
103
|
}>;
|
|
102
104
|
private pendingSubscriptions;
|
|
105
|
+
/**
|
|
106
|
+
* Per-shard batching of Subscribe{requestSubscribers} responses. When many
|
|
107
|
+
* peers announce subscriptions in a burst (group subscribe), answering each
|
|
108
|
+
* announcer with a routed unicast is quadratic in subscriber count and
|
|
109
|
+
* melts the fanout route-resolution machinery (#983); a short window lets
|
|
110
|
+
* one broadcast answer all concurrent announcers instead.
|
|
111
|
+
*/
|
|
112
|
+
private pendingSubscriberResponses;
|
|
103
113
|
lastSubscriptionMessages: Map<string, Map<string, {
|
|
104
114
|
session: bigint;
|
|
105
115
|
timestamp: bigint;
|
|
@@ -108,6 +118,7 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
108
118
|
readonly topicRootControlPlane: TopicRootControlPlane;
|
|
109
119
|
readonly subscriberCacheMaxEntries: number;
|
|
110
120
|
readonly fanout: FanoutTree;
|
|
121
|
+
private readonly nativeTopicControl?;
|
|
111
122
|
private debounceSubscribeAggregator;
|
|
112
123
|
private debounceUnsubscribeAggregator;
|
|
113
124
|
private readonly shardCount;
|
|
@@ -172,6 +183,17 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
172
183
|
private touchFanoutChannel;
|
|
173
184
|
private evictEphemeralFanoutChannels;
|
|
174
185
|
private getShardTopicForUserTopic;
|
|
186
|
+
/**
|
|
187
|
+
* Serialize a control-plane message (borsh `PubSubMessage`); rust-core
|
|
188
|
+
* mode encodes natively (byte-identical, covered by the parity suite).
|
|
189
|
+
*/
|
|
190
|
+
private encodePubSubMessage;
|
|
191
|
+
/**
|
|
192
|
+
* Parse a control-plane payload; rust-core mode decodes natively into the
|
|
193
|
+
* same message classes (decode failures throw either way, so callers keep
|
|
194
|
+
* their fallback behavior).
|
|
195
|
+
*/
|
|
196
|
+
private decodePubSubMessage;
|
|
175
197
|
private resolveTopicRootState;
|
|
176
198
|
resolveTopicRoot(topic: string): Promise<string | undefined>;
|
|
177
199
|
private resolveShardRoot;
|
|
@@ -212,13 +234,48 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
212
234
|
} & PriorityOptions & ResponsePriorityOptions & ExpiresAtOptions & IdOptions & WithExtraSigners & {
|
|
213
235
|
signal?: AbortSignal;
|
|
214
236
|
}): Promise<Uint8Array | undefined>;
|
|
237
|
+
/**
|
|
238
|
+
* Publish a pre-encoded `PubSubData` payload to explicit recipients.
|
|
239
|
+
*
|
|
240
|
+
* `payload` must be the exact borsh serialization of
|
|
241
|
+
* `new PubSubData({ topics, data, strict: true })` — callers that encode
|
|
242
|
+
* the payload elsewhere (e.g. the shared-log fused send path serializing
|
|
243
|
+
* inside wasm) hand the finished bytes here so the wrapping `PubSubData`
|
|
244
|
+
* copy of the regular `publish` path is skipped. Everything else matches
|
|
245
|
+
* the explicit-recipient branch of {@link publish}: the payload becomes a
|
|
246
|
+
* signed `DataMessage` delivered over DirectStream.
|
|
247
|
+
*/
|
|
248
|
+
publishPreEncodedData(payload: Uint8Array, properties: {
|
|
249
|
+
topics: string[];
|
|
250
|
+
}, options: {
|
|
251
|
+
mode: SilentDelivery | AcknowledgeDelivery;
|
|
252
|
+
} & {
|
|
253
|
+
client?: string;
|
|
254
|
+
} & PriorityOptions & ResponsePriorityOptions & ExpiresAtOptions & IdOptions & WithExtraSigners & {
|
|
255
|
+
signal?: AbortSignal;
|
|
256
|
+
}): Promise<Uint8Array | undefined>;
|
|
215
257
|
onPeerSession(key: PublicSignKey, _session: number): void;
|
|
216
258
|
onPeerUnreachable(publicKeyHash: string): void;
|
|
217
259
|
private collectSubscriptionState;
|
|
218
260
|
private removeSubscriptionsBeforeSession;
|
|
219
261
|
private removeSubscriptions;
|
|
220
262
|
private subscriptionStateIsLatest;
|
|
263
|
+
/**
|
|
264
|
+
* `Subscribe` replaces tracked subscription data for new subscribers or
|
|
265
|
+
* strictly newer sessions; equal/older sessions only refresh cache order.
|
|
266
|
+
*/
|
|
267
|
+
private subscribeShouldReplace;
|
|
221
268
|
private subscriptionMessageIsLatest;
|
|
269
|
+
/**
|
|
270
|
+
* Answer Subscribe{requestSubscribers} announces without letting bursts go
|
|
271
|
+
* quadratic (#983). Leading edge: the first announcer is answered
|
|
272
|
+
* immediately with a targeted unicast — a lone joiner sees no added
|
|
273
|
+
* latency. Announcers arriving within the trailing window that this opens
|
|
274
|
+
* are batched and answered with ONE broadcast, instead of S(S-1) routed
|
|
275
|
+
* unicasts whose cold route resolutions flood the shard tree.
|
|
276
|
+
*/
|
|
277
|
+
private queueSubscriberResponse;
|
|
278
|
+
private flushSubscriberResponses;
|
|
222
279
|
private sendFanoutUnicastOrBroadcast;
|
|
223
280
|
private processDirectPubSubMessage;
|
|
224
281
|
private processShardPubSubMessage;
|
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,EAIN,KAAK,MAAM,EAEX,KAAK,YAAY,EAIjB,gBAAgB,EAQhB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,YAAY,EACZ,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.js
CHANGED
|
@@ -11,11 +11,28 @@ import { debouncedAccumulatorSetCounter, } from "./debounced-set.js";
|
|
|
11
11
|
import { FanoutChannel } from "./fanout-channel.js";
|
|
12
12
|
import { TopicRootControlPlane } from "./topic-root-control-plane.js";
|
|
13
13
|
export * from "./fanout-tree.js";
|
|
14
|
+
// The complete /peerbit/fanout-tree/0.5.0 wire codec and the
|
|
15
|
+
// parent-upgrade decision module (the TS references the native rust-core
|
|
16
|
+
// implementations are parity-tested against).
|
|
17
|
+
export * as fanoutWire from "./fanout-tree-codec.js";
|
|
18
|
+
export * as fanoutParentUpgrade from "./fanout-tree-parent-upgrade.js";
|
|
14
19
|
export * from "./fanout-channel.js";
|
|
15
20
|
export * from "./topic-root-control-plane.js";
|
|
16
21
|
export { toUint8Array } from "./bytes.js";
|
|
17
22
|
export const logger = loggerFn("peerbit:transport:topic-control-plane");
|
|
18
23
|
const warn = logger.newScope("warn");
|
|
24
|
+
const utf8Encoder = new TextEncoder();
|
|
25
|
+
/**
|
|
26
|
+
* Offset of the `data` bytes inside a borsh-serialized `PubSubData`
|
|
27
|
+
* (variant byte + topics vec + strict bool + data length prefix).
|
|
28
|
+
*/
|
|
29
|
+
const preEncodedPubSubDataOffset = (topics) => {
|
|
30
|
+
let offset = 1 + 4; // variant + topics vec length
|
|
31
|
+
for (const topic of topics) {
|
|
32
|
+
offset += 4 + utf8Encoder.encode(topic).byteLength;
|
|
33
|
+
}
|
|
34
|
+
return offset + 1 + 4; // strict + data length prefix
|
|
35
|
+
};
|
|
19
36
|
const logError = (e) => {
|
|
20
37
|
logger.error(e?.message);
|
|
21
38
|
};
|
|
@@ -89,11 +106,26 @@ export class TopicControlPlane extends DirectStream {
|
|
|
89
106
|
subscriptions;
|
|
90
107
|
// Local topics requested via debounced subscribe, not yet applied in `subscriptions`.
|
|
91
108
|
pendingSubscriptions;
|
|
109
|
+
/**
|
|
110
|
+
* Per-shard batching of Subscribe{requestSubscribers} responses. When many
|
|
111
|
+
* peers announce subscriptions in a burst (group subscribe), answering each
|
|
112
|
+
* announcer with a routed unicast is quadratic in subscriber count and
|
|
113
|
+
* melts the fanout route-resolution machinery (#983); a short window lets
|
|
114
|
+
* one broadcast answer all concurrent announcers instead.
|
|
115
|
+
*/
|
|
116
|
+
pendingSubscriberResponses = new Map();
|
|
92
117
|
lastSubscriptionMessages = new Map();
|
|
93
118
|
dispatchEventOnSelfPublish;
|
|
94
119
|
topicRootControlPlane;
|
|
95
120
|
subscriberCacheMaxEntries;
|
|
96
121
|
fanout;
|
|
122
|
+
// Native topic-control components of the rust-core DirectStream engine
|
|
123
|
+
// (`@peerbit/network-rust`). When set, the PubSubMessage codec, the topic
|
|
124
|
+
// hashing behind shard mapping and root selection, the root-directory
|
|
125
|
+
// state and the subscribe-state convergence rules run natively; the
|
|
126
|
+
// observable subscription maps and all events stay unchanged. Unset (the
|
|
127
|
+
// default) leaves every code path as-is.
|
|
128
|
+
nativeTopicControl;
|
|
97
129
|
debounceSubscribeAggregator;
|
|
98
130
|
debounceUnsubscribeAggregator;
|
|
99
131
|
shardCount;
|
|
@@ -132,6 +164,10 @@ export class TopicControlPlane extends DirectStream {
|
|
|
132
164
|
this.peerToTopic = new Map();
|
|
133
165
|
this.topicRootControlPlane =
|
|
134
166
|
props?.topicRootControlPlane || new TopicRootControlPlane();
|
|
167
|
+
this.nativeTopicControl = this.rustCore?.topicControl;
|
|
168
|
+
if (this.nativeTopicControl) {
|
|
169
|
+
this.topicRootControlPlane.adoptNativeDirectoryState(this.nativeTopicControl.createRootDirectoryState());
|
|
170
|
+
}
|
|
135
171
|
this.dispatchEventOnSelfPublish =
|
|
136
172
|
props?.dispatchEventOnSelfPublish || false;
|
|
137
173
|
if (!props?.fanout) {
|
|
@@ -173,10 +209,10 @@ export class TopicControlPlane extends DirectStream {
|
|
|
173
209
|
this.fanoutPublishMaxEphemeralChannels = Math.max(0, Math.floor(requestedMaxEphemeral));
|
|
174
210
|
const requestedSubscriberCacheMaxEntries = props?.subscriberCacheMaxEntries ?? SUBSCRIBER_CACHE_DEFAULT_MAX_ENTRIES;
|
|
175
211
|
this.subscriberCacheMaxEntries = Math.min(SUBSCRIBER_CACHE_MAX_ENTRIES_HARD_CAP, Math.max(1, Math.floor(requestedSubscriberCacheMaxEntries)));
|
|
176
|
-
this.debounceSubscribeAggregator = debouncedAccumulatorSetCounter((set) => this._subscribe([...set.values()]), props?.subscriptionDebounceDelay ?? 50);
|
|
212
|
+
this.debounceSubscribeAggregator = debouncedAccumulatorSetCounter((set) => this._subscribe([...set.values()]), props?.subscriptionDebounceDelay ?? 50, (error) => logger.error(`Debounced subscribe failed: ${error?.message ?? error}`));
|
|
177
213
|
// NOTE: Unsubscribe should update local state immediately and batch only the
|
|
178
214
|
// best-effort network announcements to avoid teardown stalls (program close).
|
|
179
|
-
this.debounceUnsubscribeAggregator = debouncedAccumulatorSetCounter((set) => this._announceUnsubscribe([...set.values()]), props?.subscriptionDebounceDelay ?? 50);
|
|
215
|
+
this.debounceUnsubscribeAggregator = debouncedAccumulatorSetCounter((set) => this._announceUnsubscribe([...set.values()]), props?.subscriptionDebounceDelay ?? 50, (error) => logger.error(`Debounced unsubscribe announce failed: ${error?.message ?? error}`));
|
|
180
216
|
}
|
|
181
217
|
/**
|
|
182
218
|
* Configure deterministic topic-root candidates and disable the pubsub "auto"
|
|
@@ -268,6 +304,10 @@ export class TopicControlPlane extends DirectStream {
|
|
|
268
304
|
this.hostOwnedShardRootsDirty = false;
|
|
269
305
|
this.subscriptions.clear();
|
|
270
306
|
this.pendingSubscriptions.clear();
|
|
307
|
+
for (const entry of this.pendingSubscriberResponses.values()) {
|
|
308
|
+
clearTimeout(entry.timer);
|
|
309
|
+
}
|
|
310
|
+
this.pendingSubscriberResponses.clear();
|
|
271
311
|
this.topics.clear();
|
|
272
312
|
this.peerToTopic.clear();
|
|
273
313
|
this.lastSubscriptionMessages.clear();
|
|
@@ -362,6 +402,9 @@ export class TopicControlPlane extends DirectStream {
|
|
|
362
402
|
this.scheduleAutoTopicRootCandidatesBroadcast();
|
|
363
403
|
}
|
|
364
404
|
normalizeAutoTopicRootCandidates(candidates) {
|
|
405
|
+
if (this.nativeTopicControl) {
|
|
406
|
+
return this.nativeTopicControl.normalizeAutoCandidates(candidates, this.publicKeyHash);
|
|
407
|
+
}
|
|
365
408
|
const unique = new Set();
|
|
366
409
|
for (const c of candidates) {
|
|
367
410
|
if (!c)
|
|
@@ -430,7 +473,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
430
473
|
if (candidates.length === 0)
|
|
431
474
|
return;
|
|
432
475
|
const msg = new TopicRootCandidates({ candidates });
|
|
433
|
-
const embedded = await this.createMessage(
|
|
476
|
+
const embedded = await this.createMessage(this.encodePubSubMessage(msg), {
|
|
434
477
|
mode: new AnyWhere(),
|
|
435
478
|
priority: 1,
|
|
436
479
|
skipRecipientValidation: true,
|
|
@@ -527,7 +570,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
527
570
|
topics: userTopics,
|
|
528
571
|
requestSubscribers: true,
|
|
529
572
|
});
|
|
530
|
-
const embedded = await this.createMessage(
|
|
573
|
+
const embedded = await this.createMessage(this.encodePubSubMessage(msg), {
|
|
531
574
|
mode: new AnyWhere(),
|
|
532
575
|
priority: 1,
|
|
533
576
|
skipRecipientValidation: true,
|
|
@@ -671,11 +714,95 @@ export class TopicControlPlane extends DirectStream {
|
|
|
671
714
|
const cached = this.shardTopicCache.get(t);
|
|
672
715
|
if (cached)
|
|
673
716
|
return cached;
|
|
674
|
-
const
|
|
675
|
-
|
|
717
|
+
const shardTopic = this.nativeTopicControl
|
|
718
|
+
? this.nativeTopicControl.shardTopic(t, this.shardCount, this.shardTopicPrefix)
|
|
719
|
+
: `${this.shardTopicPrefix}${topicHash32(t) % this.shardCount}`;
|
|
676
720
|
this.shardTopicCache.set(t, shardTopic);
|
|
677
721
|
return shardTopic;
|
|
678
722
|
}
|
|
723
|
+
/**
|
|
724
|
+
* Serialize a control-plane message (borsh `PubSubMessage`); rust-core
|
|
725
|
+
* mode encodes natively (byte-identical, covered by the parity suite).
|
|
726
|
+
*/
|
|
727
|
+
encodePubSubMessage(message) {
|
|
728
|
+
const native = this.nativeTopicControl;
|
|
729
|
+
if (native) {
|
|
730
|
+
if (message instanceof PubSubData) {
|
|
731
|
+
return native.encodePubSubData(message.topics, message.strict, message.data);
|
|
732
|
+
}
|
|
733
|
+
if (message instanceof Subscribe) {
|
|
734
|
+
return native.encodeSubscribe(message.topics, message.requestSubscribers);
|
|
735
|
+
}
|
|
736
|
+
if (message instanceof Unsubscribe) {
|
|
737
|
+
return native.encodeUnsubscribe(message.topics);
|
|
738
|
+
}
|
|
739
|
+
if (message instanceof GetSubscribers) {
|
|
740
|
+
return native.encodeGetSubscribers(message.topics);
|
|
741
|
+
}
|
|
742
|
+
if (message instanceof TopicRootCandidates) {
|
|
743
|
+
return native.encodeTopicRootCandidates(message.candidates);
|
|
744
|
+
}
|
|
745
|
+
if (message instanceof PeerUnavailable) {
|
|
746
|
+
return native.encodePeerUnavailable(message.publicKeyHash, message.session, message.timestamp, message.topics);
|
|
747
|
+
}
|
|
748
|
+
if (message instanceof TopicRootQuery) {
|
|
749
|
+
return native.encodeTopicRootQuery(message.requestId, message.topic);
|
|
750
|
+
}
|
|
751
|
+
if (message instanceof TopicRootQueryResponse) {
|
|
752
|
+
return native.encodeTopicRootQueryResponse(message.requestId, message.topic, message.root);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return toUint8Array(message.bytes());
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Parse a control-plane payload; rust-core mode decodes natively into the
|
|
759
|
+
* same message classes (decode failures throw either way, so callers keep
|
|
760
|
+
* their fallback behavior).
|
|
761
|
+
*/
|
|
762
|
+
decodePubSubMessage(bytes) {
|
|
763
|
+
const native = this.nativeTopicControl;
|
|
764
|
+
if (native) {
|
|
765
|
+
const decoded = native.decodePubSubMessage(bytes);
|
|
766
|
+
switch (decoded.type) {
|
|
767
|
+
case "data":
|
|
768
|
+
return new PubSubData({
|
|
769
|
+
topics: decoded.topics,
|
|
770
|
+
data: decoded.data,
|
|
771
|
+
strict: decoded.strict,
|
|
772
|
+
});
|
|
773
|
+
case "subscribe":
|
|
774
|
+
return new Subscribe({
|
|
775
|
+
topics: decoded.topics,
|
|
776
|
+
requestSubscribers: decoded.requestSubscribers,
|
|
777
|
+
});
|
|
778
|
+
case "unsubscribe":
|
|
779
|
+
return new Unsubscribe({ topics: decoded.topics });
|
|
780
|
+
case "get-subscribers":
|
|
781
|
+
return new GetSubscribers({ topics: decoded.topics });
|
|
782
|
+
case "topic-root-candidates":
|
|
783
|
+
return new TopicRootCandidates({ candidates: decoded.candidates });
|
|
784
|
+
case "peer-unavailable":
|
|
785
|
+
return new PeerUnavailable({
|
|
786
|
+
publicKeyHash: decoded.publicKeyHash,
|
|
787
|
+
session: decoded.session,
|
|
788
|
+
timestamp: decoded.timestamp,
|
|
789
|
+
topics: decoded.topics,
|
|
790
|
+
});
|
|
791
|
+
case "topic-root-query":
|
|
792
|
+
return new TopicRootQuery({
|
|
793
|
+
requestId: decoded.requestId,
|
|
794
|
+
topic: decoded.topic,
|
|
795
|
+
});
|
|
796
|
+
case "topic-root-query-response":
|
|
797
|
+
return new TopicRootQueryResponse({
|
|
798
|
+
requestId: decoded.requestId,
|
|
799
|
+
topic: decoded.topic,
|
|
800
|
+
root: decoded.root,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return PubSubMessage.from(bytes);
|
|
805
|
+
}
|
|
679
806
|
async resolveTopicRootState(topic) {
|
|
680
807
|
const tracked = await this.topicRootControlPlane.resolveTrackedTopicRoot(topic);
|
|
681
808
|
if (tracked) {
|
|
@@ -747,7 +874,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
747
874
|
} while (true);
|
|
748
875
|
}
|
|
749
876
|
async sendDirectControlMessage(peer, pubsubMessage) {
|
|
750
|
-
const embedded = await this.createMessage(
|
|
877
|
+
const embedded = await this.createMessage(this.encodePubSubMessage(pubsubMessage), {
|
|
751
878
|
mode: new SilentDelivery({
|
|
752
879
|
to: [peer.publicKey.hashcode()],
|
|
753
880
|
redundancy: 1,
|
|
@@ -828,6 +955,8 @@ export class TopicControlPlane extends DirectStream {
|
|
|
828
955
|
return undefined;
|
|
829
956
|
}
|
|
830
957
|
async ensureFanoutChannel(shardTopic, options) {
|
|
958
|
+
if (!this.started)
|
|
959
|
+
throw new NotStartedError();
|
|
831
960
|
const t = shardTopic.toString();
|
|
832
961
|
const pin = options?.pin === true;
|
|
833
962
|
const wantEphemeral = options?.ephemeral === true;
|
|
@@ -874,7 +1003,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
874
1003
|
return;
|
|
875
1004
|
let pubsubMessage;
|
|
876
1005
|
try {
|
|
877
|
-
pubsubMessage =
|
|
1006
|
+
pubsubMessage = this.decodePubSubMessage(dm.data);
|
|
878
1007
|
}
|
|
879
1008
|
catch {
|
|
880
1009
|
return;
|
|
@@ -915,6 +1044,9 @@ export class TopicControlPlane extends DirectStream {
|
|
|
915
1044
|
this.seenCache.add(msgId, seen ? seen + 1 : 1);
|
|
916
1045
|
if (seen)
|
|
917
1046
|
return;
|
|
1047
|
+
// fanout-delivered frames bypass the inbound stream decode, so
|
|
1048
|
+
// the nested envelope is verified natively here when possible
|
|
1049
|
+
this.seedNativeWireVerification(dm, payload);
|
|
918
1050
|
if ((await this.verifyAndProcess(dm)) === false) {
|
|
919
1051
|
return;
|
|
920
1052
|
}
|
|
@@ -1106,7 +1238,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1106
1238
|
topics: userTopics,
|
|
1107
1239
|
requestSubscribers: true,
|
|
1108
1240
|
});
|
|
1109
|
-
const embedded = await this.createMessage(
|
|
1241
|
+
const embedded = await this.createMessage(this.encodePubSubMessage(msg), {
|
|
1110
1242
|
mode: new AnyWhere(),
|
|
1111
1243
|
priority: 1,
|
|
1112
1244
|
skipRecipientValidation: true,
|
|
@@ -1173,7 +1305,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1173
1305
|
// Announce first.
|
|
1174
1306
|
try {
|
|
1175
1307
|
const msg = new Unsubscribe({ topics: userTopics });
|
|
1176
|
-
const embedded = await this.createMessage(
|
|
1308
|
+
const embedded = await this.createMessage(this.encodePubSubMessage(msg), {
|
|
1177
1309
|
mode: new AnyWhere(),
|
|
1178
1310
|
priority: 1,
|
|
1179
1311
|
skipRecipientValidation: true,
|
|
@@ -1238,7 +1370,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1238
1370
|
timestamp: batch.timestamp,
|
|
1239
1371
|
topics: batch.topics,
|
|
1240
1372
|
});
|
|
1241
|
-
const embedded = await this.createMessage(
|
|
1373
|
+
const embedded = await this.createMessage(this.encodePubSubMessage(msg), {
|
|
1242
1374
|
mode: new AnyWhere(),
|
|
1243
1375
|
priority: 1,
|
|
1244
1376
|
skipRecipientValidation: true,
|
|
@@ -1265,7 +1397,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1265
1397
|
timestamp: 0n,
|
|
1266
1398
|
topics: [],
|
|
1267
1399
|
});
|
|
1268
|
-
const embedded = await this.createMessage(
|
|
1400
|
+
const embedded = await this.createMessage(this.encodePubSubMessage(msg), {
|
|
1269
1401
|
mode: new AnyWhere(),
|
|
1270
1402
|
priority: 1,
|
|
1271
1403
|
skipRecipientValidation: true,
|
|
@@ -1349,7 +1481,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1349
1481
|
}
|
|
1350
1482
|
const persistent = (this.shardRefCounts.get(shardTopic) ?? 0) > 0;
|
|
1351
1483
|
await this.ensureFanoutChannel(shardTopic, { ephemeral: !persistent });
|
|
1352
|
-
const embedded = await this.createMessage(
|
|
1484
|
+
const embedded = await this.createMessage(this.encodePubSubMessage(msg), {
|
|
1353
1485
|
mode: new AnyWhere(),
|
|
1354
1486
|
priority: 1,
|
|
1355
1487
|
skipRecipientValidation: true,
|
|
@@ -1384,7 +1516,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1384
1516
|
const msg = data
|
|
1385
1517
|
? new PubSubData({ topics: topicsAll, data, strict: true })
|
|
1386
1518
|
: undefined;
|
|
1387
|
-
const message = await this.createMessage(msg
|
|
1519
|
+
const message = await this.createMessage(msg ? this.encodePubSubMessage(msg) : undefined, {
|
|
1388
1520
|
...options,
|
|
1389
1521
|
skipRecipientValidation: this.dispatchEventOnSelfPublish,
|
|
1390
1522
|
});
|
|
@@ -1417,7 +1549,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1417
1549
|
}
|
|
1418
1550
|
}
|
|
1419
1551
|
const msg = new PubSubData({ topics: topicsAll, data, strict: false });
|
|
1420
|
-
const embedded = await this.createMessage(
|
|
1552
|
+
const embedded = await this.createMessage(this.encodePubSubMessage(msg), {
|
|
1421
1553
|
mode: new AnyWhere(),
|
|
1422
1554
|
priority: options?.priority,
|
|
1423
1555
|
responsePriority: options?.responsePriority,
|
|
@@ -1467,6 +1599,50 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1467
1599
|
}
|
|
1468
1600
|
return embedded.id;
|
|
1469
1601
|
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Publish a pre-encoded `PubSubData` payload to explicit recipients.
|
|
1604
|
+
*
|
|
1605
|
+
* `payload` must be the exact borsh serialization of
|
|
1606
|
+
* `new PubSubData({ topics, data, strict: true })` — callers that encode
|
|
1607
|
+
* the payload elsewhere (e.g. the shared-log fused send path serializing
|
|
1608
|
+
* inside wasm) hand the finished bytes here so the wrapping `PubSubData`
|
|
1609
|
+
* copy of the regular `publish` path is skipped. Everything else matches
|
|
1610
|
+
* the explicit-recipient branch of {@link publish}: the payload becomes a
|
|
1611
|
+
* signed `DataMessage` delivered over DirectStream.
|
|
1612
|
+
*/
|
|
1613
|
+
async publishPreEncodedData(payload, properties, options) {
|
|
1614
|
+
if (!this.started)
|
|
1615
|
+
throw new NotStartedError();
|
|
1616
|
+
if (!options?.mode || !deliveryModeHasReceiver(options.mode)) {
|
|
1617
|
+
throw new Error("publishPreEncodedData requires a delivery mode with explicit recipients");
|
|
1618
|
+
}
|
|
1619
|
+
const message = await this.createMessage(payload, {
|
|
1620
|
+
...options,
|
|
1621
|
+
skipRecipientValidation: this.dispatchEventOnSelfPublish,
|
|
1622
|
+
});
|
|
1623
|
+
this.dispatchEvent(new CustomEvent("publish", {
|
|
1624
|
+
detail: new PublishEvent({
|
|
1625
|
+
client: options?.client,
|
|
1626
|
+
data: new PubSubData({
|
|
1627
|
+
topics: properties.topics,
|
|
1628
|
+
data: payload.subarray(preEncodedPubSubDataOffset(properties.topics)),
|
|
1629
|
+
strict: true,
|
|
1630
|
+
}),
|
|
1631
|
+
message,
|
|
1632
|
+
}),
|
|
1633
|
+
}));
|
|
1634
|
+
const silentDelivery = options.mode instanceof SilentDelivery;
|
|
1635
|
+
try {
|
|
1636
|
+
await this.publishMessage(this.publicKey, message, undefined, undefined, options?.signal);
|
|
1637
|
+
}
|
|
1638
|
+
catch (error) {
|
|
1639
|
+
if (error instanceof DeliveryError && silentDelivery !== false) {
|
|
1640
|
+
return message.id;
|
|
1641
|
+
}
|
|
1642
|
+
throw error;
|
|
1643
|
+
}
|
|
1644
|
+
return message.id;
|
|
1645
|
+
}
|
|
1470
1646
|
onPeerSession(key, _session) {
|
|
1471
1647
|
if (!Number.isFinite(_session) || _session < 0) {
|
|
1472
1648
|
return;
|
|
@@ -1575,20 +1751,36 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1575
1751
|
return changed;
|
|
1576
1752
|
}
|
|
1577
1753
|
subscriptionStateIsLatest(subscriberKey, session, timestamp, relevantTopics) {
|
|
1578
|
-
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1754
|
+
if (this.nativeTopicControl) {
|
|
1755
|
+
const lasts = [];
|
|
1756
|
+
for (const topic of relevantTopics) {
|
|
1757
|
+
const last = this.lastSubscriptionMessages
|
|
1758
|
+
.get(subscriberKey)
|
|
1759
|
+
?.get(topic);
|
|
1760
|
+
if (last) {
|
|
1761
|
+
lasts.push(last.session, last.timestamp);
|
|
1762
|
+
}
|
|
1584
1763
|
}
|
|
1585
|
-
if (
|
|
1764
|
+
if (!this.nativeTopicControl.subscriptionIsLatest(BigUint64Array.from(lasts), session, timestamp)) {
|
|
1586
1765
|
return false;
|
|
1587
1766
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1767
|
+
}
|
|
1768
|
+
else {
|
|
1769
|
+
for (const topic of relevantTopics) {
|
|
1770
|
+
const last = this.lastSubscriptionMessages
|
|
1771
|
+
.get(subscriberKey)
|
|
1772
|
+
?.get(topic);
|
|
1773
|
+
if (!last) {
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
if (last.session > session) {
|
|
1777
|
+
return false;
|
|
1778
|
+
}
|
|
1779
|
+
if (timestamp !== 0n &&
|
|
1780
|
+
last.session === session &&
|
|
1781
|
+
last.timestamp > timestamp) {
|
|
1782
|
+
return false;
|
|
1783
|
+
}
|
|
1592
1784
|
}
|
|
1593
1785
|
}
|
|
1594
1786
|
for (const topic of relevantTopics) {
|
|
@@ -1604,10 +1796,80 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1604
1796
|
}
|
|
1605
1797
|
return true;
|
|
1606
1798
|
}
|
|
1799
|
+
/**
|
|
1800
|
+
* `Subscribe` replaces tracked subscription data for new subscribers or
|
|
1801
|
+
* strictly newer sessions; equal/older sessions only refresh cache order.
|
|
1802
|
+
*/
|
|
1803
|
+
subscribeShouldReplace(existing, session) {
|
|
1804
|
+
if (this.nativeTopicControl) {
|
|
1805
|
+
return this.nativeTopicControl.subscribeShouldReplace(existing?.session, session);
|
|
1806
|
+
}
|
|
1807
|
+
return !existing || existing.session < session;
|
|
1808
|
+
}
|
|
1607
1809
|
subscriptionMessageIsLatest(message, _pubsubMessage, relevantTopics) {
|
|
1608
1810
|
const subscriber = message.header.signatures.signatures[0].publicKey;
|
|
1609
1811
|
return this.subscriptionStateIsLatest(subscriber.hashcode(), message.header.session, message.header.timestamp, relevantTopics);
|
|
1610
1812
|
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Answer Subscribe{requestSubscribers} announces without letting bursts go
|
|
1815
|
+
* quadratic (#983). Leading edge: the first announcer is answered
|
|
1816
|
+
* immediately with a targeted unicast — a lone joiner sees no added
|
|
1817
|
+
* latency. Announcers arriving within the trailing window that this opens
|
|
1818
|
+
* are batched and answered with ONE broadcast, instead of S(S-1) routed
|
|
1819
|
+
* unicasts whose cold route resolutions flood the shard tree.
|
|
1820
|
+
*/
|
|
1821
|
+
queueSubscriberResponse(shardTopic, targetHash, topics) {
|
|
1822
|
+
let entry = this.pendingSubscriberResponses.get(shardTopic);
|
|
1823
|
+
if (!entry) {
|
|
1824
|
+
const created = {
|
|
1825
|
+
targets: new Set(),
|
|
1826
|
+
topics: new Set(),
|
|
1827
|
+
timer: setTimeout(() => {
|
|
1828
|
+
this.pendingSubscriberResponses.delete(shardTopic);
|
|
1829
|
+
if (created.targets.size === 0)
|
|
1830
|
+
return;
|
|
1831
|
+
void this.flushSubscriberResponses(shardTopic, created).catch(logErrorIfStarted);
|
|
1832
|
+
}, 150 + Math.floor(Math.random() * 150)),
|
|
1833
|
+
};
|
|
1834
|
+
this.pendingSubscriberResponses.set(shardTopic, created);
|
|
1835
|
+
// Leading edge: respond to the burst opener right away.
|
|
1836
|
+
void this.flushSubscriberResponses(shardTopic, {
|
|
1837
|
+
targets: new Set([targetHash]),
|
|
1838
|
+
topics: new Set(topics),
|
|
1839
|
+
}).catch(logErrorIfStarted);
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
entry.targets.add(targetHash);
|
|
1843
|
+
for (const t of topics)
|
|
1844
|
+
entry.topics.add(t);
|
|
1845
|
+
}
|
|
1846
|
+
async flushSubscriberResponses(shardTopic, entry) {
|
|
1847
|
+
if (!this.started)
|
|
1848
|
+
return;
|
|
1849
|
+
// Re-filter: our subscriptions may have changed within the window.
|
|
1850
|
+
const topics = this.getSubscriptionOverlap([...entry.topics]);
|
|
1851
|
+
if (topics.length === 0)
|
|
1852
|
+
return;
|
|
1853
|
+
const response = new Subscribe({
|
|
1854
|
+
topics,
|
|
1855
|
+
requestSubscribers: false,
|
|
1856
|
+
});
|
|
1857
|
+
const embedded = await this.createMessage(this.encodePubSubMessage(response), {
|
|
1858
|
+
mode: new AnyWhere(),
|
|
1859
|
+
priority: 1,
|
|
1860
|
+
skipRecipientValidation: true,
|
|
1861
|
+
});
|
|
1862
|
+
const payload = toUint8Array(embedded.bytes());
|
|
1863
|
+
const [firstTarget] = entry.targets;
|
|
1864
|
+
if (entry.targets.size === 1 && firstTarget) {
|
|
1865
|
+
await this.sendFanoutUnicastOrBroadcast(shardTopic, firstTarget, payload);
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
const st = this.fanoutChannels.get(shardTopic);
|
|
1869
|
+
if (!st)
|
|
1870
|
+
return;
|
|
1871
|
+
await st.channel.publishMaybe(payload);
|
|
1872
|
+
}
|
|
1611
1873
|
async sendFanoutUnicastOrBroadcast(shardTopic, targetHash, payload) {
|
|
1612
1874
|
const st = this.fanoutChannels.get(shardTopic);
|
|
1613
1875
|
if (!st)
|
|
@@ -1667,7 +1929,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1667
1929
|
continue;
|
|
1668
1930
|
this.initializePeer(sender);
|
|
1669
1931
|
const existing = peers.get(senderKey);
|
|
1670
|
-
if (
|
|
1932
|
+
if (this.subscribeShouldReplace(existing, message.header.session)) {
|
|
1671
1933
|
peers.delete(senderKey);
|
|
1672
1934
|
peers.set(senderKey, new SubscriptionData({
|
|
1673
1935
|
session: message.header.session,
|
|
@@ -1746,7 +2008,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1746
2008
|
continue;
|
|
1747
2009
|
this.initializePeer(sender);
|
|
1748
2010
|
const existing = peers.get(senderKey);
|
|
1749
|
-
if (
|
|
2011
|
+
if (this.subscribeShouldReplace(existing, message.header.session)) {
|
|
1750
2012
|
peers.delete(senderKey);
|
|
1751
2013
|
peers.set(senderKey, new SubscriptionData({
|
|
1752
2014
|
session: message.header.session,
|
|
@@ -1773,17 +2035,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1773
2035
|
if (pubsubMessage.requestSubscribers) {
|
|
1774
2036
|
const overlap = this.getSubscriptionOverlap(pubsubMessage.topics);
|
|
1775
2037
|
if (overlap.length > 0) {
|
|
1776
|
-
|
|
1777
|
-
topics: overlap,
|
|
1778
|
-
requestSubscribers: false,
|
|
1779
|
-
});
|
|
1780
|
-
const embedded = await this.createMessage(toUint8Array(response.bytes()), {
|
|
1781
|
-
mode: new AnyWhere(),
|
|
1782
|
-
priority: 1,
|
|
1783
|
-
skipRecipientValidation: true,
|
|
1784
|
-
});
|
|
1785
|
-
const payload = toUint8Array(embedded.bytes());
|
|
1786
|
-
await this.sendFanoutUnicastOrBroadcast(shardTopic, senderKey, payload);
|
|
2038
|
+
this.queueSubscriberResponse(shardTopic, senderKey, overlap);
|
|
1787
2039
|
}
|
|
1788
2040
|
}
|
|
1789
2041
|
return;
|
|
@@ -1873,7 +2125,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1873
2125
|
topics: overlap,
|
|
1874
2126
|
requestSubscribers: false,
|
|
1875
2127
|
});
|
|
1876
|
-
const embedded = await this.createMessage(
|
|
2128
|
+
const embedded = await this.createMessage(this.encodePubSubMessage(response), {
|
|
1877
2129
|
mode: new AnyWhere(),
|
|
1878
2130
|
priority: 1,
|
|
1879
2131
|
skipRecipientValidation: true,
|
|
@@ -1891,7 +2143,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1891
2143
|
return false;
|
|
1892
2144
|
let pubsubMessage;
|
|
1893
2145
|
try {
|
|
1894
|
-
pubsubMessage =
|
|
2146
|
+
pubsubMessage = this.decodePubSubMessage(message.data);
|
|
1895
2147
|
}
|
|
1896
2148
|
catch {
|
|
1897
2149
|
return super.onDataMessage(from, stream, message, seenBefore);
|