ThetaDataDx 12.0.0
Major release. The FPSS streaming surface is reshaped around a fluent builder, a typed FpssError enum, and an iterator on the client itself. The three earlier delivery modes (push callback over a queue, pull iterator over a queue, direct ring poller) collapse to one primitive. The companion tdbe crate is unchanged at 0.14.0.
Public surface
let client = FpssClient::builder(&creds, &hosts)
.ring_size(8192)
.read_timeout_ms(15_000)
.build()?;
client.subscribe(Contract::stock("AAPL").quote())?;
for event in &client {
match event? {
FpssEvent::Data(data) => { /* ... */ }
FpssEvent::Control(control) => { /* ... */ }
}
}Drain can also be driven via client.next_event() (blocking), client.try_next_event() (non-blocking), client.poll_batch(|event| ...) (non-blocking single batch through a closure), or client.for_each(|event| ...) (blocking loop until the ring shuts down).
Breaking changes
FpssClient::builder(creds, hosts)is the sole public constructor. The previousFpssClient::connect(args)entry point and theFpssConnectArgsstruct-literal arg bundle are crate-internal.- Drain primitives live on
FpssClientitself:next_event,try_next_event,poll_batch,for_each, andIterator for &FpssClient. TheFpssEventPollertype and itsrun/poll_batchmethods are removed. - New typed
FpssErrorenum (#[non_exhaustive]) returned by the builder, the polling methods, and the iterator.From<FpssError> for Errormaps each variant losslessly into the umbrellaErrortype (auth →Error::Auth, config →Error::Config, io →Error::Io, dispatcher → taggedError::Fpss); the docstring onFpssErrorlists every row of the table plus the two known sources of information loss (io::ErrorKindcollapse,Config.fieldregeneration). - The unified
ThetaDataDxClient::start_streaming(callback)and the Python / TypeScript / FFI bindings preserve the push-callback shape. Each binding owns its own internal dispatcher thread and panic-detection flag; no supervisor handle is exposed on the FPSS public surface. ThetaDataDxClient::start_streaming_iter,start_streaming_iter_with_wake, andstart_streaming_iter_with_wake_policyare removed.- Python:
EventIterator,StreamingIterSession,StreamingAsyncSession,StreamingAsyncBatchesSession, andBackpressurePolicypyclasses removed.streaming_iter(),streaming_async(), andstreaming_async_batches()onThetaDataDxClientand the standaloneFpssClientpyclass are gone. Thestart_streaming(callback)push surface and thestreaming(callback)context manager remain. - TypeScript:
EventIteratornapi class removed.client.startStreamingIter()is gone.client.startStreaming(cb)is preserved. - C ABI:
TdxFpssEventIterator,tdx_unified_start_streaming_iter,tdx_fpss_event_iter_next,tdx_fpss_event_iter_close, andtdx_fpss_event_iter_freeremoved.tdx_fpss_set_callbackandtdx_unified_set_callbackremain. - Crate dependency removed:
crossbeam-queue.
Streaming
Contract.symboltype changed fromStringtoArc<str>. The field derefs to&strtransparently, so existing call sites continue to work. The Rust decode path interns the symbol bytes inside the per-session contract cache, so a session that streams a sustained subscription set allocates once per unique symbol for the lifetime of the session instead of once per event on the Rust side. Python and TypeScript bindings still copy the string at the language boundary — that copy is unavoidable for the runtime's own string ownership — so SDK-surfaceContract.symboltypes remainStringand are populated via.to_string()on each delivered event.
Migration
Rust direct callers:
// Earlier (legacy):
let (client, iter) = FpssClient::connect_iter(args)?;
for event in iter { handle(event); }
// 12.0.0 — iterator on the client itself:
let client = FpssClient::builder(&creds, &hosts).build()?;
for event in &client {
let event = event?;
handle(&event);
}
// 12.0.0 — non-blocking batch drain:
let client = FpssClient::builder(&creds, &hosts).build()?;
loop {
match client.poll_batch(|event| handle(event)) {
PollOutcome::Drained(_) => { /* re-poll or yield */ }
PollOutcome::Shutdown => break,
}
}
// 12.0.0 — blocking dedicated thread:
let client = FpssClient::builder(&creds, &hosts).build()?;
let client_for_dispatch = std::sync::Arc::new(client);
let arc_for_thread = std::sync::Arc::clone(&client_for_dispatch);
std::thread::spawn(move || {
for event in &*arc_for_thread {
match event {
Ok(event) => handle(&event),
Err(_) => break,
}
}
});Embedders that exposed a push-callback over the legacy poller.run(...) shape now spawn their own dispatcher thread that drains for event in &*client_arc { ... } and invokes the user callback per event. Catch panics inside the dispatcher; surface the failed state through whatever observability flag the binding already exposes.
Internals
io_loopis structurally simpler: single producer wiring throughbuild_poller_producer. The earlierbuild_consumer_producer/ConsumerProducerArgsmachinery and thepush_with_blockqueue helper are gone.- Connect path validates configuration up front and returns the fully-assembled
FpssClient(no separate poller handle). - The earlier queue-backed soak suite is retired; the poller-backed drain semantics are covered by the workspace
cargo testgauntlet.
Removed
- REST fallback escape hatch (
Config::with_rest_fallback,FallbackPolicy,option_history_*_with_fallback). The library now speaks ThetaData's historical gRPC endpoint, streaming TCP feed, and the native flat-file distribution directly — no HTTP fallback path.
Unchanged
tdbestays at0.14.0; no API or wire-schema change.- Historical pipeline, FLATFILES, auth, and config surfaces are unchanged.
What's Changed
- feat!: curated Rust SDK + FPSS streaming reshape + cross-binding parity sweep by @aukaost in #652
- docs: correct release-notes naming and refresh streaming docstrings by @userFRM in #655
- fix(tools): install the rustls provider at CLI and MCP startup by @userFRM in #658
- fix(server): legacy-terminal behavior parity for the HTTP and WebSocket surfaces by @userFRM in #656
- feat(fpss): connection-resilience hardening for multi-minute upstream outages by @userFRM in #659
- bench(grpc): closed-loop transport comparison harness and measured results by @userFRM in #661
- feat(grpc)!: migrate the MDDS transport to the reference Rust gRPC stack by @userFRM in #664
- chore(deps-dev): bump vue from 3.5.34 to 3.5.38 in /docs-site in the patch-minor group across 1 directory by @dependabot[bot] in #622
- chore(deps-dev): bump @napi-rs/cli from 3.6.2 to 3.7.1 in /sdks/typescript in the patch-minor group across 1 directory by @dependabot[bot] in #653
- chore(deps): bump the patch-minor group across 1 directory with 11 updates by @dependabot[bot] in #660
- perf(mdds): schema-validated bulk column extraction in the decode path by @userFRM in #665
- feat(fpss): ring occupancy and capacity observability across bindings by @userFRM in #666
Full Changelog: v11.0.1...v12.0.0