Release 0.1.3 - 2026-08-01

This release kicks off a Data Framework!, including model management, adapter management, and a REST adapter.

The data framework re-imagines Ember Data in type-safe async Rust, which brings one of Ember's killer features -- client-side model state management -- to Rust and WASM.

Read the full change log here.

Manage Models

The Auric CLI now supports a new generate model subcommand:

$ auric generate model customer
created src/models/customer.rs
updated src/models/mod.rs
==> make
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/drr)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.47s

Which generates the following file:

// src/models/customer.rs
use jsonapi_core::JsonApi;

#[derive(Clone, Debug, Default, JsonApi, PartialEq)]
#[jsonapi(type = "customers")]
pub struct Customer {
    #[jsonapi(id)]
    pub id: String,
}

#[cfg(test)]
mod tests {
    use super::Customer;

    #[test]
    fn can_construct() {
        let _ = Customer::default();
    }
}

The jsonapi_core crate is used for annotations and serde.

This example demonstrates a few additional fields, including a relation:

// src/models/customer.rs
use jsonapi_core::{JsonApi, Relationship};
use super::account::Account;

#[derive(Clone, Debug, Default, JsonApi, PartialEq)]
#[jsonapi(type = "customers")]
pub struct Customer {
    #[jsonapi(id)]
    pub id: String,
    
    pub name: String,
    
    #[jsonapi(relationship, type = "accounts")]
    pub owner: Relationship<Account>,
}

Manage Adapters

The CLI now supports generating an adapter:

$ auric generate adapter application
created src/adapters/application.rs
updated src/adapters/mod.rs
==> make
   Compiling my-app v0.1.0 (/Users/davidr/workspaces/drr)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.83s

The generated file looks like this:

use anyhow::Result;
use auric_runtime::{
    async_trait::async_trait,
    data::{Adapter, QueryRecordOptions},
};
use jsonapi_core::Resource;

#[derive(Debug, Default)]
pub struct ApplicationAdapter {}

#[async_trait(?Send)]
impl Adapter for ApplicationAdapter {
    async fn create_record(&self, _resource: Resource) -> Result<Resource> {
        todo!("NIY")
    }

    async fn delete_record(&self, _resource: &Resource) -> Result<()> {
        todo!("NIY")
    }

    async fn find_record(&self, _resource_type: &str, _id: &str) -> Result<Resource> {
        todo!("NIY")
    }

    async fn query(
        &self,
        _resource_type: &str,
        _query: serde_json::Value,
    ) -> Result<Vec<Resource>> {
        todo!("NIY")
    }

    async fn query_record(
        &self,
        _resource_type: &str,
        _query: serde_json::Value,
        _options: QueryRecordOptions,
    ) -> Result<Resource> {
        todo!("NIY")
    }

    async fn update_record(&self, _resource: Resource) -> Result<Resource> {
        todo!("NIY")
    }
}

You can also define additional model-specific adapters like this:

$ auric generate adapter customer

All of the adapters are registered with the Store, which uses an adapter_for() function to locate a model-specific adapter, or to fallback on the application adapter.

REST Adapter

A REST adapter is now available, and provides a concrete working example of the Adapter trait.

This unit tests shows how it is integrated with the Store:

#[tokio::test]
async fn can_find_record() {
    // Setup mock server and expections for a find Customer operation
    let server = Server::run();
    server.expect(
        Expectation::matching(request::method_path("GET", "/v1/customers/123"))
            .respond_with(json_encoded(json!({"customer": {"id": "123", "name": "Acme Widgets"}}))),
    );
    let server_url = server.url_str("/v1");

    #[derive(Debug, JsonApi)]
    #[jsonapi(type = "customers")]
    struct Customer {
        #[jsonapi(id)]
        id: String,
        name: String,
    }

    let adapter: Arc<dyn Adapter + Send + Sync> = Arc::new(RestAdapter::new(server_url));
    const MODEL: &str = "customer";
    let adapters = HashMap::from([(MODEL.to_string(), adapter)]);
    let store = Store::new(adapters);
    let _ = store.adapter_for(MODEL);
    let customer: Customer = store.find_record(MODEL, "123", None).await.unwrap();
    assert_eq!(customer.id, "123".to_string());
    assert_eq!(customer.name, "Acme Widgets".to_string());
}

©2026 Megalithic LLC | Website | Guide | GitLab | Contact