Launch

Undra 1.0: one Rust core under native apps

Undra is a Rust framework for the part of an app that is not pixels: its domain logic, reactive state, data layer and persistence. You write that part once, in Rust; Undra generates the Swift, Kotlin and TypeScript that SwiftUI, Compose, React and React Native use, and every screen stays the platform's own.

What it is

An Undra core is a Rust crate. You mark its records, enums, errors and stores with a handful of attributes; the macros describe them as a schema, and undra bindgen turns the schema into the API each platform would write itself: an @Observable class for SwiftUI, StateFlows for Compose, hooks for React and React Native.

Each platform keeps a local mirror of the state its screens observe, so a read is a property access and never calls into Rust. A write crosses the boundary once per transaction, and the core answers with one binary change-set that patches every mirror. That is the diagram on the home page, and you can tap it. In a release build on the reference machine, an Apple M5 Pro and not a phone, a synchronous call into the core costs 49.8 ns on the core side, and a change-set of 100 signals 2.3 µs.

Who it is for

Teams that ship on two or more platforms that must agree on logic, state and data rules: the same validation, the same offline queue, the same cache and when it goes stale, the same answer to "what does this list contain now" on an iPhone, an Android phone and a browser tab. Written three times, those rules drift apart a release at a time. Undra writes them once and leaves each screen as native as it was.

It assumes a team that can carry Rust, a real cost that the default-choice post weighs against Kotlin Multiplatform, UniFFI and writing it three times.

What is in 1.0

  • The core. Records, enums and typed errors; stores of signals and computeds; keyed lists, and derived lists that send only what changed; objects and host callbacks; newtypes, decimals and declared generics.
  • The data layer. Queries and mutations with staleness, retry and optimistic updates that roll back; an offline queue; persistence that migrates stored data across schema changes and refuses what it cannot convert; paged and lazy lists; polling.
  • Ports. Ten platform services with adapters and deterministic fakes; WebSocket, server-sent events and SQLite; the app's own OkHttp, URLSession or fetch underneath.
  • The dev loop. undra dev serves one core to every running app and keeps its state across a rebuild, except in a web page started with npm run dev, which reloads and starts fresh; devtools with a change-set timeline; undra schema diff marks each API change breaking or additive.
  • Platforms. SwiftUI, Compose, React and React Native, built from Xcode, Gradle, Vite or Bazel. iOS 15 and 16 get ObservableObject stores, proven by compilation and a probe on iOS 26.5, not yet on an iOS 15 or 16 runtime, simulator or device.
  • Proof. 35 contract scenarios against the real core on Swift, Kotlin and TypeScript (two run on TypeScript only), 101 of 101 cells passing; 7,770 tests at the 2 October count, with more added since; an adversarial review before every feature merged.

Sizes are gated in CI. The hello-world web core, a WebAssembly module, is 113.1 KB gzipped in the committed record, against a budget of 120 KB. The JavaScript runtime a hello page loads up front is 15.8 KB gzipped against 16 KB. The hello-world Android library for arm64-v8a is 898.2 KB, stripped and uncompressed, against 1.2 MB per ABI. The roadmap lists everything by group.

Try it in five minutes

With Rust (and its wasm32-unknown-unknown target) and Node installed:

curl -fsSL https://shreypdev.github.io/undra/install.sh | sh
undra init myapp
cd myapp/web && npm install && npm run dev

Cargo works too (cargo install --git https://github.com/shreypdev/undra undra-cli, built from source). undra init writes a core with a to-do store, its bindings and an app per platform, downloading nothing. npm run dev builds the core to WebAssembly (the first build compiles the Undra crates and takes a minute or two) and serves the app at http://localhost:5173. Edit core/src/lib.rs and save: the page is on the new core. undra dev serves one core to every app at once.

The iOS and Android apps get their runtimes from the same GitHub release, with no account: the Swift package at the repository's root and the Kotlin modules from JitPack. Getting started has every step, as we ran it.

What it is not

  • Not a UI toolkit. No widgets, no layout, no shared screens, and none planned. Why Undra keeps your UI native says who should share the UI instead.
  • Not for a single-platform app. One platform has nothing to agree with; its logic belongs in that platform's language, without a boundary or a second toolchain.
  • Not proven on physical phones yet. Every device number so far comes from the iPhone simulator, the Android emulator or headless Chromium, and says so.
  • Not widely used. Undra is one team's work with no production users yet. The contract grid, the reviews and the records exist so you can check the claims instead of trusting them.

Where to go next

The playground runs the real core in your browser. Reads never cross the boundary explains the state model, and the comparisons with Kotlin Multiplatform and UniFFI and Crux say where each tool is ahead. Questions go to GitHub.