moonform

    MoonBit native headless form state library (architecture inspired by TanStack Form)

    form
    form-state
    validation
    headless
    lens
    Download zip
    Author
    Version
    0.1.1
    License
    MIT
    Last updated
    22 hours ago
    Downloads
    5

    Dependencies

    #moonform

    MoonBit 原生 headless 表单状态库。Architecture inspired by TanStack Form(MIT)。

    License: MIT CI mooncakes.io

    English | 中文

    #安装

    # 核心包 + 内置规则(零第三方依赖) moon add 2d5rrr333/moonform/core moon add 2d5rrr333/moonform/rules

    // moon.mod.json 中出现: "deps": { "2d5rrr333/moonform": "0.1.1" }

    // 按需在包的 moon.pkg 中引入: { "import": [ "2d5rrr333/moonform/core", "2d5rrr333/moonform/rules" ] }

    可选包:/schema(moonschema 适配,模块内已含 vendor 源码,无需额外依赖)、/react(需 moon add tiye/react)、/lens-gen(访问器生成器)。

    #是什么

    为什么:mooncakes.io 已有渲染层(tiye/react 等)与校验层(moonschema 等),但表单状态编排层完全空缺——字段状态机、校验时序(防抖/竞态)、数组字段操作、提交生命周期,是每个 Web 项目都要手写的易错胶水代码。moonform 补齐这一层:headless(不绑定渲染框架)、三目标同构(同一校验定义前端/服务端复用)、行为正确性以上游测试集等价翻译为规格(不是自夸,可审计)。

    • Headless 表单状态机:字段值、dirty/touched、错误派生、按需订阅、提交编排——语义对标 @tanstack/form-core@1.33.5(上游测试集行为等价翻译,见 upstream/PARITY.md
    • 结构化字段访问器(lens):替代 dot-path 字符串——(key, get, set) 三元组组合子,编译期安全、重构改名不失配;数组字段操作(push/insert/remove/swap/move/replace)+ meta 迁移
    • Resolver 协议:校验器即插即用——内置轻量规则包(required/min/max/pattern/闭包)+ moonschema(zod-style JSON Schema)适配
    • Adapter 协议:核心不渲染;tiye/react 适配器经 use_sync_external_store 桥接
    • 跨目标:core/rules 零第三方依赖,js/wasm/native 三目标可编译;同一校验定义服务端/前端复用(同构)
    • 虚拟时钟:防抖/竞态中止/异步提交对注入的 Clock 协议实现——测试确定性推进虚拟时间,不依赖真实定时器

    #包结构

    依赖说明
    core状态机、Key/Lens、MetaStore、订阅、Clock、Validator、提交编排
    rulesrequired/min/max/length/pattern/contains/equals/non_blank/numeric/must_be_true/自定义闭包
    schemavendor moonschemamoonschema 适配(JSON-Pointer 错误路径 → 字段错误槽)
    reacttiye/reactFieldBridge + use_field(js 目标)
    lens-gen.mbti 驱动的访问器代码生成器(增量增强)
    examples/login全部登录表单示例(headless 验证可跑)

    #快速开始

    struct Login {
    email : String
    password : String
    } derive(Eq, Debug)

    fn email_l() -> @core.Lens[Login, String] {
    @core.field("email", v => v.email, (v, e) => { ..v, email: e })
    }

    let form = @core.FormApi::make({ email: "", password: "" })
    let email = form.field(
    email_l(),
    on_change_validate=@rules.required().then(@rules.min_length(3)),
    )
    email.mount()
    email.set_value("ab") // → errors: ["Must be at least 3 characters"]
    email.handle_blur() // → touched + blurred
    form.handle_submit(submit_options=...) // 校验拦截 / 提交生命周期

    数组字段:

    // fn friends_l() -> Lens[Form, Array[Friend]] —— 访问器定义同上
    form.push_value(friends_l(), friend)
    form.remove_value(friends_l(), 0) // friends[1].name 的错误迁移到 friends[0].name
    form.swap_values(friends_l(), 0, 2)

    异步校验(虚拟时钟,测试确定性):

    let clock = @core.VirtualClock::make()
    let form = @core.FormApi::make(values).with_clock(clock.clock())
    let field = form.field(
    email_l(),
    on_change_async_debounce_ms=500,
    on_change_async_validate=@core.async_validator(...),
    )
    field.set_value("x")
    clock.run_all() // 防抖窗口 + 校验完成,一次推进

    React(tiye/react):

    let bridge = @formreact.FieldBridge::make(form, email_l())
    let handle = @formreact.use_field_handle(bridge)
    let state = @react.use_sync_external_store(
    () => handle.subscribe(),
    () => handle.get_snapshot(),
    )
    // state.value / state.errors / state.is_touched ...

    更多见 examples/README浏览器 demo(React 登录表单, 无头浏览器 5 项检查全过):web/

    #测试与验收

    moon test --target js # 210 tests(含上游译文 + 文档测试) moon test --target wasm # 205 tests moon test --target native # CI 已验证(GitHub Actions 五作业全绿) moon run src/examples/login --target js # 示例验收

    三目标 moon check 零警告;CI 覆盖 check×3 目标 + test×3 目标 + 示例运行 workflow)。

    上游对账:143 个译文测试覆盖 form-core@1.33.5 核心 291 用例的行为语义; 豁免清单(utils dot-path 机器/FormGroup/mergeForm/类型层测试)与有意偏离 (值语义/虚拟时钟/稳定 ID 簿记等 7 项)逐条记录于 upstream/PARITY.md

    #致谢

    #License

    MIT