moonfory

    The Fory cross-language binary format for MoonBit.

    serialization
    fory
    moonbit
    Download zip
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    yesterday
    Downloads
    19

    #moonfory

    A MoonBit value, written in Apache Fory's two wire formats: the cross-language binary format that Java, Go, Python, Rust and C++ already read, and the row format that lets a reader take one field without building the object.

    let bytes = @moonfory.dumps(42) // 01 ff 04 2a 00 00 00
    let back : Int = @moonfory.loads(bytes)

    The bytes are not this package's opinion. They are the ones the specification fixes, and the tests assert them one at a time — including the bytes pyfory writes, which this package reads back unchanged.

    #Install

    moon add moonbitstack/moonfory

    In moon.pkg:

    import {
    "moonbitstack/moonfory",
    }

    Then @lib is the package. Dependencies are moonbitstack/moonvar for the numbers, moonbitstack/moondate for the time types, and moonbitstack/moonmodel for the description of a struct.

    The tests need one more, moonbitstack/moonbase/base16, because a fixture is read and written as hex and hex is not this package's business: it does not export hex or unhex.

    #Documentation

    • The site — the guide, the annotated byte examples and the reference.
    • API reference — one page per public item, generated from the interface and the doc comments below.

    #Two entry points, four verbs

    Every value goes in and comes out through dumps and loads. The dump/load pair does the same thing over a Buffer or a Reader you already hold, which is what you want when a message is one part of a larger stream.

    // One value, all of it.
    let bytes : Bytes = @moonfory.dumps(value)
    let value : T = @moonfory.loads(bytes)

    // The same, without allocating a fresh buffer.
    @moonfory.dump(value, buffer)
    let value : T = @moonfory.load(reader)

    FunctionTakesGives
    dumps(value, ...)any Codec valuethe whole message as Bytes
    loads(bytes, schemas?, ...)a messagethe value, or a Refused
    dump(value, buffer, ...)a value and a bufferwrites into the buffer
    load(reader)a readerone value
    write_message(w, value)a writer and a valuehead, value, nothing else
    read_message(r)a readerthe one value the head announces
    write_value(w, v) / read_value(r)a Valuethe dynamic form

    loads reads exactly one value and then insists the message is finished, so two values in one buffer are a Trailing error rather than a silent truncation.

    #Making a type travel

    A type becomes serialisable by implementing Codec. write_data writes the body only — the type ID belongs to whoever knows the schema, so a list element and a struct field can each name their own type without the body repeating it.

    pub impl Codec for UserId with fn ty(_self) {
    TypeId::Int64
    }

    pub impl Codec for UserId with fn write_data(self, w) {
    w.write_i64(self.0)
    }

    pub impl Codec for UserId with fn read_data(r, _ty) {
    UserId(r.read_i64())
    }

    Bool, Int, Int64, UInt, UInt64, Byte, Float, Double, String, Bytes, Array[T], Map[K, V] and Value already implement it.

    #The type table

    Fory typeCodeMoonBitWritten as
    BOOL1Boolone byte, 00 or 01
    INT8 / INT16 / INT322 / 3 / 4Byte / Inttwo's complement, little-endian
    INT646Int64eight bytes
    VARINT32 / VARINT645 / 7any signed widthzig-zag, base-128
    TAGGED_INT64 / UINT648 / 15Int64 / UInt64four bytes when small, else nine
    FLOAT32 / FLOAT6419 / 20Float / DoubleIEEE 754, little-endian
    STRING21Stringlength-and-encoding header, then UTF-8
    LIST / SET22 / 23Array[T]count, element header, elements
    MAP24Map[K, V]total, then key/value chunks
    STRUCT27StructMeta + fieldsuser id, schema fingerprint, then fields in Fory order
    ENUM25ordinala varint
    DURATION / TIMESTAMP / DATE37 / 38 / 39@moondate.Span / Moment / Datesee below
    DECIMAL40(Int, BigInt)scale, then the unscaled integer
    BINARY41Byteslength, then the bytes
    BOOL_ARRAY … FLOAT64_ARRAY43 … 56Array[T]raw little-endian, no header per element

    Strings are written as UTF-8. A reader also accepts LATIN1 and UTF-16, because pyfory writes LATIN1 for ASCII and the format allows it; only the writer's choice is fixed here.

    #Numbers, in one place

    Int is written as INT32, Int64 as INT64, UInt as UINT32, UInt64 as UINT64. The reader is looser on purpose: it accepts any width of the same signedness, so a peer's VARINT64 arrives as an Int without a byte of conversion. A 64-bit value asked for as a 32-bit type is refused rather than truncated.

    #Configuration

    Every knob has a default, and every default is the Rust fory crate's, because that is the reference the specification names and a default nobody shares is a default an interop test finds.

    KnobDefaultWhat it means
    xlangtruethe header carries the cross-language flag
    track_reffalsevalues written twice are sent once and pointed at
    check_stringtruestring bytes are checked as UTF-8 before becoming a String
    depth5how deeply values may nest before the message is refused
    fields512how many fields a struct may declare
    graph134_217_728how many variable-length bytes one message may declare

    Precedence. A call has three places to say what it wants, and they are resolved in one order. The library preset (config) is the floor. A Config value passed as config= replaces it wholesale. The flat mirrors — xlang=, track_ref=, check_string=, depth=, fields=, graph= — then override single fields of that result. When a Config and a flat mirror disagree, the flat mirror wins, unless wins=Base is passed, in which case the record wins. A disagreement is silent by default; clash=Panic turns it into a Clashed error, and clash=Handle(f) lets you arbitrate.

    // The preset, with one field changed.
    @moonfory.dumps(v, track_ref=true)

    // A whole config, then one field overridden on top of it.
    @moonfory.dumps(v, config=my_config, depth=8)

    // Refuse rather than pick when the two disagree.
    @moonfory.dumps(v, config=my_config, xlang=false, clash=Panic)

    #Describing a struct

    A struct's fields are a @moonmodel description — the same one an OpenAPI document or a CREATE TABLE would be generated from — plus the three things the wire needs that a description has no word for: the type ID the field goes out as, the tag that identifies it when the schema configures one, and whether the value may be shared.

    let point = @moonfory.StructMeta::new("Point", 100, [
    @moonfory.FieldMeta::new("x", @moonfory.TypeId::VarInt32),
    @moonfory.FieldMeta::new("y", @moonfory.TypeId::VarInt32),
    ])

    let person = @moonfory.StructMeta::new("Person", 101, [
    @moonfory.FieldMeta::new("name", @moonfory.TypeId::String),
    @moonfory.FieldMeta::new("age", @moonfory.TypeId::VarInt32),
    // A list says what it holds, because the writer may leave the element type
    // out of the header.
    @moonfory.FieldMeta::new(
    "tags", @moonfory.TypeId::List, element=Some(@moonfory.TypeId::String),
    ),
    // A field whose type is declared sends no type ID, so the schema has to say
    // which struct it is.
    @moonfory.FieldMeta::new(
    "home", @moonfory.TypeId::Struct, nullable=true, child=Some(point),
    ),
    ])

    From a #model.derive model, StructMeta::of fills the type IDs from the description's kinds, and takes the child schemas by name:

    let meta = @moonfory.StructMeta::of(
    model, user=101, children=[point], refs=["home"],
    )

    A field's description is FieldMeta::desc, and meta.model() hands the whole @moonmodel.Model back, so one description can be published as a schema and used as a codec without being written twice. A kind with no wire type — Any — is refused rather than guessed at.

    #The schema fingerprint

    Every struct carries a four-byte fingerprint of its schema, and a peer checks it against its own before it trusts a field. It is identifier,typeID,ref,nullable; per field, in identifier order, hashed with MurmurHash3 x64 128 seeded with 47.

    Two conventions had to be picked, because the reference implementations differ: a field of a user type writes 0 for its type ID and 0 for its ref flag (neither can be compared across languages), and a list writes its element type as [type,0,0]. Under those two rules the fingerprints this package computes are the ones pyfory writes, byte for byte, and the all-primitive ones Go writes too.

    #Shared and circular values

    MoonBit has no object identity to track, so identity is a key you choose and the value carries: Shared(key, value) says "this is the value filed under key", and Pointer(key) says "the value already filed under key". The first one is written out in full; the second is two bytes.

    let node = @moonfory.StructMeta::new("Node", 300, [
    @moonfory.FieldMeta::new("val", @moonfory.TypeId::VarInt32),
    @moonfory.FieldMeta::new("next", @moonfory.TypeId::Struct, track_ref=true),
    ])

    // A node whose `next` is itself. Reference zero is the root, which this is.
    let value = @moonfory.Value::Struct(node, [
    ("val", @moonfory.Value::Int(1L)),
    ("next", @moonfory.Value::Pointer(0)),
    ])

    @moonfory.dumps(value, track_ref=true) // 01 00 1b ac02 eecb234f 02 fe 00

    Those are the bytes Go's fory writes for struct Node { Val int32; Next *Node }, byte for byte, and Go reads them back into a struct whose Next is itself. A value that comes back from a peer is the same shape: the node it starts at is a Shared, and the field that leads back to it is a Pointer to the same key, so the value can be held without holding itself and written again unchanged.

    A typed value is marked the same way, with shared for the writer's key:

    let once = @moonfory.shared(7, "the same string")
    let twice = [once, once] // two references, one body
    let bytes = @moonfory.dumps(twice, track_ref=true)

    Turn track_ref on for both ends, or neither: a reader that is not tracking reads the first body and then a flag it does not expect. Python's pyfory cannot write a cycle at all — its serializer recurses — so Go is the peer the cyclic fixtures are checked against.

    #The row format

    The row format trades a little space for random access: eight bytes per field, whether the field needs them or not, so field i is always at bitmap + i * 8. A reader takes one field and leaves the rest alone.

    let schema = @moonfory.RowSchema::new([
    { name: "id", ty: @moonfory.RowType::Int64, nullable: false },
    { name: "name", ty: @moonfory.RowType::String, nullable: false },
    { name: "age", ty: @moonfory.RowType::Int32, nullable: true },
    ])

    let row = @moonfory.Row::new(schema)
    row.set(0, @moonfory.RowValue::Int64(7L))
    row.set(1, @moonfory.RowValue::String("ada"))
    row.set(2, @moonfory.RowValue::Null)

    let bytes = row.finish()
    let cur = @moonfory.RowCursor::new(bytes[:], schema)
    cur.get_field(1) // RowValue::String("ada"), field 0 never read
    cur.is_null(2) // true

    The layout is the specification's standard variant: a null bitmap rounded to eight bytes, one eight-byte slot per field, then the variable data. An array keeps fixed-width elements at their natural width and variable ones as offset-and-size pairs; a map is a key array and a value array; a nested struct is a whole row inside the variable region. The compact variant is Java-only and is not implemented here.

    #Time

    Through moonbitstack/moondate, so a MoonBit program never spells out epoch seconds:

    let date = @moonfory.date_of_days(19_000L) // a Date, as DATE
    let moment = @moonfory.moment_of_parts(1_700_000_000L, 0) // a Moment, as TIMESTAMP
    let span = @moonfory.span_of_parts(3L, 500_000_000) // a Span, as DURATION
    @moonfory.dumps(moment) // and back through loads

    Nanoseconds are always the sub-second part, in [0, 1e9), so a value of 1.5 seconds is (1, 500_000_000) and a negative span borrows a second rather than carrying a negative nanosecond field.

    #What is deliberately not here

    ThingWhy not
    Compatible struct mode (28, 30)It needs a schema registry to reconcile two type definitions; that belongs with moonmodel, not inside a codec
    FLOAT8 / FLOAT8_ARRAY (16, 56)No peer in reach writes them; when one does, the table has the code
    Reading a struct without its schemaThe schema is the user id's meaning; loads(schemas=[...]) supplies it
    inferring which value is sharedIdentity is not in a MoonBit value; say Shared/Pointer and the wire follows

    Each refusal is a Refused variant with the offset that produced it, so a malformed message names where it stopped rather than "invalid input".

    #Errors

    VariantWhen
    Truncated(at, want, got)the buffer ended before the value did
    TooLarge(at, limit, got)a declared length or count crossed a limit
    TooDeep(at, limit)nesting crossed depth
    BadType(at, got) / WantType(at, want, got)the type ID is unknown, or not the one asked for
    BadHeader(at, got)the header's flag bits are not this format's
    BadRef(at, got)a back-reference named an id never written
    BadEncoding(at, got)a string's encoding tag is not one of the three
    Cyclic(at, got)a cycle that cannot be resolved
    Trailing(at, got)bytes remain after the one message
    Unsupported(kind)the type has no counterpart here

    #Interop, as a test

    Every class of message this package reads has been read from bytes another implementation wrote, and the bytes that go the other way have been read by that implementation. The two peers are pyfory for Python and fory for Go.

    The fixtures are generated by scripts/interop.py and scripts/interop.go, run by hand, and checked in as a MoonBit file the tests read. CI runs the tests and needs neither language installed, so a byte-level interop test costs a second per push rather than a toolchain.

    python3 scripts/interop.py # writes src/fixtures_pyfory_test.mbt (cd scripts && go run .) # writes src/fixtures_go_test.mbt moon fmt # the generated files are formatted by the toolchain

    A fixture is the bytes and nothing else: the MoonBit side says what the value is. A test that could be made to pass by changing both sides at once is not one, so the expectation for each fixture is written out by hand in src/fixture_test.mbt.

    Three things the fixtures found, and this package now does: a nested struct goes out as its fingerprint and its fields with no type ID in front, a list whose element type the schema declares goes out without the type in its header, and a dense array goes out as a byte count and raw elements rather than as a list.

    #The API reference is generated

    docs/api/ is written by scripts/api.py, not by hand: it reads src/pkg.generated.mbti for the surface exactly as moon info prints it, and the /// comments in src/*.mbt for the prose, which is why a sentence about a function lives beside the function. One page per public item, a source link on each, and a page for the directory itself.

    python3 scripts/api.py # after changing an interface or a doc comment

    CI runs the same command and fails if the result differs from what is committed, so a page cannot outlive the function it describes.

    #Specifications

    #Licence

    Apache-2.0. See LICENSE.

    Codec

    pub trait Codec {
    fn ty(Self) -> TypeId
    fn write_data(Self, Writer) -> Unit raise
    fn read_data(Reader, ty : TypeId) -> Self raise
    }

    A value that knows how to become bytes, and how to come back from them.

    The contract is one line: write_data and read_data are everything after the type ID. The type ID belongs to whoever knows the schema — a struct field to its FieldMeta, a list to its header, a map to its chunk header — and what follows belongs to the value, which is why a struct or an enum writes its user type ID here and not there.

    Everything this package ships implements this trait, and a caller's own type implements it the same way: there is no second path for the types that come with the library.
    impl Codec for Bool
    impl Codec for Byte
    impl Codec for Int
    impl Codec for Int64
    impl Codec for UInt
    impl Codec for UInt64
    impl Codec for Float
    impl Codec for Double
    impl Codec for String
    impl Codec for Bytes
    impl Codec for Array[T]
    impl Codec for Map[K, V]
    impl Codec for Date
    impl Codec for Moment
    impl Codec for Span

    Clashed

    pub(all) suberror Clashed {
    Clashed(name~ : String)
    } derive(Eq,
    Debug
    )

    What OnClash::Panic raises: a setting given twice where the caller asked to hear about it.
    impl Show for Clashed

    Clashed::equal

    fn Clashed::equal(Clashed, Clashed) -> Bool

    Clashed::not_equal

    fn Clashed::not_equal(x : Clashed, y : Clashed) -> Bool

    Clashed::output

    fn Clashed::output(self : Clashed, logger : &Logger) -> Unit

    Clashed::to_repr

    Clashed::to_string

    fn Clashed::to_string(self : Clashed) -> String

    Refused

    pub(all) suberror Refused {
    Truncated(at~ : Int, want~ : Int, got~ : Int)
    TooLarge(at~ : Int, limit~ : Int64, got~ : Int64)
    TooDeep(at~ : Int, limit~ : Int)
    BadType(at~ : Int, got~ : Int)
    WantType(at~ : Int, want~ : Int, got~ : Int)
    BadHeader(at~ : Int, got~ : Int)
    BadRef(at~ : Int, got~ : Int)
    BadEncoding(at~ : Int, got~ : Int)
    Cyclic(at~ : Int, got~ : Int)
    Trailing(at~ : Int, got~ : Int)
    Unsupported(kind~ : String)
    } derive(Eq,
    Debug
    )

    Why a message was refused.

    A message that cannot be read is a refusal, never a partial answer: every name here is something the writer did that the specification does not allow, and every one of them carries the offset it happened at, because a byte stream has no line numbers to point at.
    impl Show for Refused

    Refused::equal

    fn Refused::equal(Refused, Refused) -> Bool

    Refused::not_equal

    fn Refused::not_equal(x : Refused, y : Refused) -> Bool

    Refused::output

    fn Refused::output(self : Refused, logger : &Logger) -> Unit

    Refused::to_repr

    Refused::to_string

    fn Refused::to_string(self : Refused) -> String

    Config

    pub(all) struct Config {
    xlang : Bool
    track_ref : Bool
    check_string : Bool
    depth : Int
    fields : Int
    graph : Int
    }

    What a reader will accept and what a writer will send.

    Every default here is the Rust fory crate's Config::default(), because that is the reference implementation the specification names first, and because a default nobody else shares is a default an interop test finds. Where this package has no counterpart for one of its knobs — the schema version check, the type-meta cache — the knob is absent rather than inert.

    Config::new

    fn Config::new(xlang? : Bool, track_ref? : Bool, check_string? : Bool, depth? : Int, fields? : Int, graph? : Int) -> Config

    The same preset, one argument at a time.

    For when several parts differ at once and a record update would mean spelling the ones that did not change.

    FieldMeta

    pub(all) struct FieldMeta {
    desc :
    Field

    ty : TypeId
    tag : Int?
    track_ref : Bool
    child : StructMeta?
    element : TypeId?
    } derive(Eq,
    Debug
    )

    One field of a struct: the description it carries, and what the wire needs that a description has no word for.

    The description is a @moonmodel.Field — a name, a kind, whether the value may be absent, and what it means — so that a type described once can be written by this package and still be published as a JSON Schema or a CREATE TABLE without being written down twice. The three other members are the wire's own: the type ID that kind goes out as, the tag that identifies the field when the schema configures one, and whether the value may be shared.
    impl Show for FieldMeta

    FieldMeta::equal

    fn FieldMeta::equal(FieldMeta, FieldMeta) -> Bool

    FieldMeta::from

    fn FieldMeta::from(desc :
    Field
    , tag? : Int?, track_ref? : Bool, child? : StructMeta?, element? : TypeId?) -> FieldMeta raise

    The field a description asks for: its kind decides the type ID.

    A kind with no wire type — Any, or a Raw that does not name one of this format's own — is refused rather than guessed at, because a field whose type the other side cannot name is a field neither side can read.

    FieldMeta::identifier

    fn FieldMeta::identifier(self : FieldMeta) -> String

    The field's identifier as the order sees it: the tag when there is one, and the snake_case of the name when there is not.

    FieldMeta::kind

    The kind the description gives this field.

    FieldMeta::name

    fn FieldMeta::name(self : FieldMeta) -> String

    The field's name, which is what a peer reads it by.

    FieldMeta::new

    fn FieldMeta::new(name : String, ty : TypeId, tag? : Int?, nullable? : Bool, track_ref? : Bool, child? : StructMeta?, element? : TypeId?) -> FieldMeta

    A field that is simply there, named by hand.

    nullable is a question about the value and goes into the description; a kind the description has no word for is written as Raw, and the type ID the caller gave stays the one it goes out as.

    FieldMeta::not_equal

    fn FieldMeta::not_equal(x : FieldMeta, y : FieldMeta) -> Bool

    FieldMeta::nullable

    fn FieldMeta::nullable(self : FieldMeta) -> Bool

    Whether the value may be absent. A nullable field is preceded by a flag byte and sorts after every primitive that is not.

    FieldMeta::output

    fn FieldMeta::output(self : FieldMeta, logger : &Logger) -> Unit

    FieldMeta::to_string

    fn FieldMeta::to_string(self : FieldMeta) -> String

    OnClash

    pub(all) enum OnClash[T] {
    Ignore
    Panic
    Handle((T, T) -> T)
    }

    What to do when the same setting is given twice, once in the record and once as a flat argument.

    Reader

    pub(all) struct Reader {
    view : BytesView
    config : Config
    at : Int
    refs : Array[Int]
    nodes : Map[Int, Value]
    schemas : Map[Int, StructMeta]
    replay : Int
    replaying : Array[Int]
    depth : Int
    spent : Int
    }

    Where a value comes from: the bytes, the place in them, and what has been read already.

    The mirror of Writer. A reader keeps two things about references: where each one started, so a value can be read again, and the container being built for each one, so a cycle can point at a value that is not finished.

    Reader::end

    fn Reader::end(self : Reader) -> Unit raise

    Refuse the bytes left over once a value has been read.

    A message with two values in it is two messages, and reading one of them and ignoring the rest is how a truncation goes unnoticed.

    Reader::enter

    fn Reader::enter(self : Reader) -> Unit raise

    Go one level deeper, refusing a message nested past what was configured.

    Reader::enter_ref

    fn Reader::enter_ref(self : Reader, id : Int) -> Int raise

    Go back to where the value of reference id was written and read it again, which is what a reference asks for when the value was not a container put aside. The answer is the offset to hand to leave_ref afterwards.

    A reference that points into a value still being read is a cycle this package cannot rebuild — a MoonBit value is finished before it is held — so it is refused rather than read until the stack runs out.

    Reader::is_done

    fn Reader::is_done(self : Reader) -> Bool

    Whether every byte has been read.

    Reader::leave

    fn Reader::leave(self : Reader) -> Unit

    Come back one level.

    Reader::leave_ref

    fn Reader::leave_ref(self : Reader, saved : Int) -> Unit

    Come back from a replay to where the read was.

    Reader::new

    fn Reader::new(bytes : Bytes, config? : Config, schemas? : Array[StructMeta]) -> Reader

    A reader at the start of bytes, knowing the structs named in schemas.

    A struct arrives as a user type ID and nothing else, so the schema it stands for has to be given to the reader before it is met.

    Reader::node

    fn Reader::node(self : Reader, id : Int) -> Value?

    The container being built for a reference id, when there is one.

    A cycle points at a value that is not finished, so the container is put here before its contents are read, and the reference finds it.

    Reader::offset

    fn Reader::offset(self : Reader) -> Int

    Where the next byte is.

    Reader::read_binary

    fn Reader::read_binary(self : Reader) -> Bytes raise

    Bytes with their length in front: the BINARY type's body.

    Reader::read_bool

    fn Reader::read_bool(self : Reader) -> Bool raise

    A boolean: one byte, and everything but 1 is false.

    Reader::read_byte

    fn Reader::read_byte(self : Reader) -> Int raise

    One byte.

    Reader::read_bytes

    fn Reader::read_bytes(self : Reader, n : Int) -> Bytes raise

    n bytes, as they are.

    Reader::read_f32

    fn Reader::read_f32(self : Reader) -> Float raise

    A single-precision float.

    Reader::read_f64

    fn Reader::read_f64(self : Reader) -> Double raise

    A double-precision float.

    Reader::read_head

    fn Reader::read_head(self : Reader) -> Unit raise

    The header byte, checked against what this reader was asked for.

    A message that does not carry the cross-language flag is a message written by the native format, which is a different format.

    Reader::read_i16

    fn Reader::read_i16(self : Reader) -> Int raise

    Sixteen bits, signed, little endian.

    Reader::read_i32

    fn Reader::read_i32(self : Reader) -> Int raise

    Thirty-two bits, signed, little endian.

    Reader::read_i64

    fn Reader::read_i64(self : Reader) -> Int64 raise

    Sixty-four bits, signed, little endian.

    Reader::read_i8

    fn Reader::read_i8(self : Reader) -> Int raise

    Eight bits, read as signed, which is how the reference flags are written.

    Reader::read_ref_flag

    fn Reader::read_ref_flag(self : Reader) -> RefFlag raise

    The byte before a value, and what it says.

    A reference to an id this message has not written is a refusal: there is nothing to point at.

    Reader::read_string

    fn Reader::read_string(self : Reader) -> String raise

    A string: the header says how many bytes and which of the three encodings they are.

    Reader::read_tagged_i64

    fn Reader::read_tagged_i64(self : Reader) -> Int64 raise

    A sixty-four-bit signed integer that is usually small: four bytes whose low bit says whether eight more follow.

    Reader::read_tagged_u64

    fn Reader::read_tagged_u64(self : Reader) -> UInt64 raise

    A sixty-four-bit unsigned integer that is usually small.

    Reader::read_type_id

    fn Reader::read_type_id(self : Reader) -> TypeId raise

    A type ID byte.

    Reader::read_u16

    fn Reader::read_u16(self : Reader) -> Int raise

    Sixteen bits, unsigned, little endian.

    Reader::read_u32

    fn Reader::read_u32(self : Reader) -> UInt raise

    Thirty-two bits, unsigned, little endian.

    Reader::read_u64

    fn Reader::read_u64(self : Reader) -> UInt64 raise

    Sixty-four bits, unsigned, little endian.

    Reader::read_u8

    fn Reader::read_u8(self : Reader) -> Int raise

    Eight bits, unsigned.

    Reader::read_user_type

    fn Reader::read_user_type(self : Reader) -> (TypeId, Int) raise

    A type ID byte and the user type ID after it.

    Reader::read_var_i32

    fn Reader::read_var_i32(self : Reader) -> Int raise

    The same for thirty-two bits.

    Reader::read_var_i64

    fn Reader::read_var_i64(self : Reader) -> Int64 raise

    A zigzag-folded varint: the signed half of write_var_i64.

    Reader::read_var_u32

    fn Reader::read_var_u32(self : Reader) -> UInt raise

    A base-128 varint of thirty-two bits.

    Reader::read_var_u36

    fn Reader::read_var_u36(self : Reader) -> UInt64 raise

    The string header: one byte while it fits in seven bits, a varint when it does not.

    Reader::read_var_u64

    fn Reader::read_var_u64(self : Reader) -> UInt64 raise

    A base-128 varint.

    Reader::ref_offset

    fn Reader::ref_offset(self : Reader, id : Int) -> Int raise

    Where the value of a reference id starts, so it can be read again.

    Reader::remaining

    fn Reader::remaining(self : Reader) -> Int

    How many bytes are still unread.

    Reader::schema

    fn Reader::schema(self : Reader, user : Int) -> StructMeta?

    The struct registered under a user type ID, when this reader knows it.

    Reader::seek

    fn Reader::seek(self : Reader, at : Int) -> Unit

    Put the reader back at at.

    Reader::set_node

    fn Reader::set_node(self : Reader, id : Int, v : Value) -> Unit

    Remember the container being built for a reference id.

    Reader::spend

    fn Reader::spend(self : Reader, n : Int) -> Unit raise

    Count n bytes of declared payload against the budget for one message.

    RefFlag

    pub(all) enum RefFlag {
    Null
    Ref(id~ : Int)
    NotNull
    RefValue(id~ : Int)
    } derive(Eq,
    Debug
    )

    What the byte before a value can say about it.

    The four flags are the specification's, and they are how a nullable field, a shared value and a value that is simply there are told apart: one byte, before the value, that says which.

    RefFlag::equal

    fn RefFlag::equal(RefFlag, RefFlag) -> Bool

    RefFlag::not_equal

    fn RefFlag::not_equal(x : RefFlag, y : RefFlag) -> Bool

    RefFlag::to_repr

    Row

    pub(all) struct Row {
    schema : RowSchema
    values : Array[RowValue?]
    }

    A builder for one row. Create it from a schema, set each field in any order, then call finish to get the row's bytes.

    Row::finish

    fn Row::finish(self : Row) -> Bytes raise

    Serialise the row. A field left unset is written as null.

    Row::new

    fn Row::new(schema : RowSchema) -> Row

    Start a row against schema. Every field begins null until set.

    Row::set

    fn Row::set(self : Row, i : Int, v : RowValue) -> Unit raise

    Place v in field i. The value must fit the field's type, or the call refuses.

    RowCursor

    pub struct RowCursor {
    view : BytesView
    schema : RowSchema
    bitmap_size : Int
    fixed : Int
    base : Int
    }

    A reader over one row's bytes, with random access to any field.

    Build it from the same schema the row was written with, and from the bytes finish produced. base is normally zero; it is non-zero only when this cursor reads a nested struct inside a larger row.

    RowCursor::get_field

    fn RowCursor::get_field(self : RowCursor, i : Int) -> RowValue raise

    Read field i. A null field returns RowValue::Null; a variable field is read from the offset and size in its slot.

    RowCursor::is_null

    fn RowCursor::is_null(self : RowCursor, i : Int) -> Bool

    Whether field i is null.

    RowCursor::length

    fn RowCursor::length(self : RowCursor) -> Int

    The number of fields in the row.

    RowCursor::new

    fn RowCursor::new(view : BytesView, schema : RowSchema, base? : Int) -> RowCursor

    Open a cursor over a row's bytes. base defaults to zero for a top-level row.

    RowField

    pub(all) struct RowField {
    name : String
    ty : RowType
    nullable : Bool
    } derive(Eq,
    Debug
    )

    One field of a row schema: its name, its type, and whether it may be null.

    RowField::equal

    fn RowField::equal(RowField, RowField) -> Bool

    RowField::not_equal

    fn RowField::not_equal(x : RowField, y : RowField) -> Bool

    RowField::to_repr

    RowSchema

    pub(all) struct RowSchema {
    fields : Array[RowField]
    }

    A row schema: the ordered list of fields a row is built and read against.

    RowSchema::new

    fn RowSchema::new(fields : Array[RowField]) -> RowSchema

    Build a schema from an ordered list of fields.

    RowType

    pub(all) enum RowType {
    Bool
    Int8
    Int16
    Int32
    Int64
    Float32
    Float64
    String
    Binary
    Timestamp
    Date
    Duration
    Array(RowType)
    Map(RowType, RowType)
    Struct(Array[RowField])
    } derive(Eq,
    Debug
    )

    The type of one field in a row schema.

    It is the cross-language primitive set, plus the three shapes that live in the variable region: a homogeneous array, a key-and-value map, and a nested struct.

    RowType::equal

    fn RowType::equal(RowType, RowType) -> Bool

    RowType::not_equal

    fn RowType::not_equal(x : RowType, y : RowType) -> Bool

    RowType::to_repr

    RowValue

    pub(all) enum RowValue {
    Null
    Bool(Bool)
    Int8(Int)
    Int16(Int)
    Int32(Int)
    Int64(Int64)
    Float32(Double)
    Float64(Double)
    String(String)
    Binary(Bytes)
    Timestamp(Int64)
    Date(Int)
    Duration(Int64)
    Array(Array[RowValue])
    Map(Array[(RowValue, RowValue)])
    Struct(Array[RowValue])
    } derive(Eq,
    Debug
    )

    A value written into, or read out of, a row.

    It mirrors RowType rather than the cross-language Value, because the row format is schema-driven: the type of every field is fixed by the schema, not carried in the bytes. Null stands for a null field or a null array element.

    RowValue::equal

    fn RowValue::equal(RowValue, RowValue) -> Bool

    RowValue::not_equal

    fn RowValue::not_equal(x : RowValue, y : RowValue) -> Bool

    RowValue::to_repr

    Shared

    pub(all) struct Shared[T] {
    key : Int
    value : T
    } derive(Eq,
    Debug
    )

    A value that may be written more than once, or that takes part in a cycle.

    MoonBit has no identity to infer sharing from — physical_equal is a hint and not a promise — so the key is the caller's, the way Rc is the caller's in Rust: two writes of the same key send the value once and point at it afterwards.
    impl Codec for Shared[T]

    Shared::equal

    fn[T : Eq] Shared::equal(Shared[T], Shared[T]) -> Bool

    Shared::not_equal

    fn[T : Eq] Shared::not_equal(x : Shared[T], y : Shared[T]) -> Bool

    Shared::read_data

    fn[T : Codec] Shared::read_data(r : Reader, ty : TypeId) -> Shared[T] raise

    Shared::ty

    fn[T : Codec] Shared::ty(self : Shared[T]) -> TypeId

    Shared::write_data

    fn[T : Codec] Shared::write_data(self : Shared[T], w : Writer) -> Unit raise

    StructMeta

    pub struct StructMeta {
    model_ :
    Model

    id_ : Int
    wire_ : Array[(TypeId, Int?, Bool, StructMeta?, TypeId?)]
    } derive(Eq,
    Debug
    )

    A struct type: the description every projection shares, the user type ID it is registered under, and one wire record per field.

    The description is @moonmodel.Model, which is what #model.derive hands back for a MoonBit type. The wire records are index-aligned with the model's fields and carry the three things a description does not: the type ID, the tag and the reference policy.
    impl Show for StructMeta

    StructMeta::equal

    fn StructMeta::equal(StructMeta, StructMeta) -> Bool

    StructMeta::fields

    fn StructMeta::fields(self : StructMeta) -> Array[FieldMeta]

    The fields, in the order they are declared, each joined to its wire record.

    StructMeta::find

    fn StructMeta::find(self : StructMeta, name : String) -> FieldMeta?

    The field a name refers to, which is what a reader needs when the order on the wire is not the order it wants.

    StructMeta::hash

    fn StructMeta::hash(self : StructMeta) -> Int

    The number written after a user type ID in same-schema mode.

    StructMeta::model

    The description, as the projections outside this package read it.

    StructMeta::name

    fn StructMeta::name(self : StructMeta) -> String

    The struct's name, which a peer reading in compatible mode sees and which this package writes down in its errors.

    StructMeta::new

    fn StructMeta::new(name : String, user : Int, fields : Array[FieldMeta]) -> StructMeta

    A struct described field by field, which is how a schema is written when there is no @moonmodel.Model to hand.

    The model is built from the fields: its name is name, its fields are the descriptions the caller gave, in the order they were given.

    StructMeta::not_equal

    fn StructMeta::not_equal(x : StructMeta, y : StructMeta) -> Bool

    StructMeta::of

    fn StructMeta::of(model :
    Model
    , user~ : Int, tags? : Map[String, Int], refs? : Array[String], children? : Array[StructMeta]) -> StructMeta raise

    A struct described by a model, which is what #model.derive produces.

    tags names the fields whose identifier is a tag rather than a name, and refs the fields whose value may be shared; both are empty in the ordinary case, where a field is known by its name and sent as itself.

    A field whose kind has no wire type is refused: a description this format cannot carry is better named than silently dropped.

    StructMeta::order

    fn StructMeta::order(self : StructMeta) -> Array[Int]

    The indices of the fields in Fory order: the order their values are written in, which is not the order they were declared in.

    The order is computed rather than asked for, because a field added in the middle of a declaration is a change no peer should have to be told about twice.

    StructMeta::ordered

    fn StructMeta::ordered(self : StructMeta) -> Array[FieldMeta]

    The fields in Fory order.

    StructMeta::output

    fn StructMeta::output(self : StructMeta, logger : &Logger) -> Unit

    StructMeta::to_string

    fn StructMeta::to_string(self : StructMeta) -> String

    StructMeta::user

    fn StructMeta::user(self : StructMeta) -> Int

    The user type ID the struct is registered under.

    TypeId

    pub(all) enum TypeId {
    Unknown
    Bool
    Int8
    Int16
    Int32
    VarInt32
    Int64
    VarInt64
    TaggedInt64
    UInt8
    UInt16
    UInt32
    VarUInt32
    UInt64
    VarUInt64
    TaggedUInt64
    Float8
    Float16
    BFloat16
    Float32
    Float64
    String
    List
    Set
    Map
    Enum
    NamedEnum
    Struct
    CompatibleStruct
    NamedStruct
    NamedCompatibleStruct
    Ext
    NamedExt
    Union
    TypedUnion
    NamedUnion
    NoneType
    Duration
    Timestamp
    Date
    Decimal
    Binary
    Array
    BoolArray
    Int8Array
    Int16Array
    Int32Array
    Int64Array
    UInt8Array
    UInt16Array
    UInt32Array
    UInt64Array
    Float8Array
    Float16Array
    BFloat16Array
    Float32Array
    Float64Array
    } derive(Eq,
    Debug
    )

    The internal type ID: the byte that begins a value's type meta, and the number every language agrees on.

    The numbers are the specification's, and they are not ours to choose: a reader in Go reads 21 as a string because the type mapping says so. The variant names are the specification's too, except NoneType, which Python calls NoneType and the table calls NONE; MoonBit spells nothing None, so the spelling here is Python's.
    impl Show for TypeId

    TypeId::arrayed

    fn TypeId::arrayed(self : TypeId) -> TypeId?

    The dense array of this element type, when there is one.

    Only the fourteen element types above have one; array<string> stays a list, which is why the answer is optional.

    TypeId::code

    fn TypeId::code(self : TypeId) -> Int

    The number on the wire.

    TypeId::element

    fn TypeId::element(self : TypeId) -> TypeId?

    The element type of a dense array, which is what the bytes are.

    TypeId::equal

    fn TypeId::equal(TypeId, TypeId) -> Bool

    TypeId::fixed_size

    fn TypeId::fixed_size(self : TypeId) -> Int?

    How many bytes a value of this type occupies, when the type decides it.

    None for the compressed integers and for everything whose length is part of the value.

    TypeId::is_collection

    fn TypeId::is_collection(self : TypeId) -> Bool

    Whether values of this kind are counted: a list, a set or a map.

    TypeId::is_compressed

    fn TypeId::is_compressed(self : TypeId) -> Bool

    Whether the width of this type is decided by the value rather than by the type: the compressed integers, which sort after every fixed-width one.

    TypeId::is_dense

    fn TypeId::is_dense(self : TypeId) -> Bool

    Whether this kind is one of the dense primitive arrays: a run of raw little-endian elements rather than a list of values.

    TypeId::is_named_by_id

    fn TypeId::is_named_by_id(self : TypeId) -> Bool

    Whether this kind is one of the five that hold a user type ID in same-schema mode: the four that a struct can be, and the enum.

    TypeId::is_primitive

    fn TypeId::is_primitive(self : TypeId) -> Bool

    Whether a field of this type belongs to the primitive groups when the field order is decided: numbers and booleans, not strings and not containers.

    TypeId::is_user

    fn TypeId::is_user(self : TypeId) -> Bool

    Whether a value of this kind names a type of the writer's own, and so carries a user type ID or type meta after the type ID byte.

    These are the kinds a field must write type information for. An enum or a union inside a struct field does not: its type is the field's declared one.

    TypeId::kind

    The kind a type ID stands for, which is how a wire type is described to a projection that has never heard of this format.

    The widths collapse — every signed 32-bit type is Int, every 64-bit one a Big — because the description is not the place a width is decided. What the vocabulary has no word for is a Raw naming the wire type, which is what Raw is for; nothing reads inside it, and the wire record beside the description still holds the type ID itself.

    TypeId::not_equal

    fn TypeId::not_equal(x : TypeId, y : TypeId) -> Bool

    TypeId::of

    fn TypeId::of(code : Int) -> TypeId?

    The type a number on the wire stands for, when the mapping has one.

    57 and up are unmapped: the specification reserves them, and a reader that met one could not know what to build.

    TypeId::of_kind

    fn TypeId::of_kind(kind :
    Type
    ) -> TypeId raise

    The type ID a kind goes out as.

    The widths are the compact ones — a field that is Int goes out as a VARINT32 and a Big as a VARINT64 — because that is what every peer's own mapping table chooses for the same word, and a field only interoperates when the two sides agree on the type ID as well as on the value.

    A kind the format cannot carry — Any, which has no type ID at all — is refused by name.

    TypeId::output

    fn TypeId::output(self : TypeId, logger : &Logger) -> Unit

    TypeId::to_repr

    TypeId::to_string

    fn TypeId::to_string(self : TypeId) -> String

    Value

    pub(all) enum Value {
    Null
    Bool(Bool)
    Int(Int64)
    UInt(UInt64)
    Float(Double)
    Str(String)
    Binary(Bytes)
    List(TypeId, Array[Value])
    Map(Array[(Value, Value)])
    Struct(StructMeta, Array[(String, Value)])
    Enum(Int, Int)
    Timestamp(Int64, Int)
    Date(Int64)
    Duration(Int64, Int)
    Decimal(Int,
    BigInt
    )
    Shared(Int, Value)
    Pointer(Int)
    } derive(Eq,
    Debug
    )

    A value whose type travels with it.

    A message written by another language arrives with nothing but its bytes, so this is what loads builds when the caller has no schema to hand: the cross-language types, the containers, and the three time types.

    An integer of any width reads as Int or UInt at sixty-four bits, and a float of any width as Float, because a value read without a schema has no width to be. The typed codecs keep the width they were given.
    impl Show for Value
    impl Codec for Value

    Value::equal

    fn Value::equal(Value, Value) -> Bool

    Value::not_equal

    fn Value::not_equal(x : Value, y : Value) -> Bool

    Value::output

    fn Value::output(self : Value, logger : &Logger) -> Unit

    Value::read_data

    fn Value::read_data(r : Reader, ty : TypeId) -> Value raise

    Value::to_repr

    Value::to_string

    fn Value::to_string(self : Value) -> String

    Value::ty

    fn Value::ty(self : Value) -> TypeId

    The type ID this value goes out as.

    Value::write_data

    fn Value::write_data(self : Value, w : Writer) -> Unit raise

    Wins

    pub(all) enum Wins {
    Base
    Extra
    } derive(Eq,
    Debug
    )

    Which of the two ways of giving a configuration wins when both are given.

    Extra — the flat arguments — is the default, because a caller who took the trouble to write track_ref=true on one call means it on that call.

    Wins::equal

    fn Wins::equal(Wins, Wins) -> Bool

    Wins::not_equal

    fn Wins::not_equal(x : Wins, y : Wins) -> Bool

    Wins::to_repr

    Writer

    pub(all) struct Writer {
    buf :
    Buffer

    config : Config
    refs : Map[Int, Int]
    next_ref : Int
    fresh : Int
    }

    Where a value goes: a buffer, the configuration it is written under, and the table of what has already been sent.

    Everything the specification fixes is a method here, so that a type this package has never heard of can be written with the same primitives its own types are written with. Nothing here is private to a codec.

    Writer::buffer

    The buffer being written into, so that a caller who already has one does not have to copy out of it.

    Writer::bytes

    fn Writer::bytes(self : Writer) -> Bytes

    The bytes written so far.

    Writer::length

    fn Writer::length(self : Writer) -> Int

    How many bytes have been written.

    Writer::new

    A writer with nothing in it.

    Writer::write_binary

    fn Writer::write_binary(self : Writer, v : Bytes) -> Unit

    Bytes with their length in front: the BINARY type's body.

    Writer::write_bool

    fn Writer::write_bool(self : Writer, v : Bool) -> Unit

    A boolean: one byte, 1 or 0.

    Writer::write_byte

    fn Writer::write_byte(self : Writer, v : Int) -> Unit

    One byte.

    Writer::write_bytes

    fn Writer::write_bytes(self : Writer, v : Bytes) -> Unit

    Bytes as they are, with no length: for the caller who has already written one.

    Writer::write_f32

    fn Writer::write_f32(self : Writer, v : Float) -> Unit

    A single-precision float: IEEE 754 binary32, little endian, NaN preserved.

    Writer::write_f64

    fn Writer::write_f64(self : Writer, v : Double) -> Unit

    A double-precision float: IEEE 754 binary64, little endian, NaN preserved.

    Writer::write_fresh

    fn Writer::write_fresh(self : Writer) -> Unit

    The flag for a value that is tracked but was never named: it takes the next reference id, under a key of this writer's own below every caller's, so that the ids of the references that do name something still line up with a peer's.

    Writer::write_head

    fn Writer::write_head(self : Writer) -> Unit

    The header byte: the cross-language flag in bit 0.

    The specification leaves bit 1 for out-of-band data and asks that it be 0 when there is none, which is always here: this package writes no out-of-band buffer.

    Writer::write_i16

    fn Writer::write_i16(self : Writer, v : Int) -> Unit

    Sixteen bits, signed, little endian.

    Writer::write_i32

    fn Writer::write_i32(self : Writer, v : Int) -> Unit

    Thirty-two bits, signed, little endian.

    Writer::write_i64

    fn Writer::write_i64(self : Writer, v : Int64) -> Unit

    Sixty-four bits, signed, little endian.

    Writer::write_i8

    fn Writer::write_i8(self : Writer, v : Int) -> Unit

    Eight bits, signed or unsigned: the byte is the same either way.

    Writer::write_null_flag

    fn Writer::write_null_flag(self : Writer, is_null : Bool) -> Unit

    The null flag: 0xFD for a value that is not there, 0xFF for one that is.

    Writer::write_ref

    fn Writer::write_ref(self : Writer, key : Int) -> Bool

    The flag for a reference to a value that has already been written, and whether it was: a key that names nothing yet writes nothing and answers false, because a reference to a value that is not on the wire cannot be written as one.

    Writer::write_string

    fn Writer::write_string(self : Writer, v : String) -> Unit

    A string: the header, then the bytes.

    The encoding chosen is UTF-8, which the specification allows every language to read; LATIN1 and UTF-16 are read but not written, because a MoonBit string is Unicode and narrowing it would lose what is not in the other one.

    Writer::write_tagged_i64

    fn Writer::write_tagged_i64(self : Writer, v : Int64) -> Unit

    A sixty-four-bit signed integer that is usually small: four bytes carrying v << 1, or 1 and then eight bytes when it does not fit.

    The low bit of the first four bytes is the tag, which is why the range that fits is [-2^30, 2^30-1] and not the whole of the thirty-two bits.

    Writer::write_tagged_u64

    fn Writer::write_tagged_u64(self : Writer, v : UInt64) -> Unit

    A sixty-four-bit unsigned integer that is usually small: four bytes carrying v << 1, or 1 and then eight bytes when it does not fit.

    Writer::write_tracked

    fn Writer::write_tracked(self : Writer, key : Int) -> Bool

    The envelope of a value that may be written more than once, and whether the body still has to be written.

    key is the caller's name for the value. The first time it is met, 0x00 goes out and the answer is true; after that, 0xFE and the reference id go out and the answer is false, because the value is already on the wire.

    Writer::write_type_id

    fn Writer::write_type_id(self : Writer, t : TypeId) -> Unit

    A type ID byte.

    Writer::write_u16

    fn Writer::write_u16(self : Writer, v : Int) -> Unit

    Sixteen bits, unsigned, little endian.

    Writer::write_u32

    fn Writer::write_u32(self : Writer, v : UInt) -> Unit

    Thirty-two bits, unsigned, little endian.

    Writer::write_u64

    fn Writer::write_u64(self : Writer, v : UInt64) -> Unit

    Sixty-four bits, unsigned, little endian.

    Writer::write_u8

    fn Writer::write_u8(self : Writer, v : Int) -> Unit

    Eight bits, unsigned.

    Writer::write_user_type

    fn Writer::write_user_type(self : Writer, t : TypeId, user : Int) -> Unit

    A type ID byte and the user type ID that follows it: how a struct, an enum or an extension names its own type in same-schema mode.

    Writer::write_var_i32

    fn Writer::write_var_i32(self : Writer, v : Int) -> Unit

    The same for thirty-two bits.

    Writer::write_var_i64

    fn Writer::write_var_i64(self : Writer, v : Int64) -> Unit

    A signed integer folded onto an unsigned one by zigzag, then written as a varint: a small negative stays one byte.

    Writer::write_var_u32

    fn Writer::write_var_u32(self : Writer, v : UInt) -> Unit

    A base-128 varint of thirty-two bits.

    Writer::write_var_u36

    fn Writer::write_var_u36(self : Writer, v : UInt64) -> Unit

    The string header: size << 2 | encoding, written as one byte while it fits in seven bits and as a varint when it does not.

    Sizes under thirty-two bytes are most strings, so the single byte is the case worth having.

    Writer::write_var_u64

    fn Writer::write_var_u64(self : Writer, v : UInt64) -> Unit

    A base-128 varint, least significant group first, the high bit marking that more follow.

    compare_field

    fn compare_field(a : FieldMeta, b : FieldMeta) -> Int

    Which of two fields comes first, by the rules the specification lays down so that every language writes the same order.

    Three groups — primitives that cannot be absent, primitives that can, and everything else — and inside them the keys the specification names: for the primitives, fixed width before compressed, then width descending, then type ID, then identifier; for the rest, the identifier alone.

    config

    let config : Config

    The preset every entry point takes when it is not given one.

    Named config so that an argument reads config=config and an update reads { ..config, track_ref: true }.

    date_of_days

    fn date_of_days(days : Int64) ->
    Date

    The day count of a date, as the wire carries it: days since 1970-01-01.

    days_of_date

    fn days_of_date(date :
    Date
    ) -> Int64

    The days since 1970-01-01 for a date.

    dump

    fn[T : Codec] dump(value : T, buffer :
    Buffer
    , config? : Config, xlang? : Bool, track_ref? : Bool, check_string? : Bool, depth? : Int, fields? : Int, graph? : Int, wins? : Wins, clash? : OnClash[Config]) -> Unit raise

    The same, into a buffer the caller keeps: for a stream of messages written one after another, or for a buffer that already holds a header of its own.

    The configuration is the record, then this call's flat mirrors: the library preset is beaten by the record, and the record by the flat mirrors, unless wins says otherwise. A flat mirror that contradicts the record beside it is what clash arbitrates: taken silently by wins, refused, or handed to the caller's function as (the record, the two as one).

    dumps

    fn[T : Codec] dumps(value : T, config? : Config, xlang? : Bool, track_ref? : Bool, check_string? : Bool, depth? : Int, fields? : Int, graph? : Int, wins? : Wins, clash? : OnClash[Config]) -> Bytes raise

    One value as one message: the head, the type ID, and the value.

    The configuration is the record, then this call's flat mirrors: the library preset is beaten by the record, and the record by the flat mirrors, unless wins says otherwise. A flat mirror that contradicts the record beside it is what clash arbitrates: taken silently by wins, refused, or handed to the caller's function as (the record, the two as one).

    load

    fn[T : Codec] load(reader : Reader) -> T raise

    One value read out of a reader the caller owns, which is how several messages written one after another are read back: the reader keeps the position, and its configuration is the reader's.

    loads

    fn[T : Codec] loads(bytes : Bytes, schemas? : Array[StructMeta], config? : Config, xlang? : Bool, track_ref? : Bool, check_string? : Bool, depth? : Int, fields? : Int, graph? : Int, wins? : Wins, clash? : OnClash[Config]) -> T raise

    Bytes as a value: the head is checked, the value is read, and the bytes left over are refused, because a message with two values in it is two messages.

    schemas is what the reader knows about structs: a struct arrives as a user type ID and nothing else. The configuration is the record, then this call's flat mirrors: the library preset is beaten by the record, and the record by the flat mirrors, unless wins says otherwise. A flat mirror that contradicts the record beside it is what clash arbitrates: taken silently by wins, refused, or handed to the caller's function as (the record, the two as one).

    moment_of_parts

    fn moment_of_parts(seconds : Int64, nanos : Int) ->
    Moment

    The moment seconds after the epoch, plus nanos nanoseconds.

    parts_of_moment

    fn parts_of_moment(moment :
    Moment
    ) -> (Int64, Int)

    The whole seconds and the sub-second nanoseconds of a moment.

    parts_of_span

    fn parts_of_span(span :
    Span
    ) -> (Int64, Int)

    The whole seconds and the sub-second nanoseconds of a span. The nanoseconds are normalised into [0, 1e9), borrowing a second when the span is negative.

    read_body

    fn read_body(r : Reader, ty : TypeId) -> Value raise

    Read the body of a value whose type is already known.

    read_message

    fn[T : Codec] read_message(r : Reader) -> T raise

    Read one complete message: the root's flag first, then the type ID.

    A value that is not there comes back as this type read under NONE, which every type answers in its own way: Value answers Null, and a type that cannot be absent refuses. A reference where the root belongs is a message that begins by pointing at itself, and is refused.

    read_value

    fn read_value(r : Reader) -> Value raise

    Read a value: the type meta says what to build, and the body fills it.

    resolve

    fn resolve(config : Config?, xlang : Bool?, track_ref : Bool?, check_string : Bool?, depth : Int?, fields : Int?, graph : Int?, wins : Wins?, clash : OnClash[Config]?) -> Config raise

    The configuration one call runs under.

    The library preset is beaten by the record, and the record by this call's flat mirrors — unless wins says otherwise. A flat mirror that contradicts the record beside it is what clash arbitrates: taken silently by wins, refused, or handed to the caller's function as (the record, the two as one).

    root_key

    let root_key : Int

    The key the root value is tracked under, when tracking is on.

    The root is reference zero, so that the ids here line up with a peer's: a shared value is given a key of its own, and this one is kept out of the way of them.

    shared

    fn[T] shared(key : Int, value : T) -> Shared[T]

    Mark a value as one that may be shared.

    span_of_parts

    fn span_of_parts(seconds : Int64, nanos : Int) ->
    Span

    The span from seconds and nanos.

    write_body

    fn write_body(w : Writer, v : Value) -> Unit raise

    Write the body of a value whose type meta is already on the wire: what a list element and a struct field are, once the header or the schema has said what they are.

    write_message

    fn[T : Codec] write_message(w : Writer, value : T) -> Unit raise

    Write a value as a complete message: the root's flag, the type ID, then everything after it.

    The flag belongs to the message and not to the value — the specification's write_ref_or_null runs here as it runs before a nullable field — so it is written where the message is, and not in write_data, which belongs to the value. A message with no value in it is the flag alone.

    write_value

    fn write_value(w : Writer, v : Value) -> Unit raise

    Write a value: the type meta that says what it is, then the body.

    write_value_data

    fn write_value_data(w : Writer, v : Value) -> Unit raise

    Everything a value writes after its type ID: the user type ID for the kinds that carry one, then the body.

    Powered by MoonBit

    Site sourceReport issueModulesSkillsBuild queueStatistics

    © 2026 mooncakes.io