🟢 Beta SDK — This SDK is feature-complete with tests and CI for iOS/macOS. For browser/WebAssembly use cases, also see the WASM SDK. Contributions welcome!
Swift client SDK for Streamline — The Redis of Streaming.
- Swift 5.9+
- iOS 15+ / macOS 13+
- Streamline server 0.2.0 or later
Add the dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/streamlinelabs/streamline-swift-sdk.git", from: "0.2.0"),
]import StreamlineSDK
let config = StreamlineConfiguration(
url: URL(string: "ws://localhost:9092")!,
authToken: "my-token"
)
let client = StreamlineClient(configuration: config)
client.connect()
// Produce a message
try client.produce(topic: "events", key: "user-1", stringValue: "{\"action\":\"click\"}")
// Subscribe to a topic
client.subscribe(topic: "events") { message in
print("Received: \(String(data: message.value, encoding: .utf8) ?? "")")
}
// Disconnect when done
client.disconnect()let client = StreamlineClient(configuration: config)
try await client.connect()
try await client.beginTransaction()
do {
try await client.produce(topic: "orders", key: "k1", value: "v1")
try await client.produce(topic: "orders", key: "k2", value: "v2")
try await client.commitTransaction()
} catch {
try await client.abortTransaction()
throw error
}Note: Transactions use client-side buffering. Messages are collected and sent as a batch on commit, providing all-or-nothing delivery at the client level.
The AdminClient communicates with the Streamline HTTP REST API (port 9094) for topic management, consumer group inspection, and SQL queries.
let admin = AdminClient(baseURL: URL(string: "http://localhost:9094")!, authToken: "my-token")
// Topic management
try await admin.createTopic(name: "events", partitions: 3)
let topics = try await admin.listTopics()
let details = try await admin.describeTopic(name: "events")
try await admin.deleteTopic(name: "old-topic")
// Consumer groups
let groups = try await admin.listConsumerGroups()
let groupDetails = try await admin.describeConsumerGroup(groupId: "my-group")
// SQL queries
let result = try await admin.query("SELECT * FROM events LIMIT 10")
for row in result.rows { print(row) }
// Server info
let info = try await admin.serverInfo()
print("Version: \(info.version), Topics: \(info.topicCount)")let admin = AdminClient(baseURL: URL(string: "http://localhost:9094")!)
// Cluster overview
let cluster = try await admin.clusterInfo()
print("Cluster: \(cluster.clusterId), Brokers: \(cluster.brokers.count)")
// Consumer group lag monitoring
let lag = try await admin.consumerGroupLag(groupId: "my-group")
print("Total lag: \(lag.totalLag)")
for p in lag.partitions { print(" \(p.topic):\(p.partition) lag=\(p.lag)") }
// Message inspection
let messages = try await admin.inspectMessages(topic: "events", partition: 0, limit: 10)
for m in messages { print("offset=\(m.offset) value=\(m.value)") }
// Latest messages
let latest = try await admin.latestMessages(topic: "events", count: 5)
// Server metrics
let metrics = try await admin.metricsHistory()
for m in metrics { print("\(m.name)=\(m.value)") }
// Offset management
let dryRun = try await admin.resetOffsetsDryRun(groupId: "my-group", topic: "events")
try await admin.resetOffsets(groupId: "my-group", topic: "events")Consume messages using Swift's structured concurrency with for await:
// Stream messages as an AsyncStream
for await message in client.messages(topic: "events") {
let value = String(data: message.value, encoding: .utf8) ?? ""
print("Key: \(message.key ?? "nil"), Value: \(value)")
}The SDK includes a full Schema Registry client:
let registry = SchemaRegistryClient(baseURL: URL(string: "http://localhost:9094")!)
// Register a schema
let id = try await registry.registerSchema(
subject: "events-value", schema: avroJson, format: .avro
)
// Retrieve the latest schema
let schema = try await registry.getLatestSchema(subject: "events-value")
print("Version: \(schema.version), Type: \(schema.schemaType)")
// Check compatibility
let compatible = try await registry.checkCompatibility(
subject: "events-value", schema: newSchema, format: .avro
)
// List subjects and versions
let subjects = try await registry.listSubjects()
let versions = try await registry.listVersions(subject: "events-value")Supports AVRO, PROTOBUF, and JSON schema formats.
let config = StreamlineConfiguration(
url: URL(string: "wss://streamline.example.com:9092")!,
tls: TlsConfig(enabled: true, caCertificatePath: "/etc/ssl/ca.pem")
)let config = StreamlineConfiguration(
url: URL(string: "ws://streamline.example.com:9092")!,
sasl: SaslConfig(mechanism: .scramSha256, username: "admin", password: "secret")
)let config = StreamlineConfiguration(
url: URL(string: "ws://localhost:9092")!,
producerConfig: ProducerConfig(batchSize: 32768, compression: .lz4, acks: .all),
consumerConfig: ConsumerConfig(groupId: "my-app", autoCommit: false, autoOffsetReset: .earliest)
)- WebSocket connection to Streamline server
- Admin client — topic CRUD, consumer groups, SQL queries via HTTP REST API
- Schema Registry — register, retrieve, and validate schemas (Avro, Protobuf, JSON)
- Security — TLS encryption and SASL authentication (PLAIN, SCRAM-SHA-256/512)
- Producer/Consumer config — batching, compression, acknowledgments, consumer groups
- AsyncStream consumption — idiomatic
for awaitstreaming with automatic lifecycle - Telemetry — pluggable tracing with W3C Trace Context propagation
- Auto-reconnect with exponential backoff
- Offline message queue — messages produced while disconnected are buffered and sent on reconnect
- Delegate pattern for connection lifecycle events
- Sendable-safe types for structured concurrency
| Parameter | Default | Description |
|---|---|---|
url |
(required) | WebSocket URL of the Streamline server |
autoReconnect |
true |
Automatically reconnect on disconnection |
maxRetries |
10 |
Maximum reconnection attempts |
timeout |
30 |
Connection timeout in seconds |
authToken |
nil |
Optional bearer token for authentication |
tls |
nil |
TLS configuration (see Security) |
sasl |
nil |
SASL authentication (see Security) |
producerConfig |
defaults | Producer tuning (see Producer & Consumer Configuration) |
consumerConfig |
defaults | Consumer tuning (see Producer & Consumer Configuration) |
initialBackoff |
0.5 |
Initial reconnection backoff (seconds) |
maxBackoff |
30 |
Maximum backoff cap (seconds) |
All SDK errors are represented by the StreamlineError enum. Each case provides context about the failure:
| Error | Description | Retryable? |
|---|---|---|
.notConnected |
Client is not connected to the server | Yes — reconnects automatically |
.connectionFailed(String) |
Connection attempt failed with reason | Yes — retry with backoff |
.authenticationFailed(String) |
Server rejected credentials | No |
.timeout |
Operation timed out | Yes |
.topicNotFound(String) |
Requested topic does not exist | No — create the topic first |
.serializationError(String) |
Message encoding/decoding failed | No |
.offlineQueueFull |
Offline buffer capacity exceeded | No — reduce send rate |
.adminOperationFailed(String) |
Admin API call failed | Depends on cause |
.queryFailed(String) |
SQL query execution failed | Depends on cause |
.schemaRegistryError(String) |
Schema registry operation failed | Depends on cause |
do {
try client.produce(topic: "my-topic", key: "key", value: Data("value".utf8))
} catch let error as StreamlineError {
switch error {
case .notConnected:
print("Not connected — messages are queued offline")
case .connectionFailed(let reason):
print("Connection failed: \(reason)")
case .topicNotFound(let name):
print("Topic not found: \(name)")
case .timeout:
print("Operation timed out — consider increasing timeout")
case .offlineQueueFull:
print("Offline queue full — reduce send rate or increase queue size")
default:
print("Error: \(error.localizedDescription)")
}
}The Swift SDK automatically retries failed sends with exponential backoff when ProducerConfig.retries > 0 (default: 3). Configure retry behavior:
let config = ProducerConfig(
retries: 5, // Max retry attempts
retryBackoffMs: 200 // Base backoff (doubles each attempt)
)
let client = StreamlineClient(
configuration: StreamlineConfiguration(url: wsURL),
producerConfig: config
)Protect your application from cascading failures when the Streamline server is unresponsive:
import StreamlineSDK
let breaker = CircuitBreaker(config: CircuitBreakerConfig(
failureThreshold: 5, // Open after 5 consecutive failures
successThreshold: 2, // Close after 2 half-open successes
openTimeout: 30.0 // 30s before probing
))
if breaker.check() {
do {
try client.produce(topic: "events", key: "user-1", stringValue: "payload")
breaker.recordSuccess()
} catch {
breaker.recordFailure()
throw error
}
}When the circuit is open, check() returns false and operations should be skipped. See the Circuit Breaker guide for details.
The examples/ directory contains runnable examples:
| Example | Description |
|---|---|
| BasicUsage.swift | Produce, consume, and admin operations |
| QueryUsage.swift | SQL analytics with the embedded query engine |
| SchemaRegistryUsage.swift | Schema registration and validation |
| CircuitBreakerUsage.swift | Resilient production with circuit breaker |
| SecurityUsage.swift | TLS and SASL authentication |
⚠️ Experimental — These features require Streamline server 0.3.0+ with moonshot feature flags enabled.
Query topics by meaning instead of offset. Requires a topic created with semantic.embed=true.
let results = try await client.search(topic: "logs.app", query: "payment failure", k: 10)
for hit in results {
print("[p\(hit.partition)] offset=\(hit.offset) score=\(String(format: "%.2f", hit.score))")
}Verify cryptographic provenance attestations attached to records by data contracts.
import StreamlineSDK
let verifier = try StreamlineVerifier(publicKey: publicKeyData)
let result = try verifier.verify(record: record)
print("Verified: \(result.verified), Producer: \(result.producerId)")Use Streamline as persistent memory for AI agents via the MCP protocol.
let memory = MemoryClient(baseURL: URL(string: "http://localhost:9094/mcp/v1")!)
try await memory.remember("user prefers dark mode", tags: ["preferences"])
let results = try await memory.recall("user preferences", k: 5)Create topic branches for replay, A/B testing, or counterfactual analysis.
let branch = try await admin.createBranch(topic: "events", name: "experiment-v2")
for await msg in client.messages(topic: branch.topic) {
process(msg)
}Contributions are welcome! This is a community-maintained SDK. Please see the organization contributing guide for guidelines.
Apache-2.0