Language values and documents
Your application keeps its own structs, records, dataclasses and maps. The typed layer copies them into native documents and copies documents back into them. The native document API stays available beside it for work that needs native fidelity or no conversion.
Choose an interface
| Use | When you want to |
|---|---|
| Language values (encode, decode, typed rows) | Write and read ordinary application data in the types of your language. |
| Documents | Reuse a value in several calls without converting it again, keep duplicate keys and member order exactly, or batch native work. |
| Parse and serialize | Read or write text or bytes at a file or network boundary. See Formats and conversion. |
| Builders and node reads | Construct or walk documents node by node. See Documents and Building documents. |
Example
The example stores three people from language values, runs a query written in the selected format, and decodes each row into a value of the language. The captured result is the decoded rows, encoded again for display.
Store people and query their names (C uses native documents)
seed_text holds this document:
| Offset (hex) | Bytes (hex) | Meaning |
|---|---|---|
| 000 | 54 52 4F 4E | magic TRON |
| 004 | 3C 616765 | txt "age" |
| 008 | 02 24000000000000 00 | i64 36 |
| 011 | 0F 0A 04000000 0800 0000 | map leaf, size 10: @4 → @8 |
| 01B | 4C 726F6C65 | txt "role" |
| 020 | 8C 656E67696E6565 72 | txt "engineer" |
| 029 | 0F 0A 1B000000 2000 0000 | map leaf, size 10: @1B → @20 |
| 033 | 4C 6E616D65 | txt "name" |
| 038 | 3C 416461 | txt "Ada" |
| 03C | 0F 0A 33000000 3800 0000 | map leaf, size 10: @33 → @38 |
| 046 | 07 12 00910000 1100 0000 29000000 3C00 0000 | map branch, size 18, depth 0 (root), slots 8, 12, 15: @11 → @29 → @3C |
| 058 | 5C 236C696E6B | txt "#link" |
| 05E | 5C 235F616461 | txt "#_ada" |
| 064 | 0F 0A 58000000 5E00 0000 | map leaf, size 10: @58 → @5E |
| 06E | 3C 616765 | txt "age" |
| 072 | 02 29000000000000 00 | i64 41 |
| 07B | 0F 0A 6E000000 7200 0000 | map leaf, size 10: @6E → @72 |
| 085 | 6C 6D656E746F72 | txt "mentor" |
| 08C | 0F 0A 85000000 6400 0000 | map leaf, size 10: @85 → @64 |
| 096 | 4C 726F6C65 | txt "role" |
| 09B | 8C 656E67696E6565 72 | txt "engineer" |
| 0A4 | 0F 0A 96000000 9B00 0000 | map leaf, size 10: @96 → @9B |
| 0AE | 4C 6E616D65 | txt "name" |
| 0B3 | 3C 426F62 | txt "Bob" |
| 0B7 | 0F 0A AE000000 B300 0000 | map leaf, size 10: @AE → @B3 |
| 0C1 | 07 16 00990000 7B00 0000 8C000000 A400 0000 B7000000 | map branch, size 22, depth 0 (root), slots 8, 11, 12, 15: @7B → @8C → @A4 → @B7 |
| 0D7 | 3C 616765 | txt "age" |
| 0DB | 02 1D000000000000 00 | i64 29 |
| 0E4 | 0F 0A D7000000 DB00 0000 | map leaf, size 10: @D7 → @DB |
| 0EE | 4C 726F6C65 | txt "role" |
| 0F3 | 8C 64657369676E65 72 | txt "designer" |
| 0FC | 0F 0A EE000000 F300 0000 | map leaf, size 10: @EE → @F3 |
| 106 | 4C 6E616D65 | txt "name" |
| 10B | 2C 4379 | txt "Cy" |
| 10E | 0F 0A 06010000 0B01 0000 | map leaf, size 10: @106 → @10B |
| 118 | 07 12 00910000 E400 0000 FC000000 0E01 0000 | map branch, size 18, depth 0 (root), slots 8, 12, 15: @E4 → @FC → @10E |
| 12A | 6C 617574686F72 | txt "author" |
| 131 | 5C 736D6F6B65 | txt "smoke" |
| 137 | 0F 0A 2A010000 3101 0000 | map leaf, size 10: @12A → @131 |
| 141 | 6C 726561736F6E | txt "reason" |
| 148 | 4C 73656564 | txt "seed" |
| 14D | 0F 0A 41010000 4801 0000 | map leaf, size 10: @141 → @148 |
| 157 | 07 0E 40080000 3701 0000 4D010000 | map branch, size 14, depth 0 (root), slots 6, 11: @137 → @14D |
| 165 | 4C 235F7478 | txt "#_tx" |
| 16A | 0F 0A 65010000 5701 0000 | map leaf, size 10: @165 → @157 |
| 174 | 4C 235F6379 | txt "#_cy" |
| 179 | 0F 0A 74010000 1801 0000 | map leaf, size 10: @174 → @118 |
| 183 | 5C 235F626F62 | txt "#_bob" |
| 189 | 0F 0A 83010000 C100 0000 | map leaf, size 10: @183 → @C1 |
| 193 | 5C 235F616461 | txt "#_ada" |
| 199 | 0F 0A 93010000 4600 0000 | map leaf, size 10: @193 → @46 |
| 1A3 | 07 16 88210000 6A01 0000 79010000 8901 0000 99010000 | map branch, size 22, depth 0 (root), slots 3, 7, 8, 13: @16A → @179 → @189 → @199 |
| 1B9 | A3010000 00000000 | root → @1A3; previous: none |
query_text holds this document:
| Offset (hex) | Bytes (hex) | Meaning |
|---|---|---|
| 00 | 54 52 4F 4E | magic TRON |
| 04 | 5C 3F6E616D65 | txt "?name" |
| 0A | 0E 0D 00 0100 010000 00 04000000 | array leaf, size 13, shift 0, bitmap 0001, count 1 (root): @4 |
| 17 | 2C 3F65 | txt "?e" |
| 1A | 4C 6E616D65 | txt "name" |
| 1F | 5C 3F6E616D65 | txt "?name" |
| 25 | 0E 15 00 0700 030000 00 17000000 1A0000 00 1F000000 | array leaf, size 21, shift 0, bitmap 0007, count 3 (root): @17 → @1A → @1F |
| 3A | 0E 0D 00 0100 010000 00 25000000 | array leaf, size 13, shift 0, bitmap 0001, count 1 (root): @25 |
| 47 | 5C 3F6E616D65 | txt "?name" |
| 4D | 0E 0D 00 0100 010000 00 47000000 | array leaf, size 13, shift 0, bitmap 0001, count 1 (root): @47 |
| 5A | 5C 7768657265 | txt "where" |
| 60 | 0F 0A 5A000000 3A00 0000 | map leaf, size 10: @5A → @3A |
| 6A | 4C 66696E64 | txt "find" |
| 6F | 0F 0A 6A000000 0A00 0000 | map leaf, size 10: @6A → @A |
| 79 | 7C 6F726465724279 | txt "orderBy" |
| 81 | 0F 0A 79000000 4D00 0000 | map leaf, size 10: @79 → @4D |
| 8B | 07 12 2A000000 6000 0000 6F000000 8100 0000 | map branch, size 18, depth 0 (root), slots 1, 3, 5: @60 → @6F → @81 |
| 9D | 8B000000 00000000 | root → @8B; previous: none |
stardust_db *opened = NULL;
stardust_document *seed = NULL;
stardust_document *definition = NULL;
stardust_result *seeded = NULL;
stardust_db_query *query = NULL;
stardust_db_rows *rows = NULL;
stardust_result *page = NULL;
stardust_document *result = NULL;
stardust_bytes path = {(const uint8_t *)database_path, (int64_t)strlen(database_path)};
stardust_result *opening = NULL;
uint32_t status = stardust_db_open(path, NULL, &opened, &opening);
stardust_result_release(opening);
if (status == STARDUST_OK) status = stardust_parse(seed_text, STARDUST_TRON, &seed);
if (status == STARDUST_OK) status = stardust_db_merge_patch(opened, seed, NULL, NULL, &seeded);
if (status == STARDUST_OK) status = stardust_parse(query_text, STARDUST_TRON, &definition);
stardust_db_source source = {.kind = STARDUST_SOURCE_DOCUMENT, .document = definition};
if (status == STARDUST_OK) status = stardust_db_query_open(opened, &source, &query);
if (status == STARDUST_OK) status = stardust_db_query_run(query, NULL, NULL, NULL, &rows);
if (status == STARDUST_OK) status = stardust_db_rows_next(rows, &page);
if (status == STARDUST_OK) status = stardust_result_take_document(page, &result);
stardust_result_release(page);
stardust_db_rows_release(rows);
stardust_db_query_release(query);
stardust_document_release(definition);
stardust_result_release(seeded);
stardust_document_release(seed);
stardust_db_close(opened);
if (status != STARDUST_OK) {stardust_document_release(result); return status;}| Offset (hex) | Bytes (hex) | Meaning |
|---|---|---|
| 00 | 54 52 4F 4E | magic TRON |
| 04 | 4C 6E616D65 | txt "name" |
| 09 | 3C 416461 | txt "Ada" |
| 0D | 0F 0A 04000000 0900 0000 | map leaf, size 10: @4 → @9 |
| 17 | 4C 6E616D65 | txt "name" |
| 1C | 3C 426F62 | txt "Bob" |
| 20 | 0F 0A 17000000 1C00 0000 | map leaf, size 10: @17 → @1C |
| 2A | 4C 6E616D65 | txt "name" |
| 2F | 2C 4379 | txt "Cy" |
| 32 | 0F 0A 2A000000 2F00 0000 | map leaf, size 10: @2A → @2F |
| 3C | 0E 15 00 0700 030000 00 0D000000 200000 00 32000000 | array leaf, size 21, shift 0, bitmap 0007, count 3 (root): @D → @20 → @32 |
| 51 | 3C000000 00000000 | root → @3C; previous: none |
Standard JSON values
Any value that the standard JSON encoder of your language accepts also converts here, and decode reads back what encode writes: encoding/json in Go, json.dumps in Python, serde_json in Rust, cheshire or data.json in Clojure and core:encoding/json in Odin. Native tags come first, so instants, durations and links become #utc, #dur and #link values instead of JSON text. Several languages accept more, such as Python sets and UUIDs, or Clojure java.time values. A value is refused only where that encoder refuses it, or where a document cannot hold it: a non-finite number, an integer outside i64, a decimal, instant or duration without an exact native form, or a cycle. The host mapping gives each type.
Ownership and errors
Encode returns a new owned document that holds a snapshot of the value. Close it as you close any document. Decode borrows a document only for the call and returns independent values. It never closes or keeps the document. Typed row iteration borrows the rows and reads one page at a time. Close the rows yourself. Every page of one cursor reads the same store snapshot, so iteration never repeats the query.
A value-argument operation accepts a language value or a document. It passes a document through unchanged. It encodes a language value into a temporary document, calls the native operation once, and closes the temporary document on success and failure.
A conversion failure is invalid_argument with a code and a value path such as people[1].age. A failed decode returns no partly filled value.
Costs
Encode and decode copy every value. A document that you pass directly is not copied. Type inspection belongs only to the typed layer, and each language keeps per-type plans where it inspects types at run time. No conversion writes or reads DUST, EDN or TRON text, and JSON text appears only where a Go value supplies its own MarshalJSON or UnmarshalJSON method. Those formats are optional boundaries, not an intermediate transport.
The conversion rules and the host mapping below give each type, the null and missing-field rules and the strict mode.
Operations
| Operation | Returns | Summary |
|---|---|---|
| from_value | Unavailable | from_value(value) copies host values into an owned document. |
| to_value | Unavailable | document.to_value() copies a document into host values. |
from_value
C has no host value model. Use native document builders.
Also calls: stardust_document_builder_create, stardust_document_builder_add_many, stardust_document_builder_seal, stardust_document_builder_error, stardust_document_builder_release
to_value
C has no host value model. Use native typed reads.
Also calls: stardust_document_count, stardust_document_read_many, stardust_document_i64, stardust_document_f64
Typed operations
C has no language-generic typed layer. Construct documents with Building documents, read them with Documents, and pass document handles to native operations. The generated bindings/c/stardust_time.h header converts native ticks and time-tag text to and from standard struct timespec values. The SDK uses these helpers for generated time fields.
Conversion rules
| Value | Rule |
|---|---|
standard | Every value that the language's standard JSON encoder accepts converts: encoding/json in Go, json.dumps in Python, serde_json in Rust, cheshire or data.json in Clojure and core:encoding/json in Odin. Decode reads back what encode writes. Refusals remain only where that encoder refuses, or where a document cannot hold the value: a non-finite number, an integer outside i64, an inexact decimal, instant or duration, and a cycle. |
null | A nullable target (pointer, option, Maybe, nil slice or map, None, nil) accepts null. A non-nullable target refuses it with wrong_kind. Explicit null is not a missing field. |
boolean | Only booleans: no truthiness and no number or string coercion. |
integer | Numbers are i64 or f64. A host integer of any width or signedness converts when it fits i64; a larger one fails with out_of_range, in both directions. Narrower targets are range-checked (out_of_range). |
real | Finite f64 only; NaN and infinities are refused. An integer decodes into a floating target only when exactly representable, and a real into an integer target only when finite, integral and in range. f32 narrowing refuses overflow and rounds to nearest. An exact decimal (Python Decimal, Clojure BigDecimal) is an i64 when integral and in range, otherwise the f64 of its shortest spelling; any other decimal fails with inexact_number. |
text | Valid UTF-8, copied without normalization. Invalid UTF-8 and unpaired surrogates are refused. A string argument is a string value; it is never parsed as document text. Values that the standard JSON encoder writes as text stay text: UUIDs, dates and times that are not instants, Go TextMarshaler values, and Go and Clojure byte arrays as base64. Text that does not parse as a typed target fails with invalid_value. |
array | Ordered collections, including tuples, sets and Java collections. A fixed-length target needs the exact length (length_mismatch). A nil slice or list encodes null; an allocated empty one encodes an empty array. |
object | Struct, record or dataclass fields and maps. Field names follow the standard encoder's tags and embedding rules; Go also matches a key case-insensitively after an exact match, as encoding/json does. Map keys that are not strings become member keys as the standard encoder spells them (decimal integers, true, false, enum names, MarshalText text) and parse back into typed keys. Typed decode refuses a repeated key (duplicate_field); native documents keep both members. Unknown keys are ignored unless strict mode refuses them (unknown_field). A missing field keeps the language default or fails as required (missing_field). |
enum | Enums use the standard encoder's form: Rust variants as serde represents them, Python Enum members as their value, Odin enums by name. Odin unions write their active variant and decode into the one variant whose shape matches (ambiguous_variant otherwise). |
tagged | Native tags take priority over any JSON spelling. Links, UTC instants and durations use the native {#link id}, {#utc text} and {#dur text} objects through the Link type and standard-library instant and duration types. Rust uses #[serde(with = "stardust::Utc")] and #[serde(with = "stardust::Dur")] field adapters to select native tags without guessing from record names. Extra types are only for native values that the standard library cannot represent, such as signed Rust durations. An ordinary object is never reinterpreted as a tagged value. A time value that is not a whole number of 100 ns ticks is refused. Direct converters report inexact_number; Rust Serde adapter errors use the custom-hook codes unsupported_type on encode and wrong_kind on decode, with the field path. |
hooks | Conversion methods run as the standard encoder runs them: Go Encoder and Decoder hooks first, then json.Marshaler, encoding.TextMarshaler and their decode methods; Rust Serialize and Deserialize; Odin registered converters. A failing Go JSON or text method fails with hook_failed; other hooks return their own error. Only Go's JSON methods exchange JSON text, through the native JSON codec. |
limits | Recursive conversions refuse nesting beyond the depth limit with too_deep. Go and Python untyped decode traverse iteratively and accept deeper documents. Cyclic host values fail with cycle, or too_deep in Clojure. Every failure has status invalid_argument, a code and a value path such as people[3].age or tags["a.b"]. Failed conversions return no partial value or document. |
Host mapping
C has no host-value conversion. Use a document handle and the typed reads on the Documents page.