Skip to content

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

SurfaceChangeMigration
Cargo pinthetadatadx = "11""12"Bump [dependencies] in Cargo.toml
Python pinthetadatadx>=11,<12>=12,<13Bump pyproject.toml / requirements.txt
npm pin"thetadatadx": "^11""^12"Bump package.json; the prebuilt napi binding follows
C++ pinv11.x.x tag → v12.0.0 tagRe-fetch the libthetadatadx_ffi artifact and updated headers
FPSS streaming (Rust)FpssClient::connect(args) + FpssEventPollerFpssClient::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 APItdx_unified_set_callback
REST fallbackConfig::with_rest_fallback(...)Removed — the library speaks the gRPC + FPSS + flat-file wire protocols directly
Decode pipeline knobsdecode_threads / decode_queue_depth / decoder_ring_size (every binding)Removed — delete the setter calls; concurrency is sized by concurrent_requests alone
Engine internalsuse thetadatadx::mdds::... from a downstream crateGated 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

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.

python
# 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-C

TypeScript

typescript
// v11
const iter = client.startStreamingIter();
for await (const event of iter) {
    handle(event);
}

// v12
client.startStreaming(handle);
// ... do other work ...
client.stopStreaming();

C ABI

c
/* 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_fallback call entirely and rely on the native transports (recommended; covers every endpoint with no behavioural loss).
  • Continue using the standalone tools/server binary, 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 with config.mdds.concurrent_requests instead.
  • Python: cfg.decode_threads = 8 → delete; keep cfg.concurrent_requests.
  • TypeScript: cfg.setDecodeThreads(8) → delete; keep cfg.setConcurrentRequests(8).
  • C ABI: tdx_config_set_decode_threads(cfg, 8) → delete; keep tdx_config_set_concurrent_requests(cfg, 8).
  • C++: cfg.set_decode_threads(8) → delete; keep cfg.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) is Arc<str> instead of String. Field access through &str, PartialEq<str>, Display, and slicing continues to work; deep-clone call sites should switch to Arc::clone to benefit from the interned symbol cache.
  • start_streaming / reconnect_streaming serialise 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 returns int32_t (was void). Pass 0 for Batched or 1 for Immediate; any other value returns -1 and sets tdx_last_error / tdx_last_error_code = TDX_ERR_CONFIG. The C++ wrapper throws std::runtime_error internally.

See also

Released under the Apache-2.0 License.