iii

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::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

adapter
Adapter

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

store_method
string

Storage method. Options: in_memory (lost on restart) or file_based (persisted to disk).

file_path
string

Directory path for file-based storage. Each scope is stored as a separate file.

save_interval_ms
number

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

redis_url
string

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

state::set
function

Set a value in state. Fires a state:created trigger if the key did not exist, or state:updated if it did.

state::get
function

Get a value from state.

state::delete
function

Delete a value from state. Fires a state:deleted trigger.

state::update
function

Atomically update a value using one or more operations. Fires state:created or state:updated depending on whether the key existed.

state::list
function

List all values within a scope.

state::list_groups
function

List all scopes that contain state data.

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:

type
string

Always "state".

event_type
string

The kind of change: "state:created", "state:updated", or "state:deleted".

scope
string

The scope where the change occurred.

key
string

The key that changed.

old_value
any

The previous value before the change, or null for newly created keys.

new_value
any

The new value after the change. null for deleted keys.

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"
    }),
})?;

State Flow

On this page