Skip to content

Encoding Profiles

Twilic v3 defines three primary data profiles and one optional stateful profile. Each profile trades off flexibility, wire size, and session requirements differently. Pick the profile that matches your transport and data shape — not the other way around.

Profile overview

ProfileSchema requiredSession requiredBest for
DynamicNoNoAd-hoc values, single objects, first adoption
BatchNo, or yes for SCHEMA_BATCHNoSame-shape record lists, API pages, cache snapshots
BoundYesNoFixed message types, payment events, max density
StatefulNoYesWebSocket ticks, incremental entity sync

Rust, Go, JavaScript, and Zig emit v3 by default. Other published SDKs may still emit v2 payloads. For new spec-level work, use the v3 reference profile and explicitly signal version/profile when v2 and v3 coexist.

Dynamic Profile

The default. Encode any TwilicValue tree without prior agreement on shape.

ts
import { encode, decode } from "@twilic/core";
const bytes = encode({ id: 1n, name: "alice" });

Wire behavior:

  • First occurrence of each shape sends field names
  • Repeated strings within a message are interned
  • Homogeneous numeric arrays may auto-select typed vector codecs
  • No cross-message state

Use when: single HTTP bodies, one-off cache entries, prototyping.

Batch Profile

Encodes multiple same-shape records as one message with shared shape definition.

ts
import { encodeBatch } from "@twilic/core/advanced";
const bytes = encodeBatch(records);

Wire behavior:

  • Field names sent once per batch
  • String interning across all records
  • Dynamic row batch (row_batch), dynamic columnar batch (col_batch), or schema-aware SCHEMA_BATCH selected by encoder/profile

Size vs MessagePack (same-shape records):

RecordsApprox. size vs MessagePack
1060–70%
10030–40%
1,00020–30%

Use when: list API responses, telemetry flushes, bulk cache warm-up.

See Batch & Columnar.

Bound Profile

Schema-aware encoding. Field names omitted; positions and types come from a Schema.

ts
import { encodeWithSchema } from "@twilic/core/advanced";
const bytes = encodeWithSchema(schema, value);

Wire behavior:

  • Field names not sent
  • Enum fields encode as bit indices
  • BOUND_STREAM binds one schema for consecutive compact record bodies
  • Enum, bool, and range_bits fields coalesce into compact bit groups
  • Comparable to Protobuf/Avro density on schema-fixed streams under equivalent framing assumptions

Use when: payment messages, fixed RPC contracts, hot paths with stable structure.

See Schema-Bound Encoding.

Stateful Profile

Maintains encoder/decoder state across an ordered message sequence.

ts
import { createSessionEncoder } from "@twilic/core";
const enc = createSessionEncoder();
enc.encode(fullValue); // baseline
enc.encodePatch(updated); // changed fields only
enc.reset(); // after disconnect

Wire behavior:

  • State patches reference previous message or base snapshot
  • Trained dictionaries accumulate across session
  • Template batches reuse column templates

Use when: WebSocket dashboards, game state sync, live metrics.

Not for HTTP Stateful profile requires ordered delivery and persistent session state. Do not use on stateless HTTP request/response. :::

See Stateful Streams.

Decision flowchart

Profile progression

Typical adoption path:

text
1. Dynamic everywhere (drop-in MessagePack replacement)
2. Batch on list/bulk endpoints
3. Stateful on WebSocket products
4. Bound/`BOUND_STREAM` on 2–3 latency-critical fixed messages

SDK entrypoints by profile

ProfileJavaScriptRustPythonGo
Dynamicencode()encode()encode()Encode()
BatchencodeBatch()encode_batch()encode_batch()EncodeBatch()
BoundencodeWithSchema()encode_with_schema()encode_with_schema()via SessionEncoder
StatefulcreateSessionEncoder()create_session_encoder()create_session_encoder()NewSessionEncoder()

Released under the CC-BY-4.0 License.