Migrating from v11 to v12
ThetaDataDx v12 reshapes the FPSS streaming surface, curates the Rust public API behind an __internal feature gate, and removes the REST fallback path. The full diff is in the 12.0.0 changelog entry. This guide walks the changes a downstream caller actually has to make.
TL;DR
| Surface | Change | Migration |
|---|---|---|
| Cargo pin | thetadatadx = "11" → "12" | Bump [dependencies] in Cargo.toml |
| Python pin | thetadatadx>=11,<12 → >=12,<13 | Bump pyproject.toml / requirements.txt |
| npm pin | "thetadatadx": "^11" → "^12" | Bump package.json; the prebuilt napi binding follows |
| C++ pin | v11.x.x tag → v12.0.0 tag | Re-fetch the libthetadatadx_ffi artifact and updated headers |
| FPSS streaming (Rust) | FpssClient::connect(args) + FpssEventPoller | FpssClient::builder(&creds, &hosts).build()? + drain on FpssClient itself |
| FPSS streaming (Python) | client.streaming_iter() / streaming_async() | client.start_streaming(cb) or with client.streaming(cb): ... |
| FPSS streaming (TS) | client.startStreamingIter() | client.startStreaming(cb) |
| FPSS streaming (C ABI) | tdx_unified_start_streaming_iter + iter API | tdx_unified_set_callback |
| REST fallback | Config::with_rest_fallback(...) | Removed — the library speaks the gRPC + FPSS + flat-file wire protocols directly |
| Decode pipeline knobs | decode_threads / decode_queue_depth / decoder_ring_size (every binding) | Removed — delete the setter calls; concurrency is sized by concurrent_requests alone |
| Engine internals | use thetadatadx::mdds::... from a downstream crate | Gated behind features = ["__internal"]; not a stable surface |
FPSS streaming reshape
The v11 split between a builder for setup and a separate FpssEventPoller for drain collapses into a single FpssClient. Construction goes through a fluent builder; every drain primitive (next_event, try_next_event, poll_batch, for_each, Iterator for &FpssClient) lives on the client itself.
Rust
// v11
let args = FpssConnectArgs { creds, hosts, ring_size: 8192, ... };
let (client, poller) = FpssClient::connect(args)?;
for event in &poller {
handle(event);
}
// v12
let client = FpssClient::builder(&creds, &hosts)
.ring_size(8192)
.flush_mode(FpssFlushMode::Batched)
.build()?;
for event in &client {
handle(event);
}The builder returns Result<FpssClient, FpssError> (#[non_exhaustive]). From<FpssError> for Error maps each variant losslessly into a distinct umbrella Error variant; the docstring on FpssError lists every row plus the two known sources of information loss (io::ErrorKind collapse on Io round-trip, Config.field regeneration).
Python
The pull-iterator surface (streaming_iter, streaming_async, streaming_async_batches, plus their session pyclasses and the BackpressurePolicy knob) is removed. Migrate every recipe to the push-callback shape.
# v11 — pull
with client.streaming_iter() as it:
for event in it:
handle(event)
# v12 — push callback (one-shot)
client.start_streaming(handle)
# ... do other work ...
client.stop_streaming()
# v12 — context manager (recommended for blocking scripts)
import threading
with client.streaming(handle):
threading.Event().wait() # blocks until Ctrl-CTypeScript
// v11
const iter = client.startStreamingIter();
for await (const event of iter) {
handle(event);
}
// v12
client.startStreaming(handle);
// ... do other work ...
client.stopStreaming();C ABI
/* v11 */
TdxFpssEventIterator* iter = tdx_unified_start_streaming_iter(client);
while (tdx_fpss_event_iter_next(iter, &event)) { handle(event); }
tdx_fpss_event_iter_close(iter);
tdx_fpss_event_iter_free(iter);
/* v12 */
tdx_unified_set_callback(client, on_event, user_data);
/* lifecycle restriction: do not call tdx_unified_free / tdx_fpss_free
* from inside the callback */REST fallback removed
Config::with_rest_fallback, the FallbackPolicy enum, every option_history_*_with_fallback shim, and the matching C ABI / C++ / Python / TypeScript surfaces are gone. The SDK now uses ThetaData's historical gRPC endpoint, the FPSS streaming TCP feed, and the native flat-file distribution directly. Callers that previously opted into the REST escape hatch can either:
- Drop the
with_rest_fallbackcall entirely and rely on the native transports (recommended; covers every endpoint with no behavioural loss). - Continue using the standalone
tools/serverbinary, which keeps its terminal-compatible HTTP / WebSocket front-end for existing consumers.
Decode pipeline knobs removed
The two-stage decode pipeline these knobs tuned no longer exists: per-chunk decode runs inline on each request task, and the only concurrency control is concurrent_requests. decode_threads, decode_queue_depth, and decoder_ring_size are removed from every binding — delete the setter calls (and any getter reads); there is no replacement value to migrate.
- Rust:
config.mdds.decode_threads = Some(8);→ delete; size withconfig.mdds.concurrent_requestsinstead. - Python:
cfg.decode_threads = 8→ delete; keepcfg.concurrent_requests. - TypeScript:
cfg.setDecodeThreads(8)→ delete; keepcfg.setConcurrentRequests(8). - C ABI:
tdx_config_set_decode_threads(cfg, 8)→ delete; keeptdx_config_set_concurrent_requests(cfg, 8). - C++:
cfg.set_decode_threads(8)→ delete; keepcfg.set_concurrent_requests(8).
The same applies to the decode_queue_depth and decoder_ring_size variants of each setter, including the _explicit C ABI forms.
Engine internals are now gated
The thetadatadx crate now publishes a curated surface. The mdds, endpoint, decode, wire, EndpointArgs, ENDPOINTS, MddsClient, and SubscriptionTier re-exports are gated behind features = ["__internal"] and marked #[doc(hidden)]. Downstream crates that imported them directly should either use the public surface (every endpoint is wired through methods on ThetaDataDxClient) or pin the __internal feature explicitly with the understanding that those symbols are not covered by the semver contract.
Default behaviour changes
Contract.symbol(Rust) isArc<str>instead ofString. Field access through&str,PartialEq<str>,Display, and slicing continues to work; deep-clone call sites should switch toArc::cloneto benefit from the interned symbol cache.start_streaming/reconnect_streamingserialise through a single-flight lifecycle lock. Calls from multiple threads no longer race; one waits for the other to finish.tdx_config_set_flush_mode(C ABI) now returnsint32_t(wasvoid). Pass0for Batched or1for Immediate; any other value returns-1and setstdx_last_error/tdx_last_error_code = TDX_ERR_CONFIG. The C++ wrapper throwsstd::runtime_errorinternally.
See also
- 12.0.0 changelog entry — exhaustive list of every change.
- Streaming guide — push-callback API reference.
- Historical v9 → v10 migration — earlier history.