Payload Codecs

KoutenDB stores payloads as binary-safe byte strings and records a format identifier with each document. The supported identifiers are:

Codec Intended use Core behavior
raw Text or arbitrary application bytes Stored without interpretation
json JSON documents and projections Stored as bytes; eligible for JSON projection and patch APIs
nif Pre-encoded NIF text/token data Stored without conversion
bif Pre-encoded BIF binary data Stored without conversion

NIF/BIF support in the core means format-aware storage, WAL persistence, cluster transport, handoff, transaction apply, Universe sync, list, get, and retrieval. KoutenDB does not bundle a NIF/BIF encoder or decoder into the core. Applications can provide already encoded bytes directly, or use the optional koutendb-nif adapter backed by nifkit. This keeps the database independent of one codec implementation while still providing an official OSS adapter path.

Ring payload profiles

A ring can declare the payload format normally used within it. This declaration is persisted separately from records and is available to applications, CLI tools, and optional format adapters:

kouten ring-profile --data=/var/lib/kouten --ring=artifacts \
  --codec=bif --format-version=1
kouten ring-profile --data=/var/lib/kouten --ring=documents \
  --codec=nif --charset=UTF-8 --format-version=1

The profile contains defaultCodec, charset, and formatVersion. charset describes text encodings such as NIF text; it does not apply to BIF binary payloads. A record’s explicit codec always wins over the ring default, so a profile change never reinterprets bytes already stored. kouten put defaults to --codec=auto and uses the ring default; pass an explicit codec to override it.

Embedded API

import koutendb
import std/json

var db = open()

let jsonId = db.put(%*{"title": "KoutenDB"}, ring = "docs")
let bifId = db.put(encodedPayload(bifBytes, pcBif), ring = "docs")

let stored = db.getEncoded(bifId)
doAssert stored.codec == pcBif
doAssert stored.data == bifBytes

db.close()

The string overload of put remains backward compatible and uses raw. The JsonNode overload uses json automatically.

Runnable Demos

From a KoutenDB source checkout:

examples/payload_codecs_demo.sh
examples/payload_codecs_cluster_demo.sh

The embedded demo writes JSON, NIF-tagged bytes, and BIF-tagged bytes, reopens the persistent store, reuses a prepared selection, and shows that JSON projection is rejected for NIF/BIF records. The cluster demo shows negotiated codec metadata and verifies that a connection which does not opt in still sees the legacy three-field VAL header.

CLI

kouten put --ring=docs --in=document.bif --codec=bif
kouten put --ring=docs --payload='{"title":"KoutenDB"}' --codec=json
kouten get --ring=docs --filter='{"id":"RAW_ID"}'

These examples use the default embedded data directory. Set KOUTEN_DATA or pass --data=DIR when you want a specific local store, or pass --peers=host:port,... when talking to a koutend cluster.

Put and get examples

Use an explicit codec when writing payloads whose type matters. Without an explicit codec, put stores the payload as raw unless --codec=auto resolves to the ring’s payload profile.

# JSON: filter and projection work because the record is tagged as json.
kouten put --ring=docs/japan \
  --payload='{"title":"Hello","status":"draft"}' \
  --codec=json
kouten get --ring=docs/japan \
  --filter='{"status":"draft"}' \
  --selection='{ title }'

# NIF text: KoutenDB stores the bytes and keeps codec=nif metadata.
kouten put --ring=docs/nif --in=sample.nif --codec=nif
kouten get --ring=docs/nif --limit=1

# BIF binary: KoutenDB stores the bytes and keeps codec=bif metadata.
kouten put --ring=docs/bif --in=sample.bif --codec=bif
kouten get --ring=docs/bif --limit=1

# Debug or transport-safe BIF views.
kouten get --ring=docs/bif --limit=1 --view=base64
kouten get --ring=docs/bif --limit=1 --view=hex

# Raw: plain bytes/text with no format interpretation.
kouten put --ring=logs/raw --payload='plain text payload' --codec=raw
kouten get --ring=logs/raw --limit=1

get always returns the same page shape. Use --limit=1 when you only want one record:

{
  "ring": "docs/bif",
  "count": 1,
  "items": [
    {
      "codec": "bif",
      "encoding": "base64",
      "payload": "AQIDBA=="
    }
  ]
}

The stored codec metadata drives display. NIF text is returned as text. BIF uses the optional adapter when available and falls back to base64 when it is not.

The default view is auto, which prints text/NIF with codec metadata. For BIF, it first tries to use an optional adapter and prints decoded NIF text when one is available. The lookup order is KOUTENDB_NIF_TOOL, koutendb-nif, then nif_file_tool. The adapter command must support decode --in=input.bif --out=output.nif. If no adapter is available, auto falls back to base64. Use --view=hex for byte-level inspection or --view=raw for the original raw payload bytes. Decoding BIF back into NIF text is intentionally adapter-side; the published koutendb-nif adapter provides that path without making NIF/BIF parsing a KoutenDB core dependency.

Ring profile and auto codec

Ring profiles are useful when one ring is intended to contain one payload format. The profile does not rewrite existing records; it only supplies a default for future --codec=auto writes.

kouten ring-profile --data=./data \
  --ring=docs/nif \
  --codec=nif \
  --charset=UTF-8 \
  --format-version=1

kouten put --data=./data --ring=docs/nif --in=sample.nif --codec=auto
kouten get --data=./data --ring=docs/nif --limit=1

C ABI

The additive C ABI functions preserve the existing kouten_put and kouten_get contract while exposing format metadata to C/C++ and FFI consumers:

kouten_id id;
kouten_put_codec(db, "artifacts/bif", bytes, byte_len, KOUTEN_CODEC_BIF, &id);

size_t len;
int codec;
void *stored = kouten_get_codec(db, id, &len, &codec);
/* codec == KOUTEN_CODEC_BIF; release stored with kouten_free(stored). */

kouten_put_vec_codec is the equivalent vector-bearing write call. The C ABI contract smoke verifies this path in examples/cabi_contract.c.

Projection Boundary

query, patch, and prepared selections are JSON operations. KoutenDB rejects their use on explicitly tagged nif and bif records instead of attempting to interpret those bytes as JSON. raw remains projection-compatible for legacy records and older drivers that stored JSON before codec metadata existed.

Prepared Selections

KoutenDB does not execute SQL, so its prepared-statement-like API compiles the GraphQL-style projection rather than preparing an SQL string:

let fields = prepareSelection("{ title author { name } }")
let first = db.query(firstId, fields)
let second = db.query(secondId, fields)

prepareSelection validates and parses the selection once. Embedded queries reuse that parsed tree directly. Cluster nodes also keep a bounded cache of validated selection trees. The selection grammar contains field names only; payload values are never interpolated into it.

Compatibility And Byte Order

The codec name is optional on the wire and in older WAL records. Missing codec metadata is interpreted as raw, so existing data and drivers remain readable. Clients can call the additive CODECS wire command to discover the identifiers accepted by a node. CODECMETA ON opts the connection into codec fields on response headers; clients that do not negotiate keep the original header shape.

KoutenDB treats NIF/BIF payload bytes as opaque. Their byte order is therefore a property of the selected format specification, not the host CPU. This is separate from KoutenDB vector bytes, which are always canonical little-endian IEEE-754 float32 on the TCP wire.