Skip to content

Interoperability ​

Twilic interoperability requires both sides to agree on version and profile. The current specification family is v3. Rust, Go, JavaScript, and Zig are on the v3 interop line; other published SDKs remain on v2 until their v3 support lands.

The interoperability contract ​

text
Rust encoder  →  [bytes]  →  Go decoder     ✓ (v3)
JS encoder → [bytes] → Zig decoder ✓ (v3)
Python batch → [bytes] → Java decode ✓ (v2)
JS stateful → [bytes] → Rust session ✓ (with matching session state and wire line)

Requirements:

  • Both sides implement the same Twilic version/profile (v3 among Rust/Go/JS/Zig, v2 among the remaining SDKs, or an explicitly negotiated mix)
  • Same profile used (Dynamic decodes Dynamic; session patches need session decoder)
  • Field maps use string keys for cross-language stability

Cross-language smoke test ​

Rust producer → Go consumer ​

rust
// producer.rs
use twilic::{encode, Value};
let value = Value::Map(vec![
("event".into(), Value::String("user.signup".into())),
("user_id".into(), Value::U64(9001)),
]);
let bytes = encode(&value)?;
std::fs::write("event.twl", &bytes)?;
go
// consumer.go
data, _ := os.ReadFile("event.twl")
value, err := twilic.Decode(data)

Works across all 18 official SDKs without a shared schema file in Dynamic mode.

Shared test fixtures ​

The Twilic monorepo maintains cross-language conformance fixtures. Run interop checks per SDK:

bash
# Python
cd twilic-python && ./scripts/check-interop.sh

# Rust (reference)
cd twilic-rust && cargo test

# JavaScript
cd twilic-js && pnpm test

Profile-specific interop ​

ProfileInterop notes
DynamicFull cross-language; no shared state
BatchDecoder must expect batch message kind
BoundEncoder and decoder must share identical schema
StatefulDecoder must maintain matching session; patches are not self-contained

Version bytes ​

Prefix application payloads with a format version for multi-format pipelines:

text
0x03 = Twilic v3 Dynamic
0x02 = Twilic v2 Dynamic
0x01 = legacy MessagePack (during migration)

Decoders branch on the first byte. See Migrating from MessagePack.

v2 vs v3 ​

v3 is a clean break from v2 for Bound Profile field/record-body payloads. Dynamic Profile may remain v2-compatible where tags are unchanged, but implementations that support both versions must use an explicit external profile and version discriminator. Payload auto-detection is not defined.

v1 vs v2 ​

v2 is a clean break. A v2 decoder is not required to decode v1 payloads. Deploy v1 and v2 decoders in parallel during migration:

Common interop failures ​

SymptomCause
Decode error on valid bytesv1 payload sent to v2 decoder
Missing fieldsSchema mismatch (Bound profile)
Patch decode failureSession state not synchronized
Integer type mismatchJS number vs bigint — use bigint for u64/i64
Key order differencesMaps are unordered — compare semantically

Integer handling across languages ​

Languageu64/i64 type
JavaScriptbigint
Pythonint
Gouint64 / int64 via constructors
Rustu64 / i64 enum variants
Javalong / BigInteger depending on SDK

JavaScript encoders must use bigint for values outside Number.MAX_SAFE_INTEGER.

Released under the CC-BY-4.0 License.