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());
}