docs(wiki): add complete format and C++ API guide

2026-06-17 19:04:13 -05:00
parent 62fbf16550
commit a2401549c4
13 changed files with 1048 additions and 1 deletions

116
Binary-Format.md Normal file

@@ -0,0 +1,116 @@
# Binary Format
This page documents the current implementation-level layout. All multibyte fixed-width integers are little-endian.
## Common primitives
| Primitive | Encoding |
|---|---|
| `u8` | One byte |
| `u32le` | Four-byte unsigned integer, least-significant byte first |
| `u64le` | Eight-byte unsigned integer, least-significant byte first |
| `varu32` / `varu64` | Base-128 varint, seven payload bits per byte, continuation bit `0x80` |
| `vari64` | Signed integer zigzag-transformed, then encoded as `varu64` |
| string | `varu32 byte_length`, followed by that many bytes, no terminator |
Zigzag maps small signed magnitudes to small unsigned values: `0 → 0`, `-1 → 1`, `1 → 2`, `-2 → 3`.
## Type tags
| Byte | Type |
|---:|---|
| 0 | null |
| 1 | string |
| 2 | signed integer |
| 3 | double |
| 4 | boolean |
| 5 | object |
| 6 | array |
## Node encoding
A general node is:
```text
u8 type
payload(type)
```
Payloads are:
| Type | Payload |
|---|---|
| null | no bytes |
| string | string primitive |
| integer | `vari64` |
| float | IEEE-754 double bits as `u64le` |
| boolean | `u8`, zero is false and nonzero is true |
| object | `varu32 count`, then repeated `string key` + `node value` |
| array | `u8 element_type`, `varu32 count`, then elements |
For a mixed array (`element_type == 0`), every item is a complete node with its own type byte. For a typed array, each item stores only the fixed payload for the declared type. String, integer, boolean, and float typed arrays therefore avoid repeated tags. Object typed arrays still encode each item as a full node.
## iKv1 binary document
```text
4 bytes magic = "iKv1"
u8 kind = 'b'
u32le version = 1
string root_name
node root
```
iKv1 serializes the complete root sequentially.
## iKv2 indexed binary document
iKv2 requires an object root and stores top-level members in a sorted index:
```text
4 bytes magic = "iKv2"
u8 kind = 'b'
u32le version = 2
u32le flags (bit 0 = indexed root)
string root_name
varu32 entry_count
repeat entry_count times:
string key
repeat entry_count times:
u8 type
u32le payload_offset
u32le payload_size
payload area:
complete node payload for each indexed entry
```
Offsets are absolute byte offsets from the start of the file. The writer sorts top-level keys lexicographically before writing the index and payload area.
## Lazy top-level loading
When iKv2 binary input is loaded:
1. The complete byte buffer is copied or owned by the root.
2. Index keys, types, offsets, and lengths are validated.
3. An in-memory hash index is built over the key table.
4. Top-level values remain encoded until `operator[]`/object lookup requests a key.
5. The selected payload is decoded and attached to the root.
This avoids eagerly decoding unrelated top-level values. Nested data inside a selected payload is decoded normally.
## Validation
The reader rejects:
- short or truncated headers
- wrong magic, kind byte, or numeric version
- missing indexed-root flag in iKv2
- malformed or overflowing varints
- out-of-range offsets and sizes
- unknown type tags
- incomplete object/array payloads
- payloads that do not consume their declared iKv2 range exactly
Binary layout is an implementation contract for the current iKv versions. New incompatible layouts should use a new format version.

77
Build-Install-and-Test.md Normal file

