import { type Connection, type PeerId as Libp2pPeerId } from "@libp2p/interface"; import { PublicSignKey } from "@peerbit/crypto"; import { type PubSub, type PubSubEvents, SubscriptionData } from "@peerbit/pubsub-interface"; import { DirectStream, type DirectStreamComponents, type DirectStreamOptions, type PeerStreams } from "@peerbit/stream"; import { AcknowledgeDelivery, DataMessage, type ExpiresAtOptions, type IdOptions, type PriorityOptions, type ResponsePriorityOptions, type RouteHint, SilentDelivery, type WithExtraSigners } from "@peerbit/stream-interface"; import type { FanoutTree, FanoutTreeChannelOptions, FanoutTreeJoinOptions } from "./fanout-tree.js"; import { TopicRootControlPlane } from "./topic-root-control-plane.js"; export * from "./fanout-tree.js"; export * as fanoutWire from "./fanout-tree-codec.js"; export * as fanoutParentUpgrade from "./fanout-tree-parent-upgrade.js"; export * from "./fanout-channel.js"; export * from "./topic-root-control-plane.js"; export { toUint8Array } from "./bytes.js"; export declare const logger: import("@libp2p/interface").Logger; export type TopicControlPlaneOptions = DirectStreamOptions & { dispatchEventOnSelfPublish?: boolean; subscriptionDebounceDelay?: number; topicRootControlPlane?: TopicRootControlPlane; /** * Fanout overlay used for sharded topic delivery. */ fanout: FanoutTree; /** * Base fanout channel options for shard overlays (applies to both roots and nodes). */ fanoutChannel?: Partial>; /** * Fanout channel overrides applied only when this node is the shard root. */ fanoutRootChannel?: Partial>; /** * Fanout channel overrides applied when joining shard overlays as a node. * * This is the primary knob for "leaf-only" subscribers: set `maxChildren=0` * for non-router nodes so they never become relays under churn. */ fanoutNodeChannel?: Partial>; /** * Fanout join options for overlay topics. */ fanoutJoin?: FanoutTreeJoinOptions; /** * Number of pubsub shards (overlays) used for topic delivery. * * Each user-topic deterministically maps to exactly one shard topic: * `shard = hash(topic) % shardCount`, and subscription joins that shard overlay. * * Default: 256. */ shardCount?: number; /** * Prefix used to form internal shard topics. * * Default: `/peerbit/pubsub-shard/1/`. */ shardTopicPrefix?: string; /** * If enabled, this node will host (open as root) every shard for which it is * the deterministically selected root. * * This is intended for "router"/"supernode" deployments. * * Default: `false`. */ hostShards?: boolean; /** * Fanout-backed topics: require a local `subscribe(topic)` before `publish(topic)` is allowed. * * Default: `false` (publishing without subscribing will temporarily join the overlay). */ fanoutPublishRequiresSubscribe?: boolean; /** * When publishing on a fanout topic without subscribing, keep the ephemeral join * open for this long since the last publish, then auto-leave. * * Default: 60s. Set to `0` to close immediately after each publish. */ fanoutPublishIdleCloseMs?: number; /** * Max number of ephemeral fanout channels kept open concurrently for publish-only usage. * * Default: 64. Set to `0` to disable caching (channels will close after publish). */ fanoutPublishMaxEphemeralChannels?: number; /** * Best-effort bound on cached remote subscribers per topic. * * This controls memory growth at scale and bounds `getSubscribers()` and the * receiver lists used for routing optimizations. */ subscriberCacheMaxEntries?: number; }; export type TopicControlPlaneComponents = DirectStreamComponents; export type PeerId = Libp2pPeerId | PublicSignKey; /** * Bounded, serializable snapshot of effective topic-control-plane settings. * * The snapshot intentionally contains values rather than the live fanout * option objects so diagnostics can expose it without leaking mutable runtime * state or native/WASM handles. */ export type TopicControlPlaneRuntimeSnapshot = Readonly<{ fanout: Readonly<{ root: Readonly<{ uploadLimitBps: number; }>; node: Readonly<{ uploadLimitBps: number; }>; }>; }>; /** * Runtime control-plane implementation for pubsub topic membership + forwarding. */ export declare class TopicControlPlane extends DirectStream implements PubSub { topics: Map>; peerToTopic: Map>; subscriptions: Map; private pendingSubscriptions; /** * Per-shard batching of Subscribe{requestSubscribers} responses. When many * peers announce subscriptions in a burst (group subscribe), answering each * announcer with a routed unicast is quadratic in subscriber count and * melts the fanout route-resolution machinery (#983); a short window lets * one broadcast answer all concurrent announcers instead. */ private pendingSubscriberResponses; lastSubscriptionMessages: Map>; dispatchEventOnSelfPublish: boolean; readonly topicRootControlPlane: TopicRootControlPlane; readonly subscriberCacheMaxEntries: number; readonly fanout: FanoutTree; private readonly nativeTopicControl?; private debounceSubscribeAggregator; private debounceUnsubscribeAggregator; private readonly shardCount; private readonly shardTopicPrefix; private readonly hostShards; private readonly shardRootCache; private readonly shardTopicCache; private readonly shardRefCounts; private readonly pinnedShards; private readonly fanoutRootChannelOptions; private readonly fanoutNodeChannelOptions; private readonly fanoutJoinOptions?; private readonly fanoutPublishRequiresSubscribe; private readonly fanoutPublishIdleCloseMs; private readonly fanoutPublishMaxEphemeralChannels; private autoTopicRootCandidates; private autoTopicRootCandidateSet?; private reconcileShardOverlaysInFlight?; private hostOwnedShardRootsInFlight?; private hostOwnedShardRootsDirty; private autoCandidatesBroadcastTimers; private autoCandidatesGossipInterval?; private autoCandidatesGossipUntil; private _onFanoutPeerUnreachable?; private fanoutChannels; private pendingTopicRootQueries; private nextTopicRootQueryId; constructor(components: TopicControlPlaneComponents, props?: TopicControlPlaneOptions); /** * Return a detached, read-only snapshot of effective runtime settings. */ getRuntimeSnapshot(): TopicControlPlaneRuntimeSnapshot; /** * Configure deterministic topic-root candidates and disable the pubsub "auto" * candidate mode. * * Auto mode is a convenience for small ad-hoc networks where no bootstraps/ * routers are configured. When an explicit candidate set is provided (e.g. * from bootstraps or a test harness), we must stop mutating/gossiping the * candidate set; otherwise shard root resolution can diverge and overlays can * partition (especially in sparse graphs). */ setTopicRootCandidates(candidates: string[]): void; start(): Promise; stop(): Promise; onPeerConnected(peerId: Libp2pPeerId, connection: Connection): Promise; addPeer(peerId: Libp2pPeerId, publicKey: PublicSignKey, protocol: string, connId: string): PeerStreams; private maybeDisableAutoTopicRootCandidatesIfExternallyConfigured; private maybeUpdateAutoTopicRootCandidates; private normalizeAutoTopicRootCandidates; private scheduleAutoTopicRootCandidatesBroadcast; private ensureAutoCandidatesGossipInterval; private sendAutoTopicRootCandidates; private mergeAutoTopicRootCandidatesFromPeer; private scheduleHostOwnedShardRoots; private scheduleReconcileShardOverlays; private reconcileShardOverlays; private isTrackedTopic; private initializeTopic; private untrackTopic; private initializePeer; private pruneTopicSubscribers; private getSubscriptionOverlap; private clearFanoutIdleClose; private scheduleFanoutIdleClose; private touchFanoutChannel; private evictEphemeralFanoutChannels; private getShardTopicForUserTopic; /** * Serialize a control-plane message (borsh `PubSubMessage`); rust-core * mode encodes natively (byte-identical, covered by the parity suite). */ private encodePubSubMessage; /** * Parse a control-plane payload; rust-core mode decodes natively into the * same message classes (decode failures throw either way, so callers keep * their fallback behavior). */ private decodePubSubMessage; private resolveTopicRootState; resolveTopicRoot(topic: string): Promise; private resolveShardRoot; private getConnectedTopicRootTrackers; private nextTopicRootRequestIdValue; private sendDirectControlMessage; private resolvePendingTopicRootQuery; private queryTopicRootFromPeer; private confirmDirectShardRoot; private resolveQueryableTopicRoot; private resolveTopicRootThroughPeers; private ensureFanoutChannel; private closeFanoutChannel; hostShardRootsNow(): Promise; subscribe(topic: string): Promise; private _subscribe; unsubscribe(topic: string, options?: { force?: boolean; data?: Uint8Array; }): Promise; private _announceUnsubscribe; private announcePeerUnavailable; private announcePeerUnavailableOnShard; private onFanoutPeerUnreachable; getSubscribers(topic: string): PublicSignKey[] | undefined; /** * Returns best-effort route hints for a target peer by combining: * - DirectStream ACK-learned routes * - Fanout route tokens for the topic's shard overlay */ getUnifiedRouteHints(topic: string, targetHash: string): RouteHint[]; requestSubscribers(topic: string | string[], to?: PublicSignKey): Promise; publish(data: Uint8Array | undefined, options?: { topics: string[]; } & { client?: string; } & { mode?: SilentDelivery | AcknowledgeDelivery; } & PriorityOptions & ResponsePriorityOptions & ExpiresAtOptions & IdOptions & WithExtraSigners & { signal?: AbortSignal; }): Promise; /** * Publish a pre-encoded `PubSubData` payload to explicit recipients. * * `payload` must be the exact borsh serialization of * `new PubSubData({ topics, data, strict: true })` — callers that encode * the payload elsewhere (e.g. the shared-log fused send path serializing * inside wasm) hand the finished bytes here so the wrapping `PubSubData` * copy of the regular `publish` path is skipped. Everything else matches * the explicit-recipient branch of {@link publish}: the payload becomes a * signed `DataMessage` delivered over DirectStream. */ publishPreEncodedData(payload: Uint8Array, properties: { topics: string[]; }, options: { mode: SilentDelivery | AcknowledgeDelivery; } & { client?: string; } & PriorityOptions & ResponsePriorityOptions & ExpiresAtOptions & IdOptions & WithExtraSigners & { signal?: AbortSignal; }): Promise; onPeerSession(key: PublicSignKey, _session: number): void; onPeerUnreachable(publicKeyHash: string): void; private collectSubscriptionState; private removeSubscriptionsBeforeSession; private removeSubscriptions; private subscriptionStateIsLatest; /** * `Subscribe` replaces tracked subscription data for new subscribers or * strictly newer sessions; equal/older sessions only refresh cache order. */ private subscribeShouldReplace; private subscriptionMessageIsLatest; /** * Answer Subscribe{requestSubscribers} announces without letting bursts go * quadratic (#983). Leading edge: the first announcer is answered * immediately with a targeted unicast — a lone joiner sees no added * latency. Announcers arriving within the trailing window that this opens * are batched and answered with ONE broadcast, instead of S(S-1) routed * unicasts whose cold route resolutions flood the shard tree. */ private queueSubscriberResponse; private flushSubscriberResponses; private sendFanoutUnicastOrBroadcast; private processDirectPubSubMessage; private processShardPubSubMessage; onDataMessage(from: PublicSignKey, stream: PeerStreams, message: DataMessage, seenBefore: number): Promise; } export declare const waitForSubscribers: (libp2p: { services: { pubsub: PubSub; }; }, peersToWait: PeerId | PeerId[] | { peerId: Libp2pPeerId; } | { peerId: Libp2pPeerId; }[] | string | string[], topic: string, options?: { signal?: AbortSignal; timeout?: number; }) => Promise; //# sourceMappingURL=index.d.ts.map