Aria v1.0.0

Modern C++20 MVVM framework — cross-platform, layered, coroutine-first.
Targets Windows / macOS / Linux / iOS / Android / Web with a single shared core.

status: v1.0.0 C++20 MIT

English (Markdown) | 简体中文 | 中文 HTML

Why

Existing C++ MVVM offerings either drag in a giant UI framework (Qt is 100+ MB), glue you to a single platform, or hide behind macros. aria is the opposite:

Architecture (10 modules)

┌─────────────────────────────────────────────────────────────┐
│                        Application                          │
└───────────────────────────────┬─────────────────────────────┘
        ┌──────────────────┬─────┴────────────┐
        ▼                  ▼                  ▼
  ┌───────────┐      ┌───────────┐      ┌───────────┐   (optional
  │Qt6 adapter│      │JNI adapter│      │HTTP adapter│   adapters,
  │AppKit/UIKit│     │ (Android) │      │REST/SSE Web│   opt-in;
  │           │      │           │      │WASM planned│
  └─────┬─────┘      └─────┬─────┘      └─────┬─────┘
        └──────────────────┴──────────────────┘
                           ▼
            ┌─────────────────────────────┐
            │    aria-binding (SHARED)    │
            │  BindingEngine + IViewAdapter│
            └──────────────┬──────────────┘
        ┌──────────────────┴──────────────────┐
        ▼                                     ▼
┌─────────────────┐                  ┌─────────────────┐
│ aria-runtime    │                  │  aria-async     │
│ (SHARED)        │                  │  (header-only)  │
│ EventBus        │                  │  Task        │
│ Container       │                  │  Scheduler      │
│ Dispatcher      │                  │  Executor       │
│ Logger          │                  │  schedule_on    │
└────────┬────────┘                  └────────┬────────┘
         └────────────────┬───────────────────┘
                          ▼
            ┌─────────────────────────────┐
            │   aria-core (header-only)   │
            │  Property / Computed / Cmd  │
            │  ObservableList / Validator │
            └──────────────┬──────────────┘
                           ▼
            ┌─────────────────────────────┐
            │    aria-abi (STATIC)        │
            │  Type-erased Signal/Slot    │
            │  ABI-stable, no templates   │
            └─────────────────────────────┘
ModuleTypeDepends onNotes
aria-abiSTATICnoneType-erased signal/slot. No templates. ABI-stable.
aria-coreheader-onlyabiAll the templates: Property, Computed, Command, ObservableList, Validator. Source-compatible only (not ABI-stable).
aria-asyncheader-onlycoreC++20 Task<T>, executors. Source-compatible only.
aria-runtimeSHAREDcore, abiEventBus / Container / Dispatcher / Logger — singletons live in one dylib. ABI-stable (non-template exports).
aria-bindingSHAREDcore, runtimeBindingEngine, IViewAdapter. ABI-stable (non-template exports).
AdaptersSHARED/STATICbindingQt6 / AppKit / UIKit / JNI / WASM (each opt-in).

Requirements

Windows is supported on two toolchains: MSYS2 UCRT64 (GCC) and MSVC / Visual Studio 2022. Pick whichever fits your team's existing stack — both build the full framework + tests + adapters from a single tree, no source forks.

Quick start

git clone https://github.com/dqsjqian/aria.git
cd aria
cmake -B build
cmake --build build -j
ctest --test-dir build --output-on-failure

First configure pulls doctest via the bundled CPM.cmake. After that everything is offline.

One-liner build scripts

# macOS / Linux
scripts/build.sh             # release
scripts/build.sh tests       # release + ctest
scripts/build.sh asan        # debug + AddressSanitizer + UBSan
scripts/build.sh tsan        # debug + ThreadSanitizer
scripts/build.sh clean

# Windows — MSYS2 UCRT64 (GCC + Ninja)
scripts\build.ps1            # release
scripts\build.ps1 tests
scripts\build.ps1 asan

# Windows — MSVC / Visual Studio 2022
scripts\build-msvc.ps1       # release  (build/flavors/msvc/ tree)
scripts\build-msvc.ps1 tests
scripts\build-msvc.ps1 debug
scripts\build-msvc.ps1 asan  # /fsanitize=address (no UBSan on MSVC)

Windows toolchains

Aria ships with two parallel build scripts for Windows. They live side-by-side in scripts/, write to separate build directories, and neither one needs to know about the other.

ToolchainScriptBuild dirNotes
MSYS2 UCRT64 (GCC 14+ / Clang 18+)scripts\build.ps1build/Lightweight (~300 MB). Pre-installed on most CI images. Auto-detected from C:\msys64\ucrt64\bin and a few other common paths.
MSVC v143 (VS 2022)scripts\build-msvc.ps1build/flavors/msvc/Auto-detects the VS install via vswhere, scrubs MSYS2 env vars (INCLUDE / LIB / CPATH / ...) before running CMake, and uses the Visual Studio 17 2022 generator.

You can switch back and forth without clean — the two trees are isolated. CI runs both nightly to make sure neither regresses.

MSVC one-time setup

# 1. Install Visual Studio 2022 Build Tools (or the full IDE) with
#    workload "Desktop development with C++" + "C++ CMake tools".
# 2. (Optional) install Qt 6 with the msvc2022_64 kit if you need the
#    Qt6 adapter / Qt showcase.
# 3. From any PowerShell window:
scripts\build-msvc.ps1 tests

MSYS2 one-time setup

# 1. Install MSYS2 from https://www.msys2.org
# 2. Open the "MSYS2 UCRT64" shell:
pacman -Syu
pacman -S --needed mingw-w64-ucrt-x86_64-toolchain `
                   mingw-w64-ucrt-x86_64-cmake `
                   mingw-w64-ucrt-x86_64-ninja git
# 3. (Optional) Add C:\msys64\ucrt64\bin to your PATH.
# 4. From any shell:
scripts\build.ps1 tests

Rationale for shipping both: aria is coroutine-heavy C++20 code that libstdc++, libc++, and the MSVC STL all handle cleanly. Pinning a single Windows toolchain artificially excluded a large chunk of users in the .NET / Visual Studio ecosystem — we now validate against MSVC v143 on the same release gate as macOS, Ubuntu, and MSYS2.

Use it from your own project

Option A — find_package after install (recommended for production)

# In the aria tree:
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build build -j && sudo cmake --install build
# In your project's CMakeLists.txt:
find_package(aria 1.0 REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE aria::aria)
# or pick individual modules: aria::core / ::async / ::runtime / ::binding

Option B — vendored (no install)

add_subdirectory(third_party/aria EXCLUDE_FROM_ALL)
target_link_libraries(my_app PRIVATE aria::core aria::async)

A ready-to-copy template lives in templates/quickstart/.

Examples

aria ships runnable examples covering every supported UI toolkit, plus headless console examples that exercise the core with no GUI.

UI showcases — one per toolkit

#ProjectToolkitBuildWhat it shows
1qt-showcaseQt6 (Widgets)CMake (ARIA_BUILD_QT6=ON)The flagship demo: one app, nine tabs, every public feature — reactive Property/Computed/Effect, Commands, ObservableList + QAbstractListModel, Validator, Task<T> + executors, cancellation, retry, when_all, EventBus, DI Container, Dispatcher, navigation, two-way binding.
2macos-appkit-mvvmmacOS AppKit (ObjC++)XcodeSelf-contained Xcode project using aria from Objective-C++: an IViewAdapter over NSTextField/NSButton, the same ViewModel driving native AppKit controls.
3ios-oc-uikit-mvvmiOS UIKit (ObjC++)XcodeSelf-contained iOS Xcode project: an IViewAdapter over UILabel/UITextField/UIButton (Masonry layout), the same ViewModel on iPhone/iPad.
4web-mvvmWeb (HTTP/REST/SSE)CMake (ARIA_BUILD_HTTP=ON)A C++ ViewModel exposed to the browser via HttpAdapter — SSE-pushed state + REST-driven commands, two-way binding, plus a vanilla-JS client (aria_client.js). Optional HTTPS.
5android-jni-mvvmAndroid (JNI + Compose/View)Gradle (NDK r26+)An Android Studio / Gradle project driving the same C++ ViewModel from Kotlin through the aria-jni adapter.

Headless / console examples

Built with ARIA_BUILD_EXAMPLES=ON (default), no GUI:

ProjectWhat it shows
inspector-demoCLI reactive-graph flush tracer — prints the push/pull trace of a live graph (diagnostics / TraceSink).
plugin-property-demoCross-dylib ABI smoke: a host exe + plugin shared library driving a Property<T> purely through the stable, non-template aria::IProperty interface across a DSO boundary. Runs as the cross_dylib_abi_smoke test.
todomvcHeadless TodoMVC: ObservableList + two live FilteredList views (active/completed) + Selection, all reacting incrementally. Runs as the todomvc_smoke test.

Build & run example 1 (Qt):

cmake -S . -B build -DARIA_BUILD_QT6=ON
cmake --build build -j
./build/examples/1-qt-showcase/ex_qt_showcase

Example 4 (web) needs the HTTP adapter; see examples/4-web-mvvm/README.md for run instructions:

cmake -S . -B build -DARIA_BUILD_HTTP=ON
cmake --build build --target example_4_web_mvvm

The headless examples build by default and run via ctest (cross_dylib_abi_smoke, todomvc_smoke) or directly from build/bin/.

Examples 2 and 3 are not part of the CMake tree — open the Xcode project and hit Run; example 5 is an Android Studio / Gradle project (NDK r26+):

Build options

OptionDefaultDescription
ARIA_BUILD_TESTSONBuild unit tests + ctest registration.
ARIA_BUILD_EXAMPLESONBuild all examples (console + Qt6 when enabled).
ARIA_BUILD_BENCHMARKONBuild the micro-benchmark suite.
ARIA_BUILD_SHAREDONRuntime/binding as .dylib/.so/.dll instead of .a.
ARIA_BUILD_QT6OFFBuild Qt6 adapter and GUI examples (requires Qt6Widgets).
ARIA_BUILD_APPKITOFF(production-grade) macOS AppKit adapter as a first-class CMake module — built as STATIC + .mm, ships aria::adapters::appkit, passes the full adapter_conformance test battery. Requires APPLE.
ARIA_BUILD_UIKITOFF(production-grade) iOS UIKit adapter as a first-class CMake module — built as STATIC + .mm, ships aria::adapters::uikit, passes the in-app conformance battery (25/25 on iPhone 17 Pro Max). Requires APPLE.
ARIA_BUILD_JNIOFFBuild Android JNI adapter as a first-class CMake module — built as STATIC, ships aria::adapters::jni, implementing the same IViewAdapter contract as Qt/AppKit/UIKit via reflective JNI dispatch. Requires an Android NDK toolchain (NDK r26+).
ARIA_BUILD_WASMOFF(planned) Build WebAssembly adapter.
ARIA_ENABLE_ASANOFFAddressSanitizer.
ARIA_ENABLE_UBSANOFFUndefinedBehaviorSanitizer.
ARIA_ENABLE_TSANOFFThreadSanitizer.

Hello, world

#include "aria/aria.hpp"
using namespace aria;

Property<int> count{0};

// No explicit dependency list — every Property::get() inside the lambda
// is auto-tracked on first evaluation.
Computed<std::string> label([&]{
    return "count = " + std::to_string(count.get());
});

Command<> increment([&]{ count = count.get() + 1; });

auto sub = label.bind([](const std::string& s) { std::cout << s << '\n'; });

increment();   // → "count = 1"
increment();   // → "count = 2"

Async (C++20 coroutines)

#include "aria/async/task.hpp"
#include "aria/async/executor.hpp"
using namespace aria::async;

Task<std::string> fetch_user(int id) {
    co_await schedule_on(network_pool);      // jump to worker thread
    auto raw = http::get("/users/" + std::to_string(id));
    co_await schedule_on(main_dispatcher);   // jump back to UI thread
    co_return parse(raw);
}

Cross-platform mapping

PlatformUI hostAdapter
WindowsQt6 / WinUIaria-qt6 ✅ ready (MSYS2 UCRT64 + MSVC 2022)
macOSAppKit / Qt6aria-qt6 ✅ ready; AppKit ✅ ready (example 2)
LinuxQt6 / GTKaria-qt6 ✅ ready
iOSUIKit / SwiftUI bridgeUIKit ✅ ready (example 3); aria-uikit module planned
AndroidCompose / Viewaria-jni ✅ ready (NDK r26+)
Web (server-driven)HTML/JS in browseraria-http ✅ ready (REST + SSE; example 4)
Web (in-browser C++)DOM via WASMaria-wasm planned

The HTTP adapter ships a small server (HttpAdapter) that exposes any ViewModel over a JSON REST + Server-Sent-Events protocol, plus a vanilla-JS browser SDK (aria_client.js). The server is built on the vendored single-header cpp-httplib (HTTP/1.1 + SSE) and nlohmann::json (encode/decode) — both committed under third_party/, so the adapter adds no new external build dependency; aria itself owns the wire protocol, view registry, subscription dispatch and SSE fan-out. It is the right shape for desktop apps that want a web UI on the side, headless services, and local debug dashboards. The WASM adapter — which compiles C++ business logic into the browser sandbox — solves a different, more constrained problem and remains on the roadmap. See RFC 0001 for the design.

The current release ships the platform-agnostic core, runtime, async, and binding layers — fully unit-tested. Qt6, AppKit, UIKit, JNI, and HTTP are first-class opt-in adapters in the CMake tree (subject to their platform requirements). WASM remains planned; the IViewAdapter interface is stable.

Test status

$ ctest --test-dir build --output-on-failure
Test project /…/aria/build
    Start 1: abi_tests           ✅ Passed
    Start 2: core_tests          ✅ Passed
    Start 3: fuzz_tests          ✅ Passed
    Start 4: async_tests         ✅ Passed
    Start 5: runtime_tests       ✅ Passed
    Start 6: binding_tests       ✅ Passed
    Start 7: qt6_tests           ✅ Passed   (when ARIA_BUILD_QT6=ON)
    Start 8: appkit_conformance  ✅ Passed   (Apple-only)
    Start 9: appkit_table_source ✅ Passed   (Apple-only)

100% tests passed, 0 tests failed (up to 9 suites, depending on options)

75+ individual test cases across the suites, including dedicated regression tests for the lifecycle / re-entrancy / exception-safety invariants pinned in docs/reference/lifecycle.md and docs/reference/error-model.md. The build tree also includes a cross-dylib ABI smoke (cross_dylib_abi_smoke) and a headless TodoMVC (todomvc_smoke).

Benchmark (Apple M-series, -O3 -DNDEBUG)

Operationns/op
Property<int>::get()10.4
Property<int>::set() no observers28.5
Property<int>::set() 1 observer29.3
Property<int>::set() 10 observers45.9
Subscribe + auto-unsubscribe cycle54.9
Computed chain x5 (set + recompute + get)289.1
EventBus::publish (1 subscriber)13.4
Container::resolve<Singleton>7.6
10 sets wrapped in reactive::batch (notify once)156.1
Batch update speedup vs individual1.91×

Framework contracts

Every non-trivial behaviour Aria promises is pinned in a numbered contract document. Each contract item carries an ID (e.g. L-13, E-22, LD-7, D-4, S-31) so a failing assertion or PR review comment can point straight at the canonical description.

DocumentPrefixScope
docs/reference/api-style.mdS-NNaming, namespace, error and async-entry style
docs/reference/lifecycle.mdL-NThreading, subscription, reactive flush, view-destroy, async cancel/dtor invariants
docs/reference/error-model.mdE-Naria::Error / ErrorKind taxonomy and per-subsystem error contracts
docs/reference/list-diff-contract.mdLD-NInsert / Remove / Replace / Move / Reset / ItemChanged semantics
docs/reference/diagnostics.mdD-Naria::TraceEvent + aria::TraceSink protocol
docs/reference/performance.mdPERF-NComplexity bounds and per-operation baselines for every public API

The P0 hard-bedrock pass (see CHANGELOG → Latest framework-grade hardening) closed every open contract above; the seven framework-level fuzzers in modules/core/fuzz/ stress-verify the lifecycle invariants (default 50k iterations / fuzzer; nightly runs set ARIA_FUZZ_ITERS=1000000).

Capabilities

CapabilityTypeWhere
Reactive stateProperty<T> / Computed<T> / Effectaria/reactive/reactive.hpp
CommandsCommand<Args...> (reactive can_execute)aria/command.hpp
CollectionsObservableList<T> + derived Filtered/Sorted/Mapped/Distinct/Grouped/Pagedaria/observable_list.hpp, aria/derived/*
SelectionSelection<T> / MultiSelection<T> (SE-1..SE-5)aria/selection.hpp
ValidationValidator<T> / FormValidator / ValidationState + async rulesaria/validator.hpp, aria/binding/form.hpp, aria/async/async_validator.hpp
AsyncTask<T> / AsyncCommand / with_timeout / when_any / when_all / CancellationTokenaria/async/*
Data fetchingAsyncResource<T> (SWR + dedupe) / Loadable<T> (5-state)aria/async/async_resource.hpp, aria/loadable.hpp
NavigationNavigator (push/pop/push_for_result<R>, route patterns)aria/binding/navigation.hpp
BindingBindingEngine / IViewAdapter / IView / Converter / bind_view_lifetimearia/binding/*
DiagnosticsTraceEvent / TraceSink / GraphInspector (zero-overhead off)aria/diagnostics.hpp

Learn it: the documentation index links the guides, the Cookbook (task-oriented recipes), and the contract references. Build the symbol-level API reference with cmake -B build -DARIA_BUILD_DOCS=ON && cmake --build build --target aria_docs.

Roadmap

Aria does not ship public releases — the version stays 1.0.0 and there is no version-by-version changelog to maintain. The single source of truth for what is not yet done (and what has been deliberately deferred) lives in docs/ROADMAP.md. For the current capability snapshot, see CHANGELOG.md.

Contributing

Contributions are welcome! Please open an issue first to discuss design changes.

See CONTRIBUTING.md for the full build/test/layering guide.

Acknowledgments

License

MIT © 2026 aria contributors

📖 Alternative Formats

This documentation is also available in other formats:

Quick Access Script

# Open English HTML version
./scripts/open-readme.sh          # or: ./scripts/open-readme.sh en

# Open Chinese HTML version
./scripts/open-readme.sh zh

# Open both versions
./scripts/open-readme.sh all