Other clients
Durable Streams has official client libraries in 10 languages. All implement the same protocol and pass the client conformance test suite, ensuring consistent behavior regardless of which language you use.
If your language isn't listed here, you can build your own -- see Building a client.
Common features
All client libraries share the same core capabilities:
- Exactly-once writes --
IdempotentProduceruses(producerId, epoch, seq)tuples for server-side deduplication, safe to retry on any network error - Offset-based resumption -- save the offset returned by the server and pass it back on reconnect to resume from exactly where you left off
- Long-poll and SSE live modes -- choose between HTTP long-polling and Server-Sent Events for real-time tailing (note: PHP supports long-poll only)
- JSON mode with array flattening -- JSON streams automatically handle array batching on writes and flattening on reads
- Automatic retry on transient errors -- configurable exponential backoff for network failures and server errors
Summary
| Language | Install | Maturity |
|---|---|---|
| TypeScript | npm install @durable-streams/client | Production-Proven |
| Python | pip install durable-streams | Expert-Reviewed |
| Go | go get github.com/durable-streams/durable-streams/packages/client-go | Expert-Reviewed |
| Elixir | Add {:durable_streams, "~> 0.1.0"} to mix.exs | Vibe-Engineered |
| C# / .NET | dotnet add package DurableStreams | Vibe-Engineered |
| Swift | Swift Package Manager (DurableStreams) | Vibe-Engineered |
| PHP | composer require durable-streams/client | Vibe-Engineered |
| Java | Maven / Gradle (durable-streams) | Vibe-Engineered |
| Rust | cargo add durable-streams | Vibe-Engineered |
| Ruby | gem install durable_streams | Vibe-Engineered |
Maturity levels
Each client follows a maturity progression:
- Vibe-Engineered -- implements the core protocol, passes the conformance test suite, and has basic documentation. API may change based on ecosystem feedback. Suitable for prototyping and non-critical workloads.
- Expert-Reviewed -- reviewed by a language/ecosystem expert for idiomatic API design, error handling, and performance. Suitable for production use.
- Production-Proven -- used in production by multiple organizations with a track record of stability and active maintenance. The TypeScript client is at this level with 1.5+ years of production use at Electric.
All clients pass the conformance test suite regardless of maturity level. The difference is in API polish, idiomatic patterns, and battle-testing.
If you're an expert in a language with a Vibe-Engineered client, we'd love your help leveling it up. See the Client maturity model for the review process and checklist.
TypeScript
See the dedicated TypeScript client page for installation, read/write examples, and exactly-once producer usage.
Full documentation: TypeScript client
Package README: README
Python
See the dedicated Python client page for sync and async APIs, stream handles, and IdempotentProducer usage.
Full documentation: Python client
Package README: README
Go
go get github.com/durable-streams/durable-streams/packages/client-goclient := durablestreams.NewClient()
stream := client.Stream("https://streams.example.com/my-stream")
it := stream.Read(ctx)
defer it.Close()
for {
chunk, err := it.Next()
if errors.Is(err, durablestreams.Done) {
break
}
fmt.Println(string(chunk.Data))
}- Zero dependencies -- uses only the Go standard library (
net/http) - Iterator-based reads with
it.Next()/Donesentinel pattern - Concurrency-safe
Clientwith optimized HTTP transport and connection pooling IdempotentProducerwith goroutine-based batching and pipelining- Functional options pattern (
WithLive(),WithOffset(),WithContentType())
Full documentation: README
Elixir
Add to your mix.exs:
{:durable_streams, "~> 0.1.0"}alias DurableStreams.Client
alias DurableStreams.Stream, as: DS
client = Client.new("http://localhost:4437")
stream = client |> Client.stream("/my-stream") |> DS.create!()
DS.append_json!(stream, %{event: "hello"})
{:ok, {items, _meta}} = DS.read_json(stream, offset: "-1")
IO.inspect(items)- OTP-native design with
ConsumerandWriterGenServers for supervision tree integration - Pipe-friendly API with bang (
!) and{:ok, _}/{:error, _}variants WriterGenServer for fire-and-forget batched writes with exactly-once deliveryConsumerGenServer with automatic reconnection, exponential backoff, and callback-based processing- No external dependencies -- uses Erlang's built-in
:httpc(optional Finch for SSE)
Full documentation: README
C# / .NET
dotnet add package DurableStreamsusing DurableStreams;
await using var client = new DurableStreamClient(new DurableStreamClientOptions
{
BaseUrl = "https://streams.example.com"
});
var stream = client.GetStream("/my-stream");
await using var response = await stream.StreamAsync(new StreamOptions
{
Offset = Offset.Beginning,
Live = LiveMode.Off
});
var items = await response.ReadAllJsonAsync<MyEvent>();IAsyncEnumerable<T>support for naturalawait foreachconsumption- Thread-safe
DurableStreamClientdesigned for singleton/DI registration IdempotentProducerwithOnErrorevent handler for fire-and-forget writesStreamCheckpointtype for easy offset + cursor persistence- ASP.NET Core integration with
IAsyncEnumerablecontroller actions
Full documentation: README
Swift
Add via Swift Package Manager:
dependencies: [
.package(url: "https://github.com/durable-streams/durable-streams", from: "0.1.0")
]import DurableStreams
let handle = try await DurableStream.connect(
url: URL(string: "https://streams.example.com/my-stream")!
)
for try await event in handle.messages(as: MyEvent.self) {
print(event)
}AsyncSequence-based streaming withfor try awaitsyntaxCodableoffsets for easy persistence toUserDefaultsorKeychain- iOS lifecycle integration with suspend/resume and background flush support
- Batching presets (
.highThroughput,.lowLatency,.disabled) - Dynamic headers via
.provider { await getToken() }closures
Full documentation: README
PHP
composer require durable-streams/clientuse function DurableStreams\stream;
$response = stream([
'url' => 'https://streams.example.com/my-stream',
'offset' => '-1',
]);
foreach ($response->jsonStream() as $event) {
echo json_encode($event) . "\n";
}- Generator-based streaming for memory-efficient consumption with PHP's native
yield IdempotentProducerwith synchronousenqueue()/flush()model- PSR-18 compatible -- use any HTTP client (Guzzle, Symfony, etc.) or the built-in cURL client
- PSR-3 structured logging support
- Note: Long-poll only (no SSE support due to PHP's synchronous execution model)
Full documentation: README
Java
// Gradle
implementation("com.durablestreams:durable-streams:0.1.0")var client = DurableStream.create();
try (var chunks = client.read(url)) {
for (var chunk : chunks) {
System.out.println(chunk.getDataAsString());
}
}- Zero dependencies -- uses only JDK 11+ APIs (
java.net.http.HttpClient) - Type-safe
JsonIterator<T>with pluggable JSON parsers (Gson, Jackson, etc.) AutoCloseableiterators for natural try-with-resources usageCompletableFutureasync variants for all operations- Thread-safe
DurableStreamclient andIdempotentProducer
Full documentation: README
Rust
Add to your Cargo.toml:
[dependencies]
durable-streams = "0.1"use durable_streams::{Client, Offset};
let client = Client::new();
let stream = client.stream("https://streams.example.com/my-stream");
let mut reader = stream.read().offset(Offset::Beginning).build();
while let Some(chunk) = reader.next_chunk().await? {
println!("{:?}", String::from_utf8_lossy(&chunk.data));
}- Builder pattern for client, reader, and producer configuration
Producerwith fire-and-forgetappend()/append_json()andon_errorcallback- Feature flags for TLS backend (
rustlsdefault, optionalnative-tls) andtracingintegration - Tokio-based async runtime
- Uses
reqwestunder the hood with connection pooling
Full documentation: README
Ruby
gem install durable_streamsrequire 'durable_streams'
stream = DurableStreams.create("/my-stream", content_type: :json)
stream << { event: "hello" }
stream.read.each { |msg| puts msg }- Idiomatic Ruby with
Enumerableintegration,each/each_batch, and<<shovel operator Producer.openblock form for automatic flush/close on exit- Lazy enumerator support (
stream.read(live: :sse).each.lazy.take(10).to_a) - Global configuration with
DurableStreams.configureand isolated contexts for multi-tenant use - Built-in testing utilities with mock transport for RSpec/Minitest
Full documentation: README