Skip to content

v0.5.0 - Transforms, Persistence, Graph Introspection & Dynamic Routing

Choose a tag to compare

@lxsaah lxsaah released this 22 Feb 18:45
· 61 commits to main since this release
86c8342

AimDB v0.5.0 Release Notes

Transforms, persistence, graph introspection, and dynamic routing!


🎯 What's New in v0.5.0

This release introduces reactive data transformations, a pluggable persistence layer, a dependency graph introspection API, and dynamic topic/address routing for connectors. It also ships two new crates (aimdb-persistence, aimdb-persistence-sqlite).


✨ Major Features

🔄 Transform API (Design 020)

Reactive data transformations between records — computed values that update automatically!

use aimdb_core::{AimDbBuilder, RecordKey};

// Single-input transform: derive Fahrenheit from Celsius
builder.configure::<Celsius>(AppKey::TempCelsius, |reg| {
    reg.buffer(BufferCfg::SingleLatest);
});

builder.configure::<Fahrenheit>(AppKey::TempFahrenheit, |reg| {
    reg.transform_raw(AppKey::TempCelsius, |c: Celsius| {
        Fahrenheit { value: c.value * 9.0 / 5.0 + 32.0 }
    });
});

// Multi-input join: combine humidity + temperature into comfort index
builder.configure::<ComfortIndex>(AppKey::Comfort, |reg| {
    reg.transform_join_raw(|join| {
        join.input::<Celsius>(AppKey::TempCelsius)
            .input::<Humidity>(AppKey::Humidity)
            .on_trigger(|trigger, state| {
                // Called whenever any input changes
                Some(ComfortIndex::compute(state))
            })
    });
});

Features:

  • 🔗 Single-input transform_raw() for simple derivations
  • 🔗 Multi-input transform_join_raw() with JoinTrigger event dispatch
  • 🔒 Mutual exclusion: a record cannot have both .source() and .transform()
  • 🚀 Automatic spawning: transforms run as async tasks during AimDb::build()
  • 🔍 Tracing integration: full lifecycle event logging

📊 Graph Introspection API (Design 021)

Visualize the dependency graph of your AimDB instance!

// Get full dependency graph
let nodes = db.graph_nodes();
let edges = db.graph_edges();
let order = db.graph_topo_order();

// Nodes show origin type and buffer config
for node in &nodes {
    println!("{}: {:?} ({:?})", node.key, node.origin, node.buffer_info);
}

// Edges show data flow
for edge in &edges {
    println!("{} → {} ({:?})", edge.from, edge.to, edge.edge_type);
}

RecordOrigin variants:

  • Source — direct producer writes
  • Link — connector-bridged external data
  • Transform — single-input reactive derivation
  • TransformJoin — multi-input reactive join
  • Passive — no producer (consumer-only)

Also available via aimdb-client and aimdb-mcp tools.

💾 Persistence Layer (New Crates)

Long-term record history with pluggable backends!

use aimdb_persistence::AimDbBuilderPersistExt;
use aimdb_persistence_sqlite::SqliteBackend;

// Set up SQLite persistence with 7-day retention
let backend = SqliteBackend::new("./aimdb_history.db")?;
builder.with_persistence(backend, Duration::from_secs(7 * 24 * 3600));

// Mark records to persist
builder.configure::<Temperature>(AppKey::Temp, |reg| {
    reg.buffer(BufferCfg::SingleLatest)
       .persist("sensor.temperature");  // ← stored to SQLite
});

let db = builder.build().await?;

// Query historical data
let last_100 = db.query_latest::<Temperature>("sensor.*", Some(100)).await;
let this_week = db.query_range::<Temperature>(
    "sensor.*",
    week_start_ms,
    week_end_ms,
    None,  // no per-record limit
).await;

aimdb-persistence features:

  • PersistenceBackend trait — implement your own storage
  • Automatic retention cleanup task (24-hour interval)
  • query_latest, query_range, query_raw APIs

aimdb-persistence-sqlite features:

  • WAL journal mode for concurrent reads
  • Window-function queries for efficient top-N per record
  • * wildcard pattern matching
  • Actor-model writer thread; Clone = O(1) handle copy

🌐 Dynamic Topic/Address Routing (Design 018)

Resolve MQTT topics or KNX group addresses at runtime based on data values!

// Outbound: per-message topic from payload
builder.configure::<SensorReading>(AppKey::Reading, |reg| {
    reg.link_to("mqtt://broker/sensors/default")
       .with_topic_provider(|reading: &SensorReading| {
           format!("sensors/{}/data", reading.sensor_id)
       });
});

