Background
The Rust vox-core API uses a clean fluent builder pattern for session establishment:
// Initiator
let client: EchoClient = initiator_on(link, TransportMode::Bare)
.metadata(metadata)
.on_connection(my_acceptor)
.keepalive(SessionKeepaliveConfig { ... })
.resumable()
.establish::<EchoClient>()
.await?;
// Acceptor
let guard = acceptor_on(link)
.on_connection(my_acceptor)
.resumable()
.session_registry(registry.clone())
.establish::<NoopClient>()
.await?;
Two entry points (initiator_on / acceptor_on), each returning a builder. Options are set by chaining methods. The terminal method is always .establish() (or .establish_or_resume() on the acceptor side).
Current Swift situation
Swift has a Session class with 14+ static factory methods covering every combination of:
- Input type:
SessionConnector vs raw Link vs raw LinkAttachment
- Typed vs untyped
ExpectedRootClient
- Fresh establish vs establish-or-resume
This is the combinatorial explosion that the Rust builder pattern avoids entirely.
// Current — pick from 14 overloads
Session.acceptor(connector, dispatcher: d, onConnection: acc, resumable: true, metadata: m)
Session.acceptorOrResume(connector, registry: r, dispatcher: d, ...)
Session.acceptFreshLink(link, conduit: .bare, dispatcher: d, ...)
Session.acceptFreshLinkOrResume(link, conduit: .bare, registry: r, dispatcher: d, ...)
Session.acceptFreshAttachment(attachment, conduit: .bare, dispatcher: d, ...)
Session.acceptFreshAttachmentOrResume(attachment, conduit: .bare, registry: r, ...)
// ... and 8 more typed/untyped variants of the above
Proposed Swift builder API
// Entry points (free functions, like Rust)
public func initiator(_ source: some LinkSource) -> InitiatorBuilder
public func acceptor(_ source: some LinkSource) -> AcceptorBuilder
// Chainable options (both builders)
.dispatcher(_ d: any ServiceDispatcher) -> Self
.onConnection(_ a: any ConnectionAcceptor) -> Self // already implemented!
.keepalive(_ k: SessionKeepaliveConfig) -> Self
.resumable() -> Self
.metadata(_ m: [MetadataEntry]) -> Self
// Acceptor-only
.registry(_ r: SessionRegistry) -> Self
// Terminal
.establish() async throws -> Session
.establishOrResume() async throws -> SessionAcceptOutcome // acceptor only
Call sites would become:
// Subject server
let session = try await acceptor(TcpAcceptor(host: host, port: port))
.dispatcher(TestbedDispatcher(handler: handler))
.onConnection(DefaultConnectionAcceptor(dispatcher: ...))
.resumable()
.establish()
// Subject client
let session = try await initiator(TcpConnector(host: host, port: port))
.dispatcher(TestbedDispatcher(handler: handler))
.resumable()
.establish()
Input type unification
The three input types (SessionConnector, raw Link, raw LinkAttachment) can be unified by making entry points accept some LinkSource — TcpConnector, TcpAcceptor, UnixConnector all already conform to LinkSource. Raw links can be wrapped with the existing singleLinkSource(_:) helper.
What's already done (prerequisites merged)
- ✅
ConnectionAcceptor / ConnectionRequest / PendingConnection protocol added (commit 1c633bd)
- ✅
acceptConnections: Bool replaced with onConnection: (any ConnectionAcceptor)?
- ✅
NoopClient (was NoopRootClient), SessionKeepaliveConfig (was DriverKeepaliveConfig) renamed
- ✅
establishInitiator/establishAcceptor made internal
- ✅
TcpAcceptor added to VoxRuntime
What this issue tracks
- Design and implement
InitiatorBuilder and AcceptorBuilder structs in VoxRuntime
- Replace the 14+
Session static factory methods with the two builder entry points
- Update all call sites in
Subject.swift, Server.swift, and tests
- Delete the now-redundant
Session static methods (or deprecate them if source compatibility matters)
- The
Session class itself stays — it is the result of .establish() and holds rootConnection, driver, handle, peerMetadata, and run()
Background
The Rust
vox-coreAPI uses a clean fluent builder pattern for session establishment:Two entry points (
initiator_on/acceptor_on), each returning a builder. Options are set by chaining methods. The terminal method is always.establish()(or.establish_or_resume()on the acceptor side).Current Swift situation
Swift has a
Sessionclass with 14+ static factory methods covering every combination of:SessionConnectorvs rawLinkvs rawLinkAttachmentExpectedRootClientThis is the combinatorial explosion that the Rust builder pattern avoids entirely.
Proposed Swift builder API
Call sites would become:
Input type unification
The three input types (
SessionConnector, rawLink, rawLinkAttachment) can be unified by making entry points acceptsome LinkSource—TcpConnector,TcpAcceptor,UnixConnectorall already conform toLinkSource. Raw links can be wrapped with the existingsingleLinkSource(_:)helper.What's already done (prerequisites merged)
ConnectionAcceptor/ConnectionRequest/PendingConnectionprotocol added (commit 1c633bd)acceptConnections: Boolreplaced withonConnection: (any ConnectionAcceptor)?NoopClient(wasNoopRootClient),SessionKeepaliveConfig(wasDriverKeepaliveConfig) renamedestablishInitiator/establishAcceptormade internalTcpAcceptoradded to VoxRuntimeWhat this issue tracks
InitiatorBuilderandAcceptorBuilderstructs in VoxRuntimeSessionstatic factory methods with the two builder entry pointsSubject.swift,Server.swift, and testsSessionstatic methods (or deprecate them if source compatibility matters)Sessionclass itself stays — it is the result of.establish()and holdsrootConnection,driver,handle,peerMetadata, andrun()