Architecture Overview

Stately is built around a few core principles that shape its architecture. Understanding these principles helps you work effectively with the framework.

Design Principles

1. Define Once, Derive Everything

The central idea in Stately is that your entity definitions are the single source of truth. From a Rust struct with derive macros, the framework generates:

  • State management infrastructure (collections, CRUD operations)
  • API endpoints with proper HTTP semantics
  • OpenAPI documentation
  • TypeScript types for the frontend
  • Schema definitions for form rendering

This eliminates the drift that occurs when you maintain separate definitions in multiple places.

2. Vertical Plugin Architecture

Plugins in Stately are "vertical" - they span the entire stack from backend to frontend. A plugin like files provides:

  • Rust crate (stately-files) with API endpoints and utilities
  • TypeScript package (@statelyjs/files) with components and hooks

This ensures that backend capabilities have corresponding frontend support, and vice versa.

3. Schema-Driven UI

The frontend doesn't just consume API types - it uses parsed schema information to render appropriate form controls. A string field renders a text input. An enum renders a select. An object renders a nested form. This happens automatically based on the schema structure.

4. Composition Over Configuration

Stately favors composable building blocks over monolithic configuration. You can use:

  • Individual hooks without pre-built views
  • Views without full pages
  • Schema parsing without UI
  • Backend macros without frontend integration

Each layer is useful independently.

Core Architecture

Backend Layer

┌─────────────────────────────────────────────────┐
│                 Your Application                │
├─────────────────────────────────────────────────┤
│                                                 │
│  ┌─────────────────────────────────────────┐    │
│  │  #[stately::axum_api]                   │    │
│  │  Generates HTTP handlers, router,       │    │
│  │  OpenAPI docs, request/response types   │    │
│  └─────────────────────────────────────────┘    │
│                       ↓                         │
│  ┌─────────────────────────────────────────┐    │
│  │  #[stately::state]                      │    │
│  │  Generates StateEntry enum, Entity      │    │
│  │  wrapper, collection types, state impl  │    │
│  └─────────────────────────────────────────┘    │
│                       ↓                         │
│  ┌─────────────────────────────────────────┐    │
│  │  #[stately::entity]                     │    │
│  │  Implements HasName trait               │    │
│  └─────────────────────────────────────────┘    │
│                       ↓                         │
│  ┌─────────────────────────────────────────┐    │
│  │  stately crate                          │    │
│  │  Core types:                            │    │ 
│  │  EntityId, Link, Collection, Singleton, │    │
│  │  traits, error types, etc.              │    │
│  └─────────────────────────────────────────┘    │
│                                                 │
└─────────────────────────────────────────────────┘

Key components:

  • EntityId: UUID v7-based identifiers that are time-sortable
  • Link: Enum for entity references (by ID) or inline embedding
  • Collection: HashMap-backed entity collection with CRUD operations
  • Singleton: Single-instance container for settings-like entities
  • StateEntity trait: Connects entities to their collection type
  • Entity enum: Type-erased entity container

Frontend Layer

┌─────────────────────────────────────────────────┐
│                 Your Application                │
├─────────────────────────────────────────────────┤
│                                                 │
│  ┌─────────────────────────────────────────┐    │
│  │  Your Components:                       │    │
│  │  Use hooks, views, and pages            │    │
│  └─────────────────────────────────────────┘    │
│                       ↓                         │
│  ┌─────────────────────────────────────────┐    │
│  │  @statelyjs/stately                     │    │
│  │  Runtime, hooks, views, pages, codegen  │    │
│  │  Core plugin with entity management     │    │
│  └─────────────────────────────────────────┘    │
│                       ↓                         │
│  ┌─────────────────────────────────────────┐    │
│  │  @statelyjs/ui                          │    │
│  │  Base components, layout, plugin system,│    │
│  │  component registry, theming            │    │
│  └─────────────────────────────────────────┘    │
│                       ↓                         │
│  ┌─────────────────────────────────────────┐    │
│  │  @statelyjs/schema                      │    │
│  │  Schema type system, node parsing,      │    │
│  │  validation, runtime creation           │    │
│  └─────────────────────────────────────────┘    │
│                                                 │
└─────────────────────────────────────────────────┘

Key components:

  • Schema Runtime: All of the types and interfaces powering the Stately UIs
  • Codegen: Parses OpenAPI schemas into typed node trees
  • UI Runtime: Combines schema with API client and plugins
  • Component Registry: Maps node types to React components
  • Hooks: React Query-based data fetching and mutations
  • Views/Pages: Pre-built UI for common entity operations

OpenAPI Bridge

The OpenAPI specification serves as the contract between backend and frontend:

Backend                  OpenAPI                 Frontend
────────                 ───────                 ────────

#[state(openapi)]     → openapi.json          → stately generate
#[state(axum_api)]      ↓
                       ┌─────────────────┐
                       │   types.ts      │      TypeScript types
                       │   schemas.ts    │      Parsed schema nodes
                       └─────────────────┘

                       ┌─────────────────┐
                       │     stately     │      Schema runtime
                       │    statelyUi    │      UI runtime
                       └─────────────────┘

The codegen step ensures frontend types exactly match backend definitions.

Plugin Architecture

Plugins extend Stately at both layers:

Backend Plugin Pattern

// Plugin provides a router factory
pub fn router<S>(state: S) -> Router<S>
where
    S: Clone + Send + Sync + 'static,
    MyPluginState: FromRef<S>,
{
    Router::new()
        .route("/my-endpoint", get(handler))
        .with_state(state)
}

// State extraction via FromRef
impl FromRef<AppState> for MyPluginState {
    fn from_ref(state: &AppState) -> Self {
        // Extract plugin-specific state
    }
}

Frontend Plugin Pattern

// Schema plugin - extends type system
const mySchemaPlugin = createSchemaPlugin<MyPlugin>({ name: 'my-plugin' });

// UI plugin - registers components and API
export const myUiPlugin = createUiPlugin<MyUiPlugin>({
  name: 'myPlugin',
  operations: MY_OPERATIONS,
  setup: (ctx, options) => {  
    // Register custom components
    ctx.registerComponent('myNodeType', 'edit', MyEditComponent);
    
    return {};
  };
}

Data Flow

Creating an Entity

1. User fills in form

2. useCreateEntity hook

3. API client sends PUT /api/entity

4. Axum handler deserializes request

5. State.create_entity() adds to collection

6. Response with new EntityId

7. React Query invalidates cache

8. UI re-renders with new entity

Form Rendering

1. Schema codegen and runtime parses OpenAPI spec

2. Node trees created (object → properties → other node types)

3. FieldEdit component routes node

4. Registry lookup by nodeType

5. Specific component rendered (string → Input, enum → Select, etc.)

6. onChange propagates up to form state, gated at complex layers

Key Types

Backend

TypePurpose
EntityIdUUID v7 identifier for entities
Link<T>Reference or inline entity relationship
Collection<T>HashMap-backed entity collection
Singleton<T>Single-instance container
StateEntityTrait connecting entity to state
StateCollectionTrait for CRUD operations

Frontend

TypePurpose
SchemasType combining config, plugins, and derived types
StatelyConfigConfiguration containing union of all schema node types
UiRegistryComponent and transformer registries
StatelyUiRuntimeCombined schema + UI runtime