@@ -0,0 +1,77 @@
# Build, Install, and Test
## CMake options
| Option | Default | Purpose |
|---|---:|---|
| `IKVXX_BUILD_TESTS` | `ON` | Build/register iKvxx and complete upstream iKv tests |
| `IKVXX_INSTALL` | `ON` | Generate install rules and CMake package files |
## Build
```sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
```
Targets:
- `ikvxx`
- `ikvxx::ikvxx` alias for consumers
- `ikv` / `ikv::ikv` underlying C library
- `ikvxx_tests` when tests are enabled
## Install and consume
```sh
cmake --install build --prefix /opt/ikvxx
```
Consumer project:
```cmake
find_package(ikvxx 0.1 REQUIRED CONFIG)
target_link_libraries(my_app PRIVATE ikvxx::ikvxx)
```
Configure with a custom prefix:
```sh
cmake -S . -B build -DCMAKE_PREFIX_PATH=/opt/ikvxx
```
## Tests
```sh
cmake -S . -B build -DIKVXX_BUILD_TESTS=ON
cmake --build build
ctest --test-dir build --output-on-failure
```
The registered suite contains:
- 7 separately reported iKvxx groups
- 74 upstream iKv cases
- 81 total tests at the current pinned revision
Coverage includes type behavior, all overload families, object/array mutation, malformed input, binary versions, allocation and file-failure hooks, lazy loading, refresh, and the v2 benchmark.
## Windows runner
```bat
tests\run.bat
tests\run.bat Debug
```
The script checks for CMake/CTest, configures, builds, enumerates all tests, runs them, and propagates failures.
## Continuous integration
`.gitea/workflows/bindings.yml` runs on `ubuntu-latest` with:
- GCC Debug
- GCC Release
- Clang Debug
- Clang Release
Each job checks out submodules, builds, runs all CTest cases, and validates installation.

103
C++-API-Overview.md Normal file

@@ -0,0 +1,103 @@
# C++ API Overview
The complete public API is declared in `<ikvxx/ikvxx.hpp>` under namespace `ikv`.
## Supporting types
```cpp
enum ValueType {
nullValue, stringValue, intValue, realValue,
booleanValue, objectValue, arrayValue
};
enum class Version : unsigned { v1 = 1, v2 = 2 };
class Error : public std::runtime_error {};
```
## Construction and ownership
| API | Behavior |
|---|---|
| `Value(ValueType type = objectValue, std::string root_name = "root")` | Creates an object or array root; scalar roots throw |
| `Value(const Value&)` | Copies a handle and shares tree ownership |
| `operator=(const Value&) = delete` | Structural subtree assignment is deliberately unavailable |
## Static input methods
| API | Behavior |
|---|---|
| `Value::parse(text)` | Parse auto-detected text from memory |
| `Value::load(path)` | Parse auto-detected text from a file |
| `Value::fromBinary(data, size)` | Parse auto-detected binary memory |
| `Value::loadBinary(path)` | Parse auto-detected binary file |
All failures throw `ikv::Error`.
## Metadata and type checks
| API | Result |
|---|---|
| `type()` | Runtime `ValueType`; missing values report `nullValue` |
| `isNull()` | Missing/null handle |
| `isString()` | String value |
| `isInt()` | Signed integer value |
| `isDouble()` | Double value |
| `isBool()` | Boolean value |
| `isObject()` | Object value |
| `isArray()` | Array value |
| `empty()` | Object/array has zero entries, or value is scalar/missing |
| `size()` | Object member count or array length; otherwise zero |
| `name()` | Root name or member key |
## Object access
| API | Behavior |
|---|---|
| `isMember(key)` | True when an object contains the key |
| `operator[](key)` | Member handle; missing non-const members are assignable |
| `get(key, fallback)` | Member or fallback handle |
| `makeObject(key)` | Add/replace and return nested object |
| `makeArray(key, element_type)` | Add/replace and return array |
String and `const char*` key overloads are available. Null C-string keys throw.
## Array access
| API | Behavior |
|---|---|
| `operator[](int)` | Indexed access with negative/bounds checks |
| `operator[](ArrayIndex)` | Unsigned indexed access with bounds checks |
| `append(string)` | Append string |
| `append(int64_t)` | Append signed 64-bit integer |
| `append(int)` | Append integer |
| `append(double)` | Append double |
| `append(bool)` | Append boolean |
| `appendObject()` | Append and return object |
Each access and append has the appropriate const or overload variants shown in the header.
## Scalar mutation and reads
| API | Behavior |
|---|---|
| `operator=(std::string/const char*)` | Set object member string |
| `operator=(int64_t/int)` | Set object member integer |
| `operator=(double)` | Set object member double |
| `operator=(bool)` | Set object member boolean |
| `asString()` | Strict string read |
| `asInt64()` | Strict signed 64-bit read |
| `asInt()` | Strict integer read with C++ `int` range check |
| `asDouble()` | Strict double read |
| `asBool()` | Strict boolean read |
## Output and refresh
| API | Behavior |
|---|---|
| `write(path, version)` | Write text, default iKv2 |
| `writeBinary(path, version)` | Write binary file, default iKv2 |
| `toBinary(version)` | Return binary bytes, default iKv2 |
| `refresh(path)` | Atomically replace a mutable root from text/binary input |
See [Ownership, Errors, and Limits](Ownership-Errors-and-Limits) before retaining child handles across mutations.