// Inbound: late-binding topic from config/discovery
builder.configure::<Command>(AppKey::Command, |reg| {
    reg.link_from("mqtt://broker/")
       .with_topic_resolver(|| {
           // Called once at connector startup
           format!("commands/{}", load_device_id())
       });
});

Works in both std and no_std + alloc environments.

📡 Record Drain API (Design 019)

Non-blocking batch pull for accumulated history — perfect for LLM analysis!

// Via AimDbClient (remote access)
let response = client.drain_record("sensor.temperature").await?;
println!("Drained {} values", response.count);
// First call is always empty (cold start — creates the reader)
// Subsequent calls return everything since last drain

Cold-start semantics: the first drain call creates a reader and returns empty. Subsequent calls return all values accumulated since the previous drain. This enables stateful batch analysis without missing data.


💥 Breaking Changes

1. .with_serialization().with_remote_access()

// Before (v0.4.x)
reg.buffer(...).with_serialization();

// After (v0.5.0)
reg.buffer(...).with_remote_access();

2. RecordId::new() requires RecordOrigin

This affects custom buffer/record implementations only:

// Before (v0.4.x)
RecordId::new(type_id, idx)

// After (v0.5.0)
RecordId::new(type_id, idx, RecordOrigin::Source)

3. MCP subscription tools removed

The subscribe_record, unsubscribe_record, list_subscriptions, and get_notification_directory MCP tools have been replaced by drain_record. Update any LLM prompts or MCP tool configurations accordingly.


📦 Published Crates

New Crates

  • 🆕 aimdb-persistence@0.1.0 — pluggable persistence layer
  • 🆕 aimdb-persistence-sqlite@0.1.0 — SQLite backend for persistence

Updated

Crate Version
aimdb-core 0.5.0
aimdb-tokio-adapter 0.5.0
aimdb-embassy-adapter 0.5.0
aimdb-client 0.5.0
aimdb-sync 0.5.0
aimdb-mqtt-connector 0.5.0
aimdb-knx-connector 0.3.0
aimdb-cli 0.5.0
aimdb-mcp 0.5.0

Unchanged

  • aimdb-derive@0.1.0
  • aimdb-executor@0.1.0

🔧 New MCP Tools

The aimdb-mcp server now provides these tools:

Tool Description
discover_instances Find running AimDB servers
list_records List all records with metadata
get_record Get current value
set_record Set writable record value
query_schema Infer JSON Schema from value
get_instance_info Server version and capabilities
drain_record Batch pull accumulated history
graph_nodes All graph nodes with origin/buffer info
graph_edges Directed data-flow edges
graph_topo_order Topological record ordering

🖥️ New CLI Commands (aimdb graph)

# List all graph nodes (color-coded by origin)
aimdb graph nodes

# Show directed data-flow edges
aimdb graph edges

# Show topological (spawn) order
aimdb graph order

# Export to Graphviz DOT format
aimdb graph dot > pipeline.dot
dot -Tsvg pipeline.dot -o pipeline.svg

🚀 Migration Guide

Step 1: Update dependencies

[dependencies]
aimdb-core = "0.5.0"
aimdb-tokio-adapter = "0.5.0"
# Optional: add persistence
aimdb-persistence = "0.1.0"
aimdb-persistence-sqlite = "0.1.0"

Step 2: Rename .with_serialization().with_remote_access()

# Quick find & replace
grep -rn "with_serialization" src/
# Replace with:
#   .with_remote_access()

Step 3: Update RecordId::new() calls (custom buffer implementations only)

// Add RecordOrigin argument
RecordId::new(type_id, idx, RecordOrigin::Source)

Step 4: Update MCP tool usage

Replace subscribe_record / unsubscribe_record workflows with drain_record polling:

# Old: subscribe → wait → unsubscribe
# New: call drain_record periodically (first call always empty)
drain_record(socket_path: "...", record_name: "sensor.*")

📚 Examples

All examples updated for v0.5.0:

git clone https://github.com/aimdb-dev/aimdb.git && cd aimdb

# MQTT connector demo
cargo run -p tokio-mqtt-connector-demo

# KNX connector demo
cargo run -p tokio-knx-connector-demo

# Sync API demo
cargo run -p sync-api-demo

# Weather mesh demo (multi-station)
cargo run -p weather-hub

# Embedded (cross-compile)
cd examples/embassy-mqtt-connector-demo
cargo build --release --target thumbv7em-none-eabihf

🤝 Contributing

git clone https://github.com/aimdb-dev/aimdb.git
cd aimdb
make check  # fmt + clippy + test + embedded cross-compile

📄 License

Apache License 2.0 — see LICENSE.