Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Augani/kael/llms.txt

Use this file to discover all available pages before exploring further.

In Kael, every piece of mutable application state lives inside an entity. Rather than scattering state across thread-locals or global singletons, you give ownership of your data to the App — the single root object that owns all entity state. You then interact with that state through typed handles and context references that enforce safe, structured access. This model makes it easy to wire up observers, subscriptions, and async tasks without fighting the borrow checker, because the App owns the data and contexts provide a controlled gateway to it.

The App and entity ownership

When you start a Kael application, the entry point hands you a mutable reference to the App. This object is the authoritative owner of every entity in the program. You create entities by calling cx.new(), which accepts a closure that constructs your value:
use kael::{App, AppContext, Application, Entity};

struct Counter {
    count: usize,
}

Application::new().run(|cx: &mut App| {
    let counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
});
The Entity<Counter> returned is a lightweight, reference-counted handle — not the actual data. Like an Rc, it can be cloned freely and dropped to release its slot, but it grants no direct access to the inner Counter without also holding a reference to the app.

Entity<T> handles

An Entity<T> is an inert identifier plus a compile-time type tag. It carries a reference-counted pointer back to the slot the App owns, but you cannot read or write the inner value through the handle alone. To access state, you call .update() or .read() on the handle and pass in the current context:
counter.update(cx, |counter: &mut Counter, cx: &mut Context<Counter>| {
    counter.count += 1;
    cx.notify();
});

let current = counter.read(cx).count;
Handles can be cloned and stored anywhere in your application — in parent entities, event handlers, or async tasks. Because the App owns the underlying data, cloning a handle never duplicates state.

Context<T>

A Context<T> is a wrapper around App enriched with the identity of a specific entity of type T. Whenever you update an entity — whether through cx.new(), .update(), or a render callback — Kael passes you a Context<T> alongside a &mut T. The context provides both the full surface of AppContext methods and a set of entity-specific operations.
counter.update(cx, |counter, cx: &mut Context<Counter>| {
    counter.count += 10;
    cx.notify(); // mark this entity dirty
});
Context<T> dereferences to App, so any method available on App is also available on Context<T>.

The AppContext trait

The AppContext trait is the shared interface that App, Context<T>, and async contexts all implement. Its key methods are:
MethodDescription
cx.new(build)Create a new entity, returning Entity<T>
cx.update_entity(handle, update)Mutably update an entity by handle
cx.read_entity(handle, read)Read from an entity immutably
cx.background_spawn(future)Spawn a Send future on the background executor
cx.read_global::<G, _>(callback)Read a global value
pub trait AppContext {
    type Result<T>;

    fn new<T: 'static>(
        &mut self,
        build_entity: impl FnOnce(&mut Context<T>) -> T,
    ) -> Self::Result<Entity<T>>;

    fn update_entity<T, R>(
        &mut self,
        handle: &Entity<T>,
        update: impl FnOnce(&mut T, &mut Context<T>) -> R,
    ) -> Self::Result<R>
    where
        T: 'static;

    fn read_entity<T, R>(
        &self,
        handle: &Entity<T>,
        read: impl FnOnce(&T, &App) -> R,
    ) -> Self::Result<R>
    where
        T: 'static;

    fn background_spawn<R>(
        &self,
        future: impl Future<Output = R> + Send + 'static,
    ) -> Task<R>
    where
        R: Send + 'static;

    fn read_global<G, R>(
        &self,
        callback: impl FnOnce(&G, &App) -> R,
    ) -> Self::Result<R>
    where
        G: Global;
}

The VisualContext trait

For entities that are associated with a window — such as views that implement Render — Kael provides the VisualContext trait. It extends AppContext with window-aware operations:
pub trait VisualContext: AppContext {
    fn window_handle(&self) -> AnyWindowHandle;

    fn update_window_entity<T: 'static, R>(
        &mut self,
        entity: &Entity<T>,
        update: impl FnOnce(&mut T, &mut Window, &mut Context<T>) -> R,
    ) -> Self::Result<R>;

    fn new_window_entity<T: 'static>(
        &mut self,
        build_entity: impl FnOnce(&mut Window, &mut Context<T>) -> T,
    ) -> Self::Result<Entity<T>>;

    fn focus<V: Focusable>(&mut self, entity: &Entity<V>) -> Self::Result<()>;
}
Use VisualContext methods when your entity needs access to the Window — for example, when creating a view that paints to the screen.

The EventEmitter trait

Entities can emit typed events to communicate with subscribers. To opt in, implement the EventEmitter marker trait for your entity, parameterized by the event type:
use kael::EventEmitter;

struct Counter {
    count: usize,
}

struct CounterChanged {
    increment: usize,
}

impl EventEmitter<CounterChanged> for Counter {}
Once you implement EventEmitter<E>, you can call cx.emit(event) from within a Context<Counter> to broadcast the event to all subscribers.

Observing and subscribing

Kael provides two patterns for reacting to entity changes:
  • cx.observe(entity, callback) — fires when the observed entity calls cx.notify(). Your callback receives the changed entity’s handle and your own context.
  • cx.subscribe(entity, callback) — fires when the observed entity calls cx.emit(event). Your callback receives the entity handle, the event value, and your context.
Both methods return a Subscription. Dropping the subscription cancels it automatically. Call .detach() to keep the subscription alive for as long as both entities exist.
Application::new().run(|cx: &mut App| {
    let first: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });

    let second = cx.new(|cx: &mut Context<Counter>| {
        // Observe state changes
        cx.observe(&first, |second, first, cx| {
            second.count = first.read(cx).count * 2;
        })
        .detach();

        // Subscribe to typed events
        cx.subscribe(&first, |second, _first, event: &CounterChanged, _cx| {
            second.count += event.increment * 2;
        })
        .detach();

        Counter { count: 0 }
    });

    first.update(cx, |first, cx| {
        first.count += 2;
        cx.emit(CounterChanged { increment: 2 });
        cx.notify();
    });
});
You can set up observations and subscriptions inside the build closure passed to cx.new(), even before the entity itself has been fully constructed. Kael defers activation until after the entity is inserted.

Global state

For application-wide singletons — things like themes, settings, or connection pools — Kael provides a typed global store. Any type that implements the Global marker trait can be stored and retrieved by type:
use kael::{BorrowAppContext, Global};

struct AppSettings {
    font_size: f32,
}

impl Global for AppSettings {}

// Set a global
cx.set_global(AppSettings { font_size: 14.0 });

// Read a global
cx.read_global::<AppSettings, _>(|settings, _app| {
    println!("Font size: {}", settings.font_size);
});

// Update a global
cx.update_global::<AppSettings, _>(|settings, _cx| {
    settings.font_size = 16.0;
});
Globals are stored by type identity. Only one value of each type can exist at a time. Use update_default_global to initialize a global with its Default implementation if it hasn’t been set yet.

Reserving entity slots

If you need the EntityId of an entity before it is fully constructed — for example, to store a self-referential handle — use the two-step reserve_entity / insert_entity API:
let reservation = cx.reserve_entity::<Counter>();
let id = reservation.entity_id(); // available before insertion
let entity = cx.insert_entity(reservation, |_cx| Counter { count: 0 });

Build docs developers (and LLMs) love