102
Examples.md Normal file

@@ -0,0 +1,102 @@
# Examples
## Player save
```cpp
ikv::Value save(ikv::objectValue, "player_save");
save["version"] = 2;
save["timestamp"] = std::int64_t{1712345678};
auto player = save.makeObject("player");
player["name"] = "Ada";
player["health"] = 95;
player["speed"] = 7.5;
player["alive"] = true;
auto inventory = save.makeArray("inventory", ikv::stringValue);
inventory.append("wrench");
inventory.append("battery");
save.write("player.ikv");
save.writeBinary("player.ikvb");
```
## Read with validation
```cpp
auto save = ikv::Value::load("player.ikv");
if (!save.isObject() || !save.isMember("player"))
throw std::runtime_error("invalid save");
auto player = save["player"];
if (!player["name"].isString() || !player["health"].isInt())
throw std::runtime_error("invalid player");
std::string name = player["name"].asString();
int health = player["health"].asInt();
```
## Default object
```cpp
ikv::Value fallback(ikv::objectValue, "defaults");
fallback["enabled"] = false;
const ikv::Value& config = root;
auto feature = config.get("feature", fallback);
bool enabled = feature["enabled"].asBool();
```
## Mixed event list
```cpp
ikv::Value log(ikv::objectValue, "events");
auto values = log.makeArray("values");
values.append("started");
values.append(std::int64_t{42});
values.append(true);
values.append(3.5);
```
## Object array
```cpp
ikv::Value root;
auto users = root.makeArray("users", ikv::objectValue);
for (int i = 0; i < 3; ++i) {
auto user = users.appendObject();
user["id"] = i;
user["active"] = (i % 2 == 0);
}
for (ikv::Value::ArrayIndex i = 0; i < users.size(); ++i) {
int id = users[i]["id"].asInt();
}
```
## Network binary round trip
```cpp
std::vector<std::uint8_t> encoded = root.toBinary();
send(encoded.data(), encoded.size());
std::vector<std::uint8_t> received = receive();
auto decoded = ikv::Value::fromBinary(received.data(), received.size());
```
## Hot reload
```cpp
auto settings = ikv::Value::load("settings.ikv");
while (running) {
if (fileChanged("settings.ikv")) {
try {
settings.refresh("settings.ikv");
} catch (const ikv::Error& error) {
// Existing settings remain intact.
}
}
}
```

77
Getting-Started.md Normal file

@@ -0,0 +1,77 @@
# Getting Started
## Requirements
- A C++17 compiler
- CMake 3.16 or newer
- Git, including submodule support
## Clone and build
```sh
git clone --recurse-submodules https://dock-it.dev/iDENTITY-Technology/iKvxx.git
cd iKvxx
cmake -S . -B build -DIKVXX_BUILD_TESTS=ON
cmake --build build
ctest --test-dir build --output-on-failure
```
If the repository was cloned without submodules:
```sh
git submodule update --init --recursive
```
On Windows, the repository includes a complete runner:
```bat
tests\run.bat
tests\run.bat Debug
```
## Link from CMake
Add iKvxx directly:
```cmake
add_subdirectory(path/to/iKvxx)
target_link_libraries(your_app PRIVATE ikvxx::ikvxx)
```
Then include the public header:
```cpp
#include <ikvxx/ikvxx.hpp>
```
## First program
```cpp
#include <ikvxx/ikvxx.hpp>
#include <iostream>
int main()
{
ikv::Value root(ikv::objectValue, "settings");
root["fullscreen"] = true;
root["width"] = 1920;
root["height"] = 1080;
auto audio = root.makeObject("audio");
audio["volume"] = 0.75;
audio["muted"] = false;
root.write("settings.ikv");
auto loaded = ikv::Value::load("settings.ikv");
std::cout << loaded["width"].asInt() << '\n';
}
```
The `Value` root owns the complete C tree. No manual cleanup is necessary.
## Next steps
- Learn the file syntax in [Text Format](Text-Format).
- Review all methods in [C++ API Overview](C++-API-Overview).
- See typed arrays and nested objects in [Objects and Arrays](Objects-and-Arrays).

