Expand description
§tailsurf
tailsurf is the supported Rust SDK for tail.surf.
It includes REST operations, resumable SSE reads, and reconnecting WebSocket readers and writers.
§Install
cargo add tailsurf
cargo add tokio --features macros,rt-multi-thread§Quickstart
use tailsurf::{CreateStreamRequest, TsfClient};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = TsfClient::new();
let stream = client.create_stream(&CreateStreamRequest::default()).await?;
println!("{}", stream.stream_id);
Ok(())
}The default API origin is https://tail.surf. Use TsfClient::with_api_origin or TsfClient::with_config for another deployment.
§Read
TsfReadSession reads bounded binary batches. TsfSseReadSession provides the same resumable read contract over HTTP event streams.
use tailsurf::{LinkSecret, ReadOptions, ReadStart, StreamId, TsfClient};
async fn read_stream(
client: &TsfClient,
stream_id: StreamId,
read_link_secret: LinkSecret,
) -> Result<(), Box<dyn std::error::Error>> {
let mut options = ReadOptions::new(stream_id).with_link_secret(read_link_secret);
options.start = Some(ReadStart::SeqNum(0));
let mut reader = client.connect_reader(options).await?;
while let Some(batch) = reader.next_batch().await? {
for record in &batch {
println!("{}", String::from_utf8_lossy(record.data));
}
}
Ok(())
}The session reconnects from the latest record or caught-up position after transient interruption. A successful WebSocket handshake starts a fresh retry burst.
§Write
TsfWriter creates a fresh writer identity and starts its sequence at zero. It retains that identity, acknowledged progress, and unacknowledged records across reconnects. It resends only the unacknowledged suffix.
Retryable interruptions keep recovering until the records are acknowledged. This preserves the exact writer identity, sequence numbers, and payloads needed for logical deduplication. close waits through retryable outages. abort, dropping the writer, or dropping its close future stops recovery.
Records are submitted as a non-empty AppendBatch. The writer assigns writer sequence numbers in submission order, so cloned TsfProducer handles can submit concurrently without interleaving. AppendBatch::split_logical keeps the parts of an oversized logical record contiguous.
An AppendBatch is one sequencing and ticket unit, not an atomic service append. The writer may split it across frames. A terminal failure may leave a durable prefix while its ticket returns an error.
The writer queues submitted input and sends it through a fixed socket window of 1,024 records and 5 MiB of payload. An AppendBatch may be larger than that window.
Await each AppendTicket when you need its durable sequence numbers. A terminal AppendDurabilityUnknown means a non-retryable failure or explicit cancellation left an accepted append without a recovered acknowledgement. Submitting that record under a new writer identity may duplicate it.
use tailsurf::{AppendBatch, DurableWriterOptions, LinkSecret, RecordFormat, StreamId, TsfClient};
async fn write_stream(
client: &TsfClient,
stream_id: StreamId,
write_link_secret: LinkSecret,
) -> Result<(), Box<dyn std::error::Error>> {
let writer = client
.connect_writer(DurableWriterOptions::new(stream_id, write_link_secret))
.await?;
let ticket = writer.submit(AppendBatch::split_logical(
RecordFormat::Transcript,
b"deploy started\n".as_slice(),
)?)?;
let receipts = ticket.await?;
writer.close().await?;
println!("durable at sequence {}", receipts[0].seq_num);
Ok(())
}The complete example creates, writes, reads, and deletes a stream.
§Manage
Management methods require an owner link secret. list_links returns one page. list_all_links follows pagination and validates the complete inventory.
§Retries and errors
REST mutations use idempotency keys. Use create_stream_with_idempotency_key or create_link_with_idempotency_key when a logical creation must survive process restarts.
Transient REST failures, initial connections, and readers use bounded_operation_attempts. The SDK owns a jittered exponential backoff with a 200 ms base and a 2 s cap. An established durable writer uses that schedule without an attempt limit. Operations return TsfClientError. HTTP failures expose the status, request ID, retry hint, structured API code, and sequence mismatch details when the server provides them.
http_request_timeout bounds HTTP requests and SSE opening handshakes. websocket_connect_timeout bounds WebSocket establishment. websocket_progress_timeout bounds authentication, sends, and append acknowledgements. Their defaults are 10 seconds, 10 seconds, and 30 seconds.
Established SSE bodies are not timed out. WebSocket read-idle detection is derived from the protocol heartbeat interval.
§Modules
Common client types are re-exported from the crate root. Lower-level codecs, wire models, URL helpers, permissions, and transcript reconstruction remain available in their named modules.
§License
MIT
Re-exports§
pub use client::AppendAck;pub use client::AppendReceipt;pub use client::AppendTicket;pub use client::DurableWriterOptions;pub use client::IdempotencyKey;pub use client::InvalidIdempotencyKey;pub use client::ListLinksOptions;pub use client::TsfClient;pub use client::TsfClientConfig;pub use client::TsfClientError;pub use client::TsfProducer;pub use client::TsfReadSession;pub use client::TsfSseReadSession;pub use client::TsfWriteSession;pub use client::TsfWriter;pub use client::default_api_origin;pub use ids::ClientWriterId;pub use ids::LinkId;pub use ids::LinkIdError;pub use ids::LinkSecret;pub use ids::LinkSecretError;pub use ids::MAX_LINK_ID_LEN;pub use ids::StreamId;pub use ids::WriterId;pub use permissions::LinkPermissions;pub use permissions::PermissionsError;pub use protocol::read::ReadOptions;pub use protocol::read::ReadStart;pub use protocol::read::ReadStop;pub use protocol::rest::AppendRange;pub use protocol::rest::CreateLinkInput;pub use protocol::rest::CreateStreamRequest;pub use protocol::rest::CreateStreamResponse;pub use protocol::rest::InitialStreamLink;pub use protocol::rest::ListLinksResponse;pub use protocol::rest::MAX_INITIAL_STREAM_LINKS;pub use protocol::rest::StreamLinkCredential;pub use protocol::rest::StreamLinkSummary;pub use protocol::rest::StreamMetadata;pub use protocol::rest::UpdateStreamRequest;pub use protocol::rest::Visibility;pub use protocol::ws::MAX_WRITER_IN_FLIGHT_PAYLOAD_BYTES;pub use protocol::ws::MAX_WRITER_IN_FLIGHT_RECORDS;pub use protocol::ws::WriteSessionOptions;pub use protocol::ws::frame::AppendBatch;pub use protocol::ws::frame::AppendRecord;pub use protocol::ws::frame::CaughtUpPosition;pub use protocol::ws::frame::IntoRecordData;pub use protocol::ws::frame::OwnedReadRecord;pub use protocol::ws::frame::PartHeader;pub use protocol::ws::frame::ReadBatch;pub use protocol::ws::frame::ReadRecord;pub use protocol::ws::frame::RecordFormat;pub use protocol::ws::frame::RecordPayload;pub use stream_title::MAX_STREAM_TITLE_CODE_POINTS;pub use stream_title::StreamTitle;pub use stream_title::StreamTitleError;
Modules§
- client
- REST, SSE, and WebSocket clients and durable writer types. Bounded REST, SSE, and WebSocket clients for the TSF service.
- ids
- Stream and link IDs, link secrets, and writer identities. Identifiers and link secret values used by the TSF API.
- permissions
- Stream-link permission parsing and validation. Canonical owner, read, and write permissions carried by stream links.
- protocol
- TSF read options, REST models, and v1 binary WebSocket protocol types. Language-neutral TSF REST, SSE, and v1 WebSocket models.
- stream_
title - User-provided stream titles. User-provided titles for streams.
- stream_
url - Human-facing stream link parsing and construction.
Parsing and construction for human-facing
/s/{stream_id}stream links. - transcript
- Duplicate suppression and split-record transcript reconstruction.
Logical records over physical TSF records:
split_logical_recordproduces the split-part layout on the write side thatLogicalTranscriptreassembles on the read side.