| Crates.io | greentic-state |
| lib.rs | greentic-state |
| version | 0.4.2 |
| created_at | 2025-10-31 12:23:42.895967+00 |
| updated_at | 2026-01-18 12:29:58.713312+00 |
| description | Greentic JSON working-memory store with in-memory and Redis backends |
| homepage | |
| repository | https://github.com/greentic-ai/greentic-state |
| max_upload_size | |
| id | 1909971 |
| size | 137,709 |
Production-grade JSON working-memory store with pluggable backends for Greentic flows. The crate exposes a StateStore trait that supports whole-document operations as well as targeted updates using JSON Pointer paths. Implementations are provided for an in-memory store (suitable for single-node workers and tests) and Redis (for cross-node coordination).
TenantCtx + caller prefix + StateKey, ensuring strict separation between tenants and flow executions.serde_json::Value with optional JSON Pointer paths for partial reads and writes.(tenant, prefix) for clean flow teardowns.redis backend is optional (default feature) and can be disabled for embedded scenarios.#![forbid(unsafe_code)], lazy expiry in-memory, and Lua-assisted atomic upserts on Redis.use greentic_state::{inmemory::InMemoryStateStore, StateKey, StatePath, StateStore, TenantCtx};
use greentic_types::{EnvId, TenantId};
use serde_json::json;
let ctx = TenantCtx::new(
EnvId::try_from("dev").expect("valid env id"),
TenantId::try_from("tenant-123").expect("valid tenant id"),
);
let prefix = "flow/example";
let key = StateKey::new("node/state");
let store = InMemoryStateStore::new();
// Whole-document write with TTL (in seconds)
store.set_json(&ctx, prefix, &key, None, &json!({"status": "ready"}), Some(300))?;
// Partial update via JSON Pointer
let path = StatePath::from_pointer("/status");
store.set_json(&ctx, prefix, &key, Some(&path), &json!("running"), None)?;
let current = store.get_json(&ctx, prefix, &key, None)?;
assert_eq!(current.unwrap(), json!({"status": "running"}));
use greentic_state::redis_store::RedisStateStore;
use redis::Client;
let client = Client::open("redis://127.0.0.1/")?;
let store = RedisStateStore::new(client);
To run Redis locally:
docker run --rm -p 6379:6379 redis:7
export REDIS_URL=redis://127.0.0.1/
Run tests with Redis enabled:
cargo test --all-features
Fully-qualified keys are generated via:
greentic:state:{env}.{tenant_id}[.{team}(.{user})]:{prefix}:{state_key}
Only the generated FQN should be used within backends. StatePath helpers understand a subset of RFC 6901 JSON Pointers (array indices and object keys).
ttl_secs is None, resets the TTL when a value is provided, and clears TTL when ttl_secs == Some(0).set_json with a StatePath performs read-modify-write:
Value::Null by default).Use del_prefix to drop all keys under a namespace:
let removed = store.del_prefix(&ctx, "flow/example")?;
println!("Removed {removed} keys");
Redis uses SCAN + batched DEL, avoiding blocking the server on large keyspaces.
cargo fmt --allcargo clippy --all-targets -- -D warningscargo test --workspace --all-featuresauto-tag.yml: tags crates on version bumps merged to master.publish.yml: fmt/clippy/test + idempotent publish via katyo/publish-crates@v2.Run ci/local_check.sh to mirror the main GitHub Actions pipeline before pushing. It handles fmt/clippy/build/tests (spinning up a disposable Redis via Docker when REDIS_URL is unset), dependency gate checks, and an optional cargo publish --dry-run. Useful toggles:
LOCAL_CHECK_ONLINE=1 – enable networked steps such as the publish dry run.LOCAL_CHECK_STRICT=1 – fail when optional tools are missing instead of skipping.LOCAL_CHECK_VERBOSE=1 – echo each command (set -x).LOCAL_CHECK_BYPASS=1 – skip the git pre-push hook that calls the script.By default the script runs offline and skips checks whose tooling is unavailable, matching CI behavior as closely as possible without requiring secrets.
The crate follows semantic versioning. Publishing is tag-driven and idempotent—rerunning publish on the same version is a no-op. Performance considerations include zero-copy JSON navigation and Redis-side Lua scripts for atomic updates. Contributions should keep shared types in greentic-types and WIT bindings in greentic-interfaces.