47
Home.md

@@ -1 +1,46 @@
Welcome to the Wiki.
# iKvxx Wiki
iKvxx is the C++17 interface for [iKv](https://dock-it.dev/iDENTITY-Technology/iKv), a compact object-and-array data format with readable text files and efficient binary files.
```cpp
#include <ikvxx/ikvxx.hpp>
ikv::Value root(ikv::objectValue, "player_save");
root["title"] = "Demo";
root["version"] = 2;
auto player = root.makeObject("player");
player["name"] = "Ada";
player["alive"] = true;
root.write("save.ikv");
```
## Start here
- [Getting Started](Getting-Started) — clone, build, link, and write a first file
- [iKv Format](iKv-Format) — versions, value model, and compatibility
- [Text Format](Text-Format) — complete syntax and parsing rules
- [Binary Format](Binary-Format) — v1 and indexed v2 layouts
- [C++ API Overview](C++-API-Overview) — complete `ikv::Value` reference
- [Value Types](Value-Types) — runtime types and conversions
- [Objects and Arrays](Objects-and-Arrays) — construction, lookup, and mutation
- [Parsing and Serialization](Parsing-and-Serialization) — text, files, memory, versions, and refresh
- [Ownership, Errors, and Limits](Ownership-Errors-and-Limits) — lifetime and failure semantics
- [Examples](Examples) — complete common workflows
- [Build, Install, and Test](Build-Install-and-Test) — CMake, package consumption, and all tests
## Core properties
- C++17, with RAII ownership and exceptions
- JsonCpp-inspired `Value` access
- iKv1 and iKv2 text and binary compatibility
- Auto-detection when reading
- iKv2 output by default
- Typed or mixed arrays
- Lazy top-level lookup for iKv2 binary objects
## Repositories
- [iKvxx](https://dock-it.dev/iDENTITY-Technology/iKvxx)
- [iKv C library](https://dock-it.dev/iDENTITY-Technology/iKv)

86
Objects-and-Arrays.md Normal file

@@ -0,0 +1,86 @@
# Objects and Arrays
## Object roots and members
```cpp
ikv::Value root(ikv::objectValue, "root");
root["name"] = "demo";
root["count"] = 3;
auto nested = root.makeObject("nested");
nested["enabled"] = true;
```
Relevant methods:
```cpp
bool isMember(const std::string& key) const;
Value operator[](const std::string& key);
Value operator[](const char* key);
Value get(const std::string& key, const Value& fallback) const;
Value makeObject(const std::string& key);
Value makeArray(const std::string& key, ValueType element_type = nullValue);
```
Const overloads of `operator[]` return read-only handles. `get` returns the member when present or a shared copy of the supplied fallback.
## Array roots and members
```cpp
ikv::Value array_root(ikv::arrayValue, "items");
ikv::Value document;
auto values = document.makeArray("values");
```
`nullValue` creates a mixed array. A concrete type creates a typed array:
```cpp
auto names = document.makeArray("names", ikv::stringValue);
auto scores = document.makeArray("scores", ikv::intValue);
auto flags = document.makeArray("flags", ikv::booleanValue);
auto players = document.makeArray("players", ikv::objectValue);
```
## Append operations
```cpp
Value append(const std::string& value);
Value append(const char* value);
Value append(std::int64_t value);
Value append(int value);
Value append(double value);
Value append(bool value);
Value appendObject();
```
Each successful append returns a handle to the new element.
```cpp
scores.append(10);
scores.append(25);
auto player = players.appendObject();
player["name"] = "Ada";
player["score"] = 100;
```
A typed array rejects an incompatible append with `ikv::Error`. Arrays of arrays are not supported by the current public iKv C API.
## Indexing
```cpp
Value operator[](int index);
Value operator[](ArrayIndex index);
```
Both const and non-const overloads exist. Negative or out-of-range indexes throw `std::out_of_range`.
```cpp
for (ikv::Value::ArrayIndex i = 0; i < scores.size(); ++i) {
int score = scores[i].asInt();
}
```
Existing array elements cannot currently be replaced through assignment. Append a new value or rebuild the array instead.

@@ -0,0 +1,67 @@
# Ownership, Errors, and Limits
## Shared tree ownership
`ikv::Value` is a lightweight handle backed by shared ownership of the root C node:
```cpp
auto root = ikv::Value::load("data.ikv");
auto copy = root;
auto child = root["child"];
```
`root`, `copy`, and `child` keep the same tree alive. Copying does not deep-clone data.
## Handle invalidation
A child handle points at a node inside the shared tree. Reacquire it after:
- replacing that member with scalar assignment
- replacing it with `makeObject` or `makeArray`
- refreshing the root
Do not dereference a stale child handle.
## Const behavior
A handle derived through a const root is read-only. Mutation attempts throw `ikv::Error`, including mutations through nested temporary handles.
## Exceptions
`ikv::Error` derives from `std::runtime_error` and reports:
- parse failures
- file open/read/write failures
- incompatible value types
- unsupported root or array structure
- typed-array append rejection
- invalid mutation targets
- refresh failures
Standard exceptions are used where they are more precise:
- `std::out_of_range` for invalid array indexes
- `std::out_of_range` when `asInt()` cannot represent an `int64_t`
```cpp
try {
auto root = ikv::Value::load("settings.ikv");
int width = root["width"].asInt();
} catch (const ikv::Error& error) {
// Parse, I/O, or type error.
} catch (const std::out_of_range& error) {
// Index or integer-range error.
}
```
## Current API limits
- Roots constructed through iKvxx must be objects or arrays.
- The iKv2 indexed binary writer requires an object root.
- Arrays of arrays cannot be built through the current public C API.
- Existing array elements cannot be replaced in place.
- `Value`-to-`Value` structural assignment is deleted.
- Object iteration is not exposed because the public C API does not expose key iteration.
- Null scalar assignment is not exposed by the current C mutation API.
These constraints are explicit: operations throw or fail to compile instead of silently producing an incomplete tree.

@@ -0,0 +1,94 @@
# Parsing and Serialization
## Text input
```cpp
static Value parse(const std::string& text);
static Value load(const std::string& path);
```
Both methods auto-detect iKv1 and iKv2. `parse` reads an in-memory string; `load` reads a text file.
```cpp
auto memory = ikv::Value::parse(R"(
ikv2 "settings"
{
"enabled" true
}
)");
auto file = ikv::Value::load("settings.ikv");
```
## Binary input
```cpp
static Value fromBinary(const void* data, std::size_t size);
static Value loadBinary(const std::string& path);
```
The parsed tree owns or copies all data required after the call returns. The caller may release its source buffer.
```cpp
std::vector<std::uint8_t> bytes = receiveData();
auto value = ikv::Value::fromBinary(bytes.data(), bytes.size());
```
## Text output
```cpp
void write(const std::string& path,
ikv::Version version = ikv::Version::v2) const;
```
```cpp
root.write("current.ikv");
root.write("legacy.ikv", ikv::Version::v1);
```
## Binary output
```cpp
void writeBinary(const std::string& path,
ikv::Version version = ikv::Version::v2) const;
std::vector<std::uint8_t> toBinary(
ikv::Version version = ikv::Version::v2) const;
```
```cpp
root.writeBinary("current.ikvb");
auto bytes = root.toBinary();
auto legacy = root.toBinary(ikv::Version::v1);
```
## Refresh
```cpp
void refresh(const std::string& path);
```
Refresh replaces an existing root in place from a text or binary path. Format detection is automatic.
```cpp
auto settings = ikv::Value::load("settings.ikv");
auto shared = settings;
settings.refresh("updated-settings.ikvb");
// shared observes the same refreshed root.
```
Rules:
- Only a mutable root handle can be refreshed.
- A failed refresh preserves the existing tree.
- Reacquire child handles after refresh.
- Other root copies continue sharing the refreshed tree.
## Version selection
```cpp
enum class Version : unsigned { v1 = 1, v2 = 2 };
```
Version arguments affect output only. Input functions detect the version from text tags or binary headers.

121
Text-Format.md Normal file

@@ -0,0 +1,121 @@
# Text Format
## Canonical document
```ikv
ikv2 "player_save"
{
"title" "iKv demo"
"version" 2
"enabled" true
"speed" 12.5
"player" {
"name" "Ada"
"note" "line one\nline two"
}
"inventory" [
"wrench"
"battery"
"map"
]
}
```
The normal header is:
```text
ikv<version> <root-name>
```
The root name may be quoted or a bare word when parsing. Writers always emit a quoted name and use `"root"` if the in-memory root name is empty.
## Informal grammar
```text
document := versioned-object | object | plain-members
versioned-object := ("ikv1" | "ikv2") root-name object
object := "{" { member [","] } "}"
member := quoted-string value
array := "[" { value [","] } "]"
value := object | array | quoted-string | word
root-name := quoted-string | word
plain-members := { member [","] }
```
Commas are optional between object members and array elements. Whitespace separates adjacent values.
## Keys and strings
Object keys must be quoted strings. Supported escapes are:
| Escape | Result |
|---|---|
| `\\` | Backslash |
| `\"` | Double quote |
| `\n` | Newline |
| `\r` | Carriage return |
| `\t` | Tab |
Other backslash sequences are retained literally rather than interpreted as Unicode or hexadecimal escapes. The current format does not implement `\uXXXX` escapes.
## Bare-word inference
Unquoted words are interpreted in this order:
1. `true` or `false` → boolean
2. `null` → null
3. A complete numeric token containing `.`, `e`, or `E` → float
4. A complete base-10 numeric token → signed integer
5. Anything else → string
Examples:
```ikv
{
"a" true
"b" -42
"c" 6.25
"d" 1e6
"e" unquoted-string
}
```
Writers quote all strings, so generated files do not rely on bare-string inference.
## Comments
Two line-comment forms are supported outside strings:
```ikv
// C++ style comment
# shell style comment
```
Each runs through the end of the line. Block comments are not supported.
## Alternate accepted roots
The parser also accepts an unversioned braced object:
```ikv
{
"name" "demo"
}
```
Or plain top-level members:
```ikv
"name" "demo"
"count" 3
```
Versioned output is recommended because it is explicit and is what the writer produces for object roots.
## Formatting behavior
- Writers indent nested values by four spaces.
- Floating-point values use up to 17 significant digits.
- Arrays and objects are written over multiple lines.
- Object member order is not guaranteed.
- An object root receives a version header; a non-object C root is written as a bare value.

74
Value-Types.md Normal file

@@ -0,0 +1,74 @@
# Value Types
## `ikv::ValueType`
```cpp
enum ValueType {
nullValue = 0,
stringValue,
intValue,
realValue,
booleanValue,
objectValue,
arrayValue
};
```
## Inspection
```cpp
ikv::ValueType type() const noexcept;
bool isNull() const noexcept;
bool isString() const noexcept;
bool isInt() const noexcept;
bool isDouble() const noexcept;
bool isBool() const noexcept;
bool isObject() const noexcept;
bool isArray() const noexcept;
bool empty() const;
std::size_t size() const;
std::string name() const;
```
- `type()` returns `nullValue` for a missing member.
- `empty()` is true for empty objects/arrays and for scalar or missing values.
- `size()` returns object member count or array length; scalars return zero.
- `name()` returns the node key/root name, or an empty string when absent.
## Scalar assignment
Assignment is supported on a non-const object-member handle produced by `operator[]`:
```cpp
root["text"] = std::string("hello");
root["literal"] = "world";
root["wide"] = std::int64_t{9000000000};
root["count"] = 42;
root["ratio"] = 0.5;
root["enabled"] = true;
```
Assigning a key that already exists replaces that member. A null `const char*` is rejected.
## Scalar conversion
```cpp
std::string asString() const;
std::int64_t asInt64() const;
int asInt() const;
double asDouble() const;
bool asBool() const;
```
Conversions are strict; iKvxx does not silently coerce between types. Calling `asInt()` on a string, for example, throws `ikv::Error`. `asInt()` also throws `std::out_of_range` if the stored 64-bit integer does not fit in a C++ `int`.
## Null handles
```cpp
auto missing = root["missing"];
if (missing.isNull()) {
root["missing"] = "created";
}
```
A missing non-const object member preserves its parent and key, making scalar assignment possible. A missing const lookup is read-only.

23
_Sidebar.md Normal file

@@ -0,0 +1,23 @@
### iKvxx
- [Home](Home)
- [Getting Started](Getting-Started)
- [Examples](Examples)
### Format
- [iKv Format](iKv-Format)
- [Text Format](Text-Format)
- [Binary Format](Binary-Format)
- [Value Types](Value-Types)
### C++ API
- [API Overview](C++-API-Overview)
- [Objects and Arrays](Objects-and-Arrays)
- [Parsing and Serialization](Parsing-and-Serialization)
- [Ownership, Errors, and Limits](Ownership-Errors-and-Limits)
### Development
- [Build, Install, and Test](Build-Install-and-Test)

62
iKv-Format.md Normal file

@@ -0,0 +1,62 @@
# iKv Format
iKv stores a named tree of objects, arrays, strings, signed integers, double-precision floating-point numbers, booleans, and null values. The same logical tree can be represented as text or binary.
## Versions
| Version | Text tag | Binary magic | Notes |
|---|---|---|---|
| iKv1 | `ikv1` | `iKv1` | Sequential binary root encoding |
| iKv2 | `ikv2` | `iKv2` | Indexed binary object root with lazy top-level loading |
Generic read functions inspect the input and select the correct loader. Generic writes use iKv2. Use `ikv::Version::v1` only when an older consumer requires it.
## Data model
| iKv type | C++ API type | Meaning |
|---|---|---|
| null | `ikv::nullValue` | No scalar payload |
| string | `ikv::stringValue` | Length-delimited byte string; text uses quotes |
| integer | `ikv::intValue` | Signed 64-bit integer |
| float | `ikv::realValue` | IEEE-754 double |
| boolean | `ikv::booleanValue` | `true` or `false` |
| object | `ikv::objectValue` | Key/value mapping |
| array | `ikv::arrayValue` | Ordered values, optionally typed |
## Root model
Normal document roots are named objects:
```ikv
ikv2 "player_save"
{
"level" 4
}
```
iKvxx supports object and array roots in memory. The indexed iKv2 binary writer requires an object root. The C++ wrapper rejects scalar root construction.
## Object semantics
- Keys are strings.
- Assigning the same key again replaces its value.
- Object order is not a stable semantic property. Do not depend on write order.
- Missing-key lookup returns a null handle in iKvxx.
## Array semantics
Arrays retain insertion order. An array has an element-type marker:
- `null` marker: mixed/untyped; each binary item carries its own type tag.
- Scalar or object marker: typed; binary items omit repeated type tags.
- Text parsing infers a common type when all non-null elements agree, otherwise the array becomes mixed.
See [Text Format](Text-Format) and [Binary Format](Binary-Format) for wire details.
## Compatibility rules
- Readers validate text version tags and binary version fields.
- iKv1 and iKv2 share the same logical types and text grammar.
- iKv2 binary top-level keys are indexed and loaded on first lookup.
- Unknown, malformed, truncated, or version-mismatched binary input is rejected.
- Explicit version output is available through all write/encode methods.