Distributed key-value state storage with scope-based organization and reactive triggers that fire on any state change.
modules::state::StateModule
Architecture
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 : 5000
Configuration
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 : 5000
Configuration
Storage method. Options: in_memory (lost on restart) or file_based (persisted to disk).
Directory path for file-based storage. Each scope is stored as a separate file.
Interval in milliseconds between automatic disk saves. Defaults to 5000.
modules::state::adapters::RedisAdapter
Uses Redis as the state backend.
class : modules::state::adapters::RedisAdapter
config :
redis_url : ${REDIS_URL:redis://localhost:6379}
Configuration
The URL of the Redis instance to use.
modules::state::adapters::Bridge
Forwards state operations to a remote III Engine instance via the Bridge Client.
class : modules::state::adapters::Bridge
Functions
Set a value in state. Fires a state:created trigger if the key did not exist, or state:updated if it did.
The scope (namespace) to organize state within.
The key to store the value under.
The value to store. Can be any JSON-serializable value. Also accepted as data (backward-compatible alias).
The previous value, or null if the key did not exist.
The value that was stored.
Get a value from state.
The stored value, or null if the key does not exist.
Delete a value from state. Fires a state:deleted trigger.
The scope to delete from.
The deleted value, or null if the key did not exist.
Atomically update a value using one or more operations. Fires state:created or state:updated depending on whether the key existed.
The scope to update within.
Array of update operations applied in order. Each operation is an object with a single key: Operation Shape Description set{ "set": value }Replace the current value entirely. merge{ "merge": object }Shallow-merge an object into the current value. increment{ "increment": { "field": string, "by": number } }Add by to a numeric field. decrement{ "decrement": { "field": string, "by": number } }Subtract by from a numeric field. remove{ "remove": field }Remove a field from the current object.
The value before the operations were applied, or null if the key did not exist.
The value after all operations were applied.
List all values within a scope.
The scope to list entries from.
A flat JSON array of all stored values within the scope: any[].
List all scopes that contain state data.
An object with a single groups field: A sorted, deduplicated array of all scope names that contain at least one key.
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.
Only fire for state changes within this scope. When omitted, fires for all scopes.
Only fire for state changes to this specific key. When omitted, fires for all keys.
Function ID for conditional execution. The engine invokes it with the state event; if it returns false, the handler function is not called.
State Event Payload
When the trigger fires, the handler receives a state event object:
The kind of change: "state:created", "state:updated", or "state:deleted".
The scope where the change occurred.
The previous value before the change, or null for newly created keys.
The new value after the change. null for deleted keys.
Sample Code
Node / TypeScript
Python
Rust
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 :: with_id ( "state::onUserUpdated" . into ()),
| 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"
}),
metadata : None ,
}) ? ;
Usage Example: User Profile with Reactive Sync
Store user profiles in state and react when they change:
Node / TypeScript
Python
Rust
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:
Node / TypeScript
Python
Rust
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 :: with_id ( "conditions::emailChanged" . into ()),
| 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 :: with_id ( "state::onEmailChange" . into ()),
| 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"
}),
metadata : None ,
}) ? ;
State Flow