State
Distributed key-value state management with reactive change triggers.
Distributed key-value state storage with scope-based organization and reactive triggers that fire on any state change.
modules::state::StateModuleArchitecture
State is server-side key-value storage with trigger-based reactivity. Unlike streams, state does not push updates to WebSocket clients — it fires triggers that workers handle server-side.
Sample Configuration
- class: modules::state::StateModule
config:
adapter:
class: modules::state::adapters::KvStore
config:
store_method: file_based
file_path: ./data/state_store
save_interval_ms: 5000Configuration
The adapter to use for state persistence and distribution. Defaults to modules::state::adapters::KvStore when not specified.
Adapters
modules::state::adapters::KvStore
Built-in key-value store. Supports both in-memory and file-based persistence.
class: modules::state::adapters::KvStore
config:
store_method: file_based
file_path: ./data/state_store
save_interval_ms: 5000Configuration
Storage method. Options: in_memory (lost on restart) or file_based (persisted to disk).
modules::state::adapters::RedisAdapter
Uses Redis as the state backend.
class: modules::state::adapters::RedisAdapter
config:
redis_url: ${REDIS_URL:redis://localhost:6379}Configuration
modules::state::adapters::Bridge
Forwards state operations to a remote III Engine instance via the Bridge Client.
class: modules::state::adapters::BridgeFunctions
Set a value in state. Fires a state:created trigger if the key did not exist, or state:updated if it did.
Atomically update a value using one or more operations. Fires state:created or state:updated depending on whether the key existed.
Trigger Type
This module adds a new Trigger Type: state.
When a state value is created, updated, or deleted, all registered state triggers are evaluated and fired if they match.
State Event Payload
When the trigger fires, the handler receives a state event object:
Sample Code
const fn = iii.registerFunction(
{ id: 'state::onUserUpdated' },
async (event) => {
console.log('State changed:', event.event_type, event.key)
console.log('Previous:', event.old_value)
console.log('Current:', event.new_value)
return {}
},
)
iii.registerTrigger({
type: 'state',
function_id: fn.id,
config: { scope: 'users', key: 'profile' },
})def on_user_updated(event):
print('State changed:', event['event_type'], event['key'])
print('Previous:', event.get('old_value'))
print('Current:', event.get('new_value'))
return {}
iii.register_function({'id': 'state::onUserUpdated'}, on_user_updated)
iii.register_trigger({'type': 'state', 'function_id': 'state::onUserUpdated', 'config': {'scope': 'users', 'key': 'profile'}})use iii_sdk::RegisterFunctionMessage;
iii.register_function(
RegisterFunctionMessage {
id: "state::onUserUpdated".into(),
..Default::default()
},
|event| async move {
println!("State changed: {} {}", event["event_type"], event["key"]);
println!("Previous: {:?}", event.get("old_value"));
println!("Current: {:?}", event.get("new_value"));
Ok(json!({}))
},
);
iii.register_trigger(RegisterTriggerInput {
trigger_type: "state".into(),
function_id: "state::onUserUpdated".into(),
config: json!({
"scope": "users",
"key": "profile"
}),
})?;Usage Example: User Profile with Reactive Sync
Store user profiles in state and react when they change:
await iii.trigger({
function_id: 'state::set',
payload: {
scope: 'users',
key: 'user-123',
value: { name: 'Alice', email: 'alice@example.com', preferences: { theme: 'dark' } },
},
action: TriggerAction.Void(),
})
const profile = await iii.trigger({
function_id: 'state::get',
payload: { scope: 'users', key: 'user-123' },
})
await iii.trigger({
function_id: 'state::set',
payload: {
scope: 'users',
key: 'user-123',
value: { name: 'Alice', email: 'alice@example.com', preferences: { theme: 'light' } },
},
action: TriggerAction.Void(),
})
const allUsers = await iii.trigger({
function_id: 'state::list',
payload: { scope: 'users' },
})
const scopes = await iii.trigger({
function_id: 'state::list_groups',
payload: {},
})iii.trigger({
'function_id': 'state::set',
'payload': {
'scope': 'users',
'key': 'user-123',
'value': {'name': 'Alice', 'email': 'alice@example.com', 'preferences': {'theme': 'dark'}},
},
'action': {'type': 'void'},
})
profile = iii.trigger({
'function_id': 'state::get',
'payload': {'scope': 'users', 'key': 'user-123'},
})
iii.trigger({
'function_id': 'state::set',
'payload': {
'scope': 'users',
'key': 'user-123',
'value': {'name': 'Alice', 'email': 'alice@example.com', 'preferences': {'theme': 'light'}},
},
'action': {'type': 'void'},
})
all_users = iii.trigger({
'function_id': 'state::list',
'payload': {'scope': 'users'},
})
scopes = iii.trigger({
'function_id': 'state::list_groups',
'payload': {},
})use iii_sdk::{TriggerRequest, TriggerAction};
use serde_json::json;
iii.trigger(TriggerRequest {
function_id: "state::set".into(),
payload: json!({
"scope": "users",
"key": "user-123",
"value": { "name": "Alice", "email": "alice@example.com", "preferences": { "theme": "dark" } }
}),
action: Some(TriggerAction::Void),
timeout_ms: None,
}).await?;
let profile = iii.trigger(TriggerRequest {
function_id: "state::get".into(),
payload: json!({ "scope": "users", "key": "user-123" }),
action: None,
timeout_ms: None,
}).await?;
iii.trigger(TriggerRequest {
function_id: "state::set".into(),
payload: json!({
"scope": "users",
"key": "user-123",
"value": { "name": "Alice", "email": "alice@example.com", "preferences": { "theme": "light" } }
}),
action: Some(TriggerAction::Void),
timeout_ms: None,
}).await?;
let all_users = iii.trigger(TriggerRequest {
function_id: "state::list".into(),
payload: json!({ "scope": "users" }),
action: None,
timeout_ms: None,
}).await?;
let scopes = iii.trigger(TriggerRequest {
function_id: "state::list_groups".into(),
payload: json!({}),
action: None,
timeout_ms: None,
}).await?;Usage Example: Conditional Trigger
Only process profile updates when the email field changed:
const conditionFn = iii.registerFunction(
{ id: 'conditions::emailChanged' },
async (event) =>
event.event_type === 'state:updated' &&
event.old_value?.email !== event.new_value?.email,
)
const fn = iii.registerFunction({ id: 'state::onEmailChange' }, async (event) => {
await sendVerificationEmail(event.new_value.email)
return {}
})
iii.registerTrigger({
type: 'state',
function_id: fn.id,
config: {
scope: 'users',
key: 'profile',
condition_function_id: conditionFn.id,
},
})def email_changed(event):
if event.get('event_type') != 'state:updated':
return False
old = event.get('old_value', {})
new = event.get('new_value', {})
return old.get('email') != new.get('email')
iii.register_function({'id': 'conditions::emailChanged'}, email_changed)
def on_email_change(event):
send_verification_email(event['new_value']['email'])
return {}
iii.register_function({'id': 'state::onEmailChange'}, on_email_change)
iii.register_trigger({
'type': 'state',
'function_id': 'state::onEmailChange',
'config': {
'scope': 'users',
'key': 'profile',
'condition_function_id': 'conditions::emailChanged',
},
})use iii_sdk::{RegisterFunctionMessage, RegisterTriggerInput};
use serde_json::json;
iii.register_function(
RegisterFunctionMessage {
id: "conditions::emailChanged".into(),
..Default::default()
},
|event| async move {
let is_update = event["event_type"].as_str() == Some("state:updated");
let old_email = event.get("old_value").and_then(|v| v.get("email"));
let new_email = event.get("new_value").and_then(|v| v.get("email"));
Ok(json!(is_update && old_email != new_email))
},
);
iii.register_function(
RegisterFunctionMessage {
id: "state::onEmailChange".into(),
..Default::default()
},
|event| async move {
let email = event["new_value"]["email"].as_str().unwrap_or("");
send_verification_email(email).await?;
Ok(json!({}))
},
);
iii.register_trigger(RegisterTriggerInput {
trigger_type: "state".into(),
function_id: "state::onEmailChange".into(),
config: json!({
"scope": "users",
"key": "profile",
"condition_function_id": "conditions::emailChanged"
}),
})?;