Sign in

    moonvalve

    Ordered Valve KeyValues tooling, source editing, streaming and Steam metadata in MoonBit

    valve
    keyvalues
    vdf
    steam
    parser
    Download zip
    Version
    0.4.0
    License
    MIT
    Last updated
    2 hours ago
    Downloads
    5

    No dependencies

    #MoonValve

    MoonBit 原生 Valve KeyValues 工具库:KV1、文本 KV3、Steam 清单与编辑器服务。

    MoonValve 让 MoonBit 程序可以读取、查询和写出 VDF/ACF 文本,同时保留键的顺序与重复项。核心库没有第三方依赖,也不调用 Python、Valve SDK 或外部解析器。Steam 数据模型和命令行工具建立在同一份解析实现上。

    当前版本为开发中的 0.4.0,提供库、命令行、本地可视化工作台和跨平台下载包。尚未发布到 Mooncakes。

    新增查询表达式与快照批量修改、跨对象移动复制、批量编辑优化、文本 KV3、多文件引用重构及标准输入输出 LSP。详见 进阶功能与边界,VS Code 扩展 可通过 VSIX 安装。

    #为什么需要它

    Steam 和 Source 生态使用 KeyValues 保存配置和资源信息。这种格式有嵌套对象,也允许重复键;把它直接转换成普通字典容易丢失信息。MoonValve 使用有序条目数组表示对象,方便游戏工具、资源处理程序和桌面应用复用。

    #已实现

    • KV1 文本解析:嵌套对象、带引号和不带引号的字符串、空值、空对象、// 注释、Unicode 和 UTF-8 BOM。
    • 有序条目模型,重复键不覆盖;支持首项查询、全部同名项查询和逐层路径查询。
    • 规范化写出与结构往返,提供保留重复键的 JSON 双向转换。
    • 按同名键出现次序定位的插入、替换、删除与重命名,以及保留原文注释和排版的局部编辑。
    • 显式平台条件求值与调用方提供加载回调的 #include/#base 解析,含循环检测和全局资源预算。
    • 保留类型与浮点原始位的标准二进制 KV1 编解码。
    • 可选择的转义模式,以及输入长度、字符串长度、总条目数和嵌套深度限制。
    • 包含错误码、行列号和 Unicode 字符偏移的解析诊断。
    • Steam appmanifest_*.acf 和新旧 libraryfolders.vdf 数据模型。
    • JavaScript 与原生命令行;Steam 清单筛选、排序、精确容量汇总、重复及缺失检查与 CSV 导出。
    • 全重复项路径查询、树遍历、严格类型读取与有序编辑事务。
    • 保留条件、引用与字面路径的源编辑,插入沿用相邻缩进、换行和键的引号方式。
    • shortcuts.vdf 增删改、appinfo/packageinfo 多版本索引、二进制外部字符串表。
    • 增量 UTF-8 解析与事件回调,可跳过对象交付而继续验证语法,附百万条目示例。
    • 命令行编辑预览、带备份的原子保存、受选定目录约束的引用展开和结构化错误。
    • 本机可视化工作台,提供文本/二进制树形浏览、编辑预览、撤销和下载副本。
    • 分块文件命令,支持 JSON Lines 原文事件、原始标量路径查询、CSV 和无条件文本的流式规范化;输出处理慢速接收端的背压。
    • 快捷方式专用表单、批量修改预览;缓存按 ID 搜索、分页和记录按需解码。
    • 多文件工程切换、草稿快照、引用关系及展开字段到原文件行列的来源导航。

    完整格式边界见 兼容性说明,库接口见 API 导览,工具用法见 命令行、本地工作台 和 发行包。

    #环境与获取代码

    • 已验证工具链:moonc v0.10.14+7d59c7ec9,配套 moonbitlang/core 同版本。
    • JavaScript CLI 需要 Node.js 22 或更高版本;原生 CLI 构建需要 C 编译器,运行时无需 Node。

    git clone https://github.com/Charles67676767/MoonValve.git cd MoonValve moon version --all moon check moon test moon run examples/demo moon run examples/advanced

    示例输出以 480: Moon Harbor (Moon Harbor) 开头,随后输出规范化的 KV1。示例游戏、路径和清单均为合成数据。

    #库用法

    在本仓库中的包配置里导入:

    import {
    "Charles67676767/moonvalve" @valve,
    }

    let doc = @valve.parse("root { tag one tag two name \"Moon Harbor\" }")
    let name = doc.lookup(["root", "name"])
    let normalized = @valve.stringify(doc)
    let same = @valve.parse(normalized) == doc

    parse 和 stringify 会抛出结构化错误,需要由调用方处理。可直接运行的完整错误处理示例在 examples/demo。Mooncakes 发布前,也可通过 独立模块示例 的 moon.work 引入源码依赖。

    对象由 Array[Entry] 表示,Value 为 Text(String) 或 Object(Array[Entry])。查询默认区分大小写,遇到重复键时返回第一项;Document::get_all 返回所有顶层同名项。Steam 数据模型单独采用 ASCII 不区分大小写的字段查询。

    #命令行

    以下命令在仓库根目录执行,默认向标准输出写结果;只有显式 --in-place 才保存到文件并创建备份。

    moon run cmd/main --target js -- help moon run cmd/main --target js -- parse fixtures/duplicates.vdf moon run cmd/main --target js -- from-json data.json moon run cmd/main --target js -- format fixtures/duplicates.vdf moon run cmd/main --target js -- query fixtures/appmanifest_480.acf AppState name moon run cmd/main --target js -- app fixtures/appmanifest_480.acf moon run cmd/main --target js -- libraries fixtures/libraryfolders.vdf

    app 的示例结果:

    {"app_id":"480","name":"Moon Harbor","install_dir":"Moon Harbor","size_on_disk":"1048576","build_id":"20261023"}

    游戏 ID、构建 ID 和输出的字节数使用字符串表示,避免 JSON 消费端的浮点精度问题。

    读取本机实际清单时,将命令中的路径换成自己的 libraryfolders.vdf:

    moon run cmd/main --target js -- inventory "/path/to/Steam/steamapps/libraryfolders.vdf" moon run cmd/main --target js -- inventory "/path/to/libraryfolders.vdf" --filter portal --sort size --csv

    inventory 扫描该文件列出的库中的 steamapps/appmanifest_*.acf,返回 games、errors 和筛选结果的 summary。相对库路径以输入文件的目录为基准。它读取清单记录,不计算实际文件夹大小,也不验证游戏下载是否完整。库路径和清单名可能出现在本机输出中。

    除 inventory、文件引用展开及原地保存外,可以把文件名换成 - 从标准输入读取。文本按 UTF-8 严格解码,普通输入上限 16 MiB,缓存命令上限 512 MiB;流式命令默认 64 MiB,可显式设定预算,见 流式文件处理。退出码为 0(成功)、1(inventory 部分失败)或 2(参数、输入或解析错误)。

    也可以先构建,再直接使用 Node.js:

    moon build --target js --release node _build/js/release/build/cmd/main/main.js help

    原生程序:

    moon build --target native --release moon run cmd/main --target native --release -- help

    可直接分发 _build/native/release/build/cmd/main/ 下生成的 main.exe 或 main,以工具链实际产物为准;.exe 后缀也可能出现在 Linux 产物上。两种 CLI 的命令、输入格式与退出码一致。

    #检查与测试

    moon fmt --check moon check --target js moon test --target all --deny-warn moon build --target js --release node scripts/test-cli.cjs moon build --target native --release node scripts/test-cli.cjs --native python -m pip install vdf==3.4 python scripts/check-reference.py node scripts/benchmark.cjs --native moon info

    核心测试覆盖文本与二进制、编辑事务、格式保留、条件、引用、流解析、Steam 快捷方式、缓存及资源边界。CLI 使用临时目录验收;Python vdf 与 .NET ValveKeyValue 是独立测试参考,不参与产品运行。另有带失败样本缩减的确定性模糊测试。持续集成执行四后端验证、Windows/Linux 命令行、编辑器核心、独立参考和发行包校验。复现细节见 验证说明。

    #目录

    model.mbt 有序数据模型与查询 parser.mbt KV1 文本解析及诊断 writer.mbt 规范化文本写出 steam.mbt Steam 清单模型 inventory.mbt 筛选、排序、统计、检查与 CSV edit.mbt 重复键感知的文档编辑 source_edit.mbt 保留原文的局部编辑 json_codec.mbt 有序 JSON 导入 conditions.mbt 平台条件表达式 resolve.mbt 文件引用解析 project.mbt 引用关系与原文来源 stream_writer.mbt 有界事件写出 binary.mbt 标准二进制 KV1 cmd/main/ JS/C 宿主适配与 MoonBit CLI examples/demo/ 跨后端最小使用示例 examples/advanced/ 编辑、条件、引用与二进制示例 fixtures/ 合成示例数据 scripts/test-cli.cjs CLI 端到端测试 docs/ 兼容性、设计与路线图

    #后续方向

    当前重点是稳定已实现 API、扩充实际兼容性样例并准备首个 Mooncakes 发布版本。KV2、二进制 KV3、VBKV 和未知版本的 Steam 缓存不在当前范围内。详见 路线图。

    #许可证与参考

    采用 MIT 许可证。格式兼容性参考 ValvePython/vdf 和 ValveKeyValue,来源及分发边界见 NOTICE.md。

    MoonValve 是独立项目,与 Valve 或 Steam 没有隶属关系。

    AccessError

    pub suberror AccessError {
    InvalidAccess(Array[Selector], String)
    } derive(
    Debug
    )

    BinaryError

    pub suberror BinaryError {
    InvalidBinary(Int, String)
    } derive(
    Debug
    )

    ConditionError

    pub suberror ConditionError {
    InvalidCondition(String)
    } derive(
    Debug
    )

    EditError

    pub suberror EditError {
    InvalidEdit(String)
    } derive(
    Debug
    )

    JsonImportError

    pub suberror JsonImportError {
    InvalidJsonDocument(String)
    } derive(
    Debug
    )

    ParseError

    pub suberror ParseError {
    ParseError(Diagnostic)
    } derive(
    Debug
    )

    QueryError

    pub suberror QueryError {
    InvalidQuery(String)
    } derive(
    Debug
    )

    ResolveError

    pub suberror ResolveError {
    InvalidReference(String)
    } derive(
    Debug
    )

    SteamError

    pub suberror SteamError {
    InvalidManifest(String)
    } derive(
    Debug
    )

    WriteError

    pub suberror WriteError {
    InvalidDocument(String)
    } derive(
    Debug
    )

    BinaryEntry

    pub(all) struct BinaryEntry {
    key : String
    value : BinaryValue
    } derive(Eq,
    Debug
    )

    BinaryEntry::equal

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

    BinaryEntry::not_equal

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

    BinaryOptions

    pub(all) struct BinaryOptions {
    max_depth : Int
    max_entries : Int
    max_bytes : Int
    max_string_bytes : Int
    } derive(Eq,
    Debug
    )

    BinaryOptions::default

    fn BinaryOptions::default() -> BinaryOptions

    BinaryOptions::equal

    BinaryOptions::not_equal

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

    BinaryValue

    pub(all) enum BinaryValue {
    ObjectValue(Array[BinaryEntry])
    StringValue(String)
    Int32Value(Int)
    Float32Bits(UInt)
    Pointer32(UInt)
    WideString(String)
    Color32(UInt)
    UInt64Value(UInt64)
    Int64Value(Int64)
    } derive(Eq,
    Debug
    )

    Typed standard binary KV1. Floating-point payloads retain their exact bits, including NaN payloads and negative zero. Pointer values are data, never dereferenced.

    BinaryValue::equal

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

    BinaryValue::not_equal

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

    CacheFormat

    pub(all) enum CacheFormat {
    AppInfo
    PackageInfo
    } derive(Eq,
    Debug
    )

    CacheFormat::equal

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

    CacheFormat::not_equal

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

    CachePage

    pub(all) struct CachePage {
    records : Array[CacheRecord]
    total : Int
    offset : Int
    }

    CacheRecord

    pub(all) struct CacheRecord {
    id : UInt
    change_number : UInt
    token : UInt64
    sha1 : Bytes
    binary_sha1 : Bytes?
    info_state : UInt?
    last_updated : UInt?
    offset : Int
    length : Int
    } derive(Eq,
    Debug
    )

    CacheRecord::equal

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

    CacheRecord::not_equal

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

    Change

    pub(all) struct Change {
    path : Array[Selector]
    operation : Edit
    } derive(
    Debug
    )

    Change::to_repr

    Diagnostic

    pub(all) struct Diagnostic {
    code : String
    message : String
    offset : Int
    line : Int
    column : Int
    } derive(Eq,
    Debug
    )

    Offset is a zero-based Unicode scalar index; line and column are one-based.

    Diagnostic::equal

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

    Diagnostic::not_equal

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

    Document

    pub(all) struct Document {
    entries : Array[Entry]
    } derive(Eq,
    Debug
    )

    A document can contain zero, one, or several top-level entries.

    Document::apply

    fn Document::apply(self : Document, changes : Array[Change], options? : ParseOptions) -> Document raise

    Sequential paths address the result of the preceding change. A failed batch never modifies the original document. Final output must satisfy text limits.

    Document::apply_snapshot

    fn Document::apply_snapshot(self : Document, changes : Array[Change]) -> Document raise

    Document::edit

    fn Document::edit(self : Document, path : Array[Selector], operation : Edit) -> Document raise EditError

    Return a changed document without modifying the input arrays. An empty path selects the root object and supports Insert or Replace(Object(...)).

    Document::equal

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

    Document::find

    fn Document::find(self : Document, key : String) -> Array[LocatedValue] raise

    Document::get

    fn Document::get(self : Document, key : String) -> Value?

    Look up the first exact, case-sensitive key match.

    Document::get_all

    fn Document::get_all(self : Document, key : String) -> Array[Value]

    Look up every exact key match in source order.

    Document::lookup

    fn Document::lookup(self : Document, path : Array[String]) -> Value?

    Walk object keys. At each level, the first duplicate wins. An empty path addresses the document's top-level object.

    Document::lookup_all

    fn Document::lookup_all(self : Document, path : Array[String]) -> Array[Value] raise AccessError

    Follow every matching duplicate at every level, retaining document order.

    Document::lookup_at

    fn Document::lookup_at(self : Document, path : Array[Selector]) -> Value raise AccessError

    Exact, duplicate-aware lookup. Errors retain the complete requested path.

    Document::not_equal

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

    Document::query_plan

    fn Document::query_plan(self : Document, expression : String, operation : QueryEdit) -> Array[Change] raise

    Plans address one original snapshot. Use apply_snapshot, not sequential apply.

    Document::read_bool

    fn Document::read_bool(self : Document, path : Array[Selector]) -> Bool raise AccessError

    Document::read_double

    fn Document::read_double(self : Document, path : Array[Selector]) -> Double raise AccessError

    Document::read_int64

    fn Document::read_int64(self : Document, path : Array[Selector]) -> Int64 raise AccessError

    Document::read_text

    fn Document::read_text(self : Document, path : Array[Selector]) -> String raise AccessError

    Document::select

    fn Document::select(self : Document, expression : String) -> Array[LocatedValue] raise

    Document::to_json

    fn Document::to_json(self : Document) -> Json

    Lossless JSON representation: ordered arrays of {key,value} entries. An ordinary JSON object would silently lose duplicate keys.

    Document::to_repr

    Document::transfer

    fn Document::transfer(self : Document, from : Array[Selector], to : Array[Selector], index : Int, copy? : Bool) -> Document raise

    Index is a boundary in the original destination object, before removal. Moving into oneself/descendants is rejected. Copying produces a finite snapshot.

    Document::walk

    fn Document::walk(self : Document, visitor : (Array[Selector], Value) -> Bool raise) -> Unit raise

    Preorder visitor; returning false skips this object's descendants. Paths are independent snapshots. The root itself is not emitted. Exceptions propagate.

    Edit

    pub(all) enum Edit {
    Replace(Value)
    Remove
    Rename(String)
    Insert(Int, Entry)
    } derive(
    Debug
    )

    Edit::to_repr

    Entry

    pub(all) struct Entry {
    key : String
    value : Value
    } derive(Eq,
    Debug
    )

    One key-value pair, in source order.

    Entry::equal

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

    Entry::not_equal

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

    Entry::to_repr

    InventoryIssue

    pub(all) struct InventoryIssue {
    code : String
    library : String
    manifest : String?
    message : String
    } derive(Eq,
    Debug
    )

    InventoryIssue::equal

    InventoryIssue::not_equal

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

    InventoryRecord

    pub(all) struct InventoryRecord {
    library : String
    manifest : String
    app : SteamApp
    } derive(Eq,
    Debug
    )

    InventoryRecord::equal

    InventoryRecord::not_equal

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

    InventoryTotal

    pub(all) struct InventoryTotal {
    known_size_bytes : Int64
    unknown_size_count : Int
    } derive(Eq,
    Debug
    )

    InventoryTotal::equal

    InventoryTotal::not_equal

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

    KV3Document

    pub(all) struct KV3Document {
    header : KV3Header?
    root : KV3Value
    } derive(Eq,
    Debug
    )

    KV3Document::equal

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

    KV3Document::not_equal

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

    KV3Entry

    pub(all) struct KV3Entry {
    key : String
    value : KV3Value
    } derive(Eq,
    Debug
    )

    KV3Entry::equal

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

    KV3Entry::not_equal

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

    KV3Entry::to_repr

    KV3Header

    pub(all) struct KV3Header {
    format_name : String
    format_id : String
    } derive(Eq,
    Debug
    )

    KV3Header::equal

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

    KV3Header::not_equal

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

    KV3Value

    pub(all) enum KV3Value {
    KV3Null
    KV3Bool(Bool)
    KV3Integer(String)
    KV3Float(String)
    KV3String(String)
    KV3Blob(Bytes)
    KV3Array(Array[KV3Value])
    KV3Object(Array[KV3Entry])
    KV3Flag(String, KV3Value)
    } derive(Eq,
    Debug
    )

    KV3Value::equal

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

    KV3Value::not_equal

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

    KV3Value::to_repr

    LocatedValue

    pub(all) struct LocatedValue {
    path : Array[Selector]
    value : Value
    } derive(Eq,
    Debug
    )

    LocatedValue::equal

    LocatedValue::not_equal

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

    LspFramer

    type LspFramer

    LspFramer::feed

    fn LspFramer::feed(self : LspFramer, chunk : Bytes) -> Array[LspPacket] raise

    LspFramer::finish

    fn LspFramer::finish(self : LspFramer) -> Unit raise

    LspPacket

    pub(all) enum LspPacket {
    LspRequest(Json)
    LspInvalidJson
    }

    LspServer

    type LspServer

    Transport-independent LSP 3.17 engine. Files enter only through client messages.

    LspServer::exit_code

    fn LspServer::exit_code(self : LspServer) -> Int

    LspServer::handle

    fn LspServer::handle(self : LspServer, request : Json) -> Array[Json]

    LspServer::should_exit

    fn LspServer::should_exit(self : LspServer) -> Bool

    ParseOptions

    pub(all) struct ParseOptions {
    escape_sequences : Bool
    max_depth : Int
    max_entries : Int
    max_input_chars : Int
    max_token_chars : Int
    conditions : Array[String]?
    } derive(Eq,
    Debug
    )

    Parsing limits apply to the whole document; depth counts nested objects.

    ParseOptions::default

    fn ParseOptions::default() -> ParseOptions

    Escapes are enabled for canonical output and Steam's escaped Windows paths.

    ParseOptions::equal

    ParseOptions::not_equal

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

    Query

    type Query

    Compiled, duplicate-aware path expression; not a JSONPath implementation.

    Query::select

    fn Query::select(self : Query, document : Document, max_matches? : Int, max_visited? : Int) -> Array[LocatedValue] raise

    QueryEdit

    pub(all) enum QueryEdit {
    SetText(String)
    ReplacePrefix(String, String)
    RenameKey(String)
    RemoveMatches
    } derive(
    Debug
    )

    ReferenceEdge

    pub(all) struct ReferenceEdge {
    source : String
    target : String
    kind : String
    location : SourceLocation
    } derive(Eq,
    Debug
    )

    ReferenceEdge::equal

    ReferenceEdge::not_equal

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

    ResolvedOrigin

    pub(all) struct ResolvedOrigin {
    path : Array[Selector]
    location : SourceLocation
    } derive(Eq,
    Debug
    )

    ResolvedOrigin::equal

    ResolvedOrigin::not_equal

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

    ResolvedProject

    pub(all) struct ResolvedProject {
    document : Document
    origins : Array[ResolvedOrigin]
    references : Array[ReferenceEdge]
    } derive(Eq,
    Debug
    )

    ResolvedProject::equal

    ResolvedProject::not_equal

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

    ResolvedProject::to_json

    fn ResolvedProject::to_json(self : ResolvedProject) -> Json

    Selector

    pub(all) struct Selector {
    key : String
    occurrence : Int
    } derive(Eq,
    Debug
    )

    Select the zero-based occurrence of a key, preserving duplicate siblings.

    Selector::equal

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

    Selector::not_equal

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

    Selector::to_repr

    ShortcutFile

    type ShortcutFile

    ShortcutFile::add

    fn ShortcutFile::add(self : ShortcutFile, name : String, exe : String, start_dir? : String, launch_options? : String, tags? : Array[String]) -> ShortcutFile raise SteamError

    ShortcutFile::remove

    fn ShortcutFile::remove(self : ShortcutFile, index : String) -> ShortcutFile raise SteamError

    ShortcutFile::set

    fn ShortcutFile::set(self : ShortcutFile, shortcut : SteamShortcut) -> ShortcutFile raise SteamError

    Replace modeled fields while retaining unknown fields and all other entries. The supplied app_id is kept exactly; callers can deliberately preserve identity.

    ShortcutFile::shortcuts

    fn ShortcutFile::shortcuts(self : ShortcutFile) -> Array[SteamShortcut] raise SteamError

    ShortcutFile::to_binary

    fn ShortcutFile::to_binary(self : ShortcutFile) -> Array[BinaryEntry]

    SourceDocument

    type SourceDocument

    A parsed source with scalar-index spans. Edit returns a new, reparsed source; all characters outside the selected replacement range remain unchanged.

    SourceDocument::apply

    fn SourceDocument::apply(self : SourceDocument, changes : Array[Change]) -> SourceDocument raise

    Apply a batch to independent source snapshots; nothing is published on failure.

    SourceDocument::apply_snapshot

    fn SourceDocument::apply_snapshot(self : SourceDocument, changes : Array[Change]) -> SourceDocument raise

    All targets address the same original source; compile patches then parse once.

    SourceDocument::document

    fn SourceDocument::document(self : SourceDocument) -> Document

    SourceDocument::edit

    fn SourceDocument::edit(self : SourceDocument, path : Array[Selector], operation : Edit) -> SourceDocument raise

    Supports the same occurrence-aware edits as Document::edit. Comments outside replaced values remain intact; removing an entry leaves surrounding comments. Newly inserted or replaced structures use canonical escaped KV1 formatting.

    SourceDocument::edit_query

    fn SourceDocument::edit_query(self : SourceDocument, expression : String, operation : QueryEdit) -> SourceDocument raise

    SourceDocument::evaluate

    fn SourceDocument::evaluate(self : SourceDocument, symbols : Array[String]) -> Document raise ParseError

    Evaluate a source without changing its raw view. Root file directives still require load_resolved with an explicitly supplied loader.

    SourceDocument::locations

    fn SourceDocument::locations(self : SourceDocument, source : String) -> Array[ResolvedOrigin]

    Raw source locations include branches and directives without evaluation.

    SourceDocument::text

    fn SourceDocument::text(self : SourceDocument) -> String

    SourceDocument::transfer

    fn SourceDocument::transfer(self : SourceDocument, from : Array[Selector], to : Array[Selector], index : Int, copy? : Bool) -> SourceDocument raise

    SourceDocument::transfer_json

    fn SourceDocument::transfer_json(self : SourceDocument, json : Json) -> SourceDocument raise

    SourceLocation

    pub(all) struct SourceLocation {
    source : String
    offset : Int
    line : Int
    column : Int
    } derive(Eq,
    Debug
    )

    SourceLocation::equal

    SourceLocation::not_equal

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

    SteamApp

    pub(all) struct SteamApp {
    app_id : String
    name : String
    install_dir : String
    size_on_disk : Int64?
    build_id : String?
    } derive(Eq,
    Debug
    )

    A validated Steam app manifest. Decimal identifiers stay as strings.

    SteamApp::equal

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

    SteamApp::not_equal

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

    SteamApp::to_repr

    SteamCache

    type SteamCache

    An index owns a snapshot of the input. AppInfo payloads are decoded only when requested. PackageInfo has no record sizes, so indexing validates each tree.

    SteamCache::format

    fn SteamCache::format(self : SteamCache) -> CacheFormat

    SteamCache::page

    fn SteamCache::page(self : SteamCache, query? : String, offset? : Int, limit? : Int) -> CachePage raise BinaryError

    Metadata-only pagination never decodes payloads. Query matches decimal IDs.

    SteamCache::read

    fn SteamCache::read(self : SteamCache, id : UInt) -> Array[BinaryEntry]? raise BinaryError

    SteamCache::records

    fn SteamCache::records(self : SteamCache) -> Array[CacheRecord]

    SteamCache::universe

    fn SteamCache::universe(self : SteamCache) -> UInt

    SteamCache::version

    fn SteamCache::version(self : SteamCache) -> Int

    SteamLibrary

    pub(all) struct SteamLibrary {
    index : String
    path : String
    label : String?
    app_ids : Array[String]
    } derive(Eq,
    Debug
    )

    SteamLibrary::equal

    SteamLibrary::not_equal

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

    SteamShortcut

    pub(all) struct SteamShortcut {
    index : String
    app_id : UInt
    name : String
    exe : String
    start_dir : String
    launch_options : String
    tags : Array[String]
    } derive(Eq,
    Debug
    )

    SteamShortcut::equal

    SteamShortcut::not_equal

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

    SteamShortcut::url

    fn SteamShortcut::url(self : SteamShortcut) -> String

    StreamEvent

    pub(all) enum StreamEvent {
    Pair(String, String)
    BeginObject(String)
    EndObject
    Condition(String, String)
    } derive(Eq,
    Debug
    )

    Raw SAX-style events. Conditions are validated and retained, never evaluated. Returning false for BeginObject suppresses descendants but still validates them and emits the matching EndObject. All other return values are ignored.

    StreamEvent::equal

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

    StreamEvent::not_equal

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

    StreamParser

    type StreamParser

    StreamParser::characters_seen

    fn StreamParser::characters_seen(self : StreamParser) -> Int

    StreamParser::entries_seen

    fn StreamParser::entries_seen(self : StreamParser) -> Int

    StreamParser::feed

    fn StreamParser::feed(self : StreamParser, chunk : String) -> Unit raise

    StreamParser::feed_bytes

    fn StreamParser::feed_bytes(self : StreamParser, chunk : Bytes) -> Unit raise

    Strict incremental UTF-8, retaining at most three incomplete bytes between calls.

    StreamParser::finish

    fn StreamParser::finish(self : StreamParser) -> Unit raise

    StreamWriter

    type StreamWriter

    A bounded event writer. Conditions and root directives require a document resolver and are rejected rather than silently changing their meaning.

    StreamWriter::finish

    fn StreamWriter::finish(self : StreamWriter) -> Unit raise WriteError

    StreamWriter::write

    fn StreamWriter::write(self : StreamWriter, event : StreamEvent) -> Unit raise

    Value

    pub(all) enum Value {
    Text(String)
    Object(Array[Entry])
    } derive(Eq,
    Debug
    )

    A KeyValues value. Objects are ordered lists, so duplicate keys are retained.

    Value::as_object

    fn Value::as_object(self : Value) -> Array[Entry]?

    Value::as_text

    fn Value::as_text(self : Value) -> String?

    Value::equal

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

    Value::not_equal

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

    Value::to_repr

    Workspace

    type Workspace

    Immutable in-memory KV1 workspace. Operations return a new snapshot; hosts decide how to present or save it. No multi-file filesystem atomicity is claimed.

    Workspace::add_file

    fn Workspace::add_file(self : Workspace, name : String, text : String) -> Workspace raise

    Workspace::apply

    fn Workspace::apply(self : Workspace, plan : Json) -> Workspace raise

    Workspace::copy_file

    fn Workspace::copy_file(self : Workspace, from : String, to : String) -> Workspace raise

    Workspace::files

    fn Workspace::files(self : Workspace) -> Map[String, String]

    Workspace::remove_file

    fn Workspace::remove_file(self : Workspace, name : String) -> Workspace raise

    Workspace::rename_file

    fn Workspace::rename_file(self : Workspace, from : String, to : String) -> Workspace raise

    Rename incoming references, including inactive branches, and rebase outgoing references when the moved file changes directory.

    Workspace::to_json

    fn Workspace::to_json(self : Workspace) -> Json

    binary_from_json

    fn binary_from_json(json : Json, options? : BinaryOptions) -> Array[BinaryEntry] raise BinaryError

    binary_to_json

    fn binary_to_json(entries : Array[BinaryEntry]) -> Json

    Ordered typed JSON; numeric payloads use decimal strings, including raw Float32 bits. No precision or NaN information is routed through JSON numbers.

    changes_from_json

    fn changes_from_json(json : Json) -> Array[Change] raise

    Portable JSON plan: [{path:[{key,occurrence}],op,value/key/index/entry}]. Values and entries use the same ordered representation as Document::to_json.

    check_inventory

    fn check_inventory(libraries : Array[SteamLibrary], records : Array[InventoryRecord]) -> Array[InventoryIssue]

    Check listed records against declared apps. Paths are compared exactly; filesystem canonicalization and case rules belong to the host adapter.

    compile_query

    fn compile_query(expression : String) -> Query raise QueryError

    decode_binary

    fn decode_binary(bytes : Bytes, options? : BinaryOptions, string_table? : Array[String]?) -> Array[BinaryEntry] raise BinaryError

    Decode standard KV1 with marker 8. Reject truncated streams, trailing bytes, invalid encodings and unknown types. Appinfo containers and VBKV are separate formats.

    document_from_json

    fn document_from_json(value : Json, options? : ParseOptions) -> Document raise JsonImportError

    Decode the ordered [{key,value}] representation emitted by to_json.

    edit_workspace_snapshot

    fn edit_workspace_snapshot(snapshot : Json, plan : Json, options? : ParseOptions) -> Json raise

    encode_binary

    fn encode_binary(entries : Array[BinaryEntry], options? : BinaryOptions, string_table? : Array[String]?) -> Bytes raise BinaryError

    Encode with the same limits as decoding. Order, duplicate keys and numeric types survive a decode/encode round trip without coercion through JSON numbers.

    evaluate_condition

    fn evaluate_condition(expression : String, symbols : Array[String]) -> Bool raise ConditionError

    Evaluate $NAME, !, &&, || and parentheses. Symbols are explicit and ASCII case-insensitive; unknown symbols are false. No host platform is inferred.

    generic_kv3_header

    fn generic_kv3_header() -> KV3Header

    inventory_csv

    fn inventory_csv(records : Array[InventoryRecord]) -> String

    RFC 4180 quoting with CRLF records. All fields are quoted, so commas, quotes and embedded line breaks remain unambiguous.

    inventory_total

    fn inventory_total(records : Array[InventoryRecord]) -> InventoryTotal raise SteamError

    kv3_from_json

    fn kv3_from_json(json : Json) -> KV3Document raise

    kv3_to_json

    fn kv3_to_json(document : KV3Document) -> Json raise

    Typed ordered JSON preserves integer precision, float spelling and flags.

    load_project

    fn load_project(source : String, loader : (String) -> String raise, options? : ParseOptions, max_files? : Int) -> ResolvedProject raise

    The resolver and provenance use one merge operation. Locations identify key starts in the original files, before filtering or formatting, in scalar units.

    load_resolved

    fn load_resolved(source : String, loader : (String) -> String raise, options? : ParseOptions, max_files? : Int) -> Document raise

    Load and resolve root-level includes and bases through an explicit loader. Virtual paths use / separators. Budgets count every loaded file, including repeated references, and entries parsed from filtered conditional branches.

    located_values_to_json

    fn located_values_to_json(values : Array[LocatedValue]) -> Json

    lsp_frame

    fn lsp_frame(message : Json) -> Bytes

    lsp_position

    fn lsp_position(text : String, offset : Int) -> Json

    new_lsp_framer

    fn new_lsp_framer() -> LspFramer

    new_lsp_server

    fn new_lsp_server() -> LspServer

    new_shortcut_file

    fn new_shortcut_file() -> ShortcutFile

    new_stream_parser

    fn new_stream_parser(visitor : (StreamEvent) -> Bool raise, options? : ParseOptions) -> StreamParser raise ParseError

    new_stream_writer

    fn new_stream_writer(sink : (String) -> Unit raise) -> StreamWriter

    new_workspace

    fn new_workspace(files : Map[String, String], options? : ParseOptions) -> Workspace raise

    open_steam_cache

    fn open_steam_cache(input : Bytes, max_bytes? : Int, max_records? : Int) -> SteamCache raise BinaryError

    Supported cache revisions: appinfo 39/40/41, packageinfo 39/40. Record bounds, table offsets, identifiers, sentinels and trailing data are checked strictly.

    parse

    fn parse(input : String, options? : ParseOptions) -> Document raise ParseError

    Parse the documented KV1 text subset; unsupported file-loading directives and platform conditionals are rejected rather than silently misinterpreted.

    parse_json_document

    fn parse_json_document(text : String, options? : ParseOptions) -> Document raise JsonImportError

    parse_kv3

    fn parse_kv3(input : String, options? : ParseOptions, allow_headerless? : Bool) -> KV3Document raise

    Text KV3 only. Limits reuse ParseOptions; KV1 condition/literal modes are invalid.

    parse_preserving

    fn parse_preserving(input : String, options? : ParseOptions) -> SourceDocument raise

    query_edit_from_json

    fn query_edit_from_json(json : Json) -> QueryEdit raise

    read_app_manifest

    fn read_app_manifest(document : Document) -> SteamApp raise SteamError

    Interpret an AppState object without touching the filesystem.

    read_library_folders

    fn read_library_folders(document : Document) -> Array[SteamLibrary] raise SteamError

    Read current object-style and legacy string-style libraryfolders entries.

    read_shortcut_file

    fn read_shortcut_file(entries : Array[BinaryEntry]) -> ShortcutFile raise SteamError

    select_inventory

    fn select_inventory(records : Array[InventoryRecord], filter? : String, sort? : String) -> Array[InventoryRecord] raise SteamError

    Filter by Unicode lowercase name or identifier; sort by name, id or descending size. Missing sizes sort last. Identifiers are compared as decimal integers.

    shortcut_app_id

    fn shortcut_app_id(name : String, exe : String) -> UInt

    Stable Steam shortcut identifier: CRC-32/ISO-HDLC(exe + name), high bit set.

    stringify

    fn stringify(document : Document) -> String raise WriteError

    Canonical escaped KV1 text. All keys and strings are quoted; order and duplicates are retained. Comments and original whitespace are not retained.

    stringify_kv3

    fn stringify_kv3(document : KV3Document) -> String raise

    version

    fn version() -> String

    Development version; distribution scripts verify it against moon.mod.

    workspace_from_json

    fn workspace_from_json(json : Json, options? : ParseOptions) -> Workspace raise