frame-state 0.2.0

Content-addressed state layer — haematite integration, entities, branching, cross-component references
Documentation
use frame_core::component::ComponentId;
use haematite::CustomMergeFn;

use crate::error::StateError;
use crate::store::ComponentStateStore;
use crate::types::{ComponentSchema, MergePolicy, MetaState};

impl ComponentStateStore {
    /// Registers a named custom resolver available to schema declaration.
    ///
    /// Re-registering a name is refused so a durable schema cannot silently
    /// change behavior during one process lifetime. The embedding host installs
    /// the same function under the same name on every process boot.
    ///
    /// # Errors
    ///
    /// Returns a typed duplicate-name or synchronization failure.
    pub fn register_custom_resolver(
        &self,
        name: impl Into<String>,
        resolver: CustomMergeFn,
    ) -> Result<(), StateError> {
        let name = name.into();
        let mut resolvers = self
            .shared
            .resolvers
            .lock()
            .map_err(|_| StateError::SynchronizationPoisoned)?;
        if resolvers.contains_key(&name) {
            return Err(StateError::ResolverAlreadyRegistered { name });
        }
        resolvers.insert(name, resolver);
        Ok(())
    }

    /// Declares the policy that every Work merge for the next incarnation uses.
    ///
    /// Declaration rejects policies the published engine cannot execute and
    /// custom names not already registered. Haematite 0.5.0 exposes
    /// `VectorClock` but its resolver returns `Unimplemented`, so it is refused
    /// here at declaration time.
    ///
    /// # Errors
    ///
    /// Returns typed active, duplicate, unsupported-policy, resolver, or
    /// synchronization failures.
    pub fn declare_schema(
        &self,
        component: ComponentId,
        schema: ComponentSchema,
    ) -> Result<(), StateError> {
        if self
            .lock_storage()?
            .meta_record(component)?
            .is_some_and(|record| !matches!(record.state, MetaState::Archived { .. }))
        {
            return Err(StateError::AlreadyActive { component });
        }
        match &schema.merge_policy {
            MergePolicy::Lww => {}
            MergePolicy::VectorClock => {
                return Err(StateError::UnsupportedPolicy {
                    policy: MergePolicy::VectorClock,
                });
            }
            MergePolicy::Custom(name) => {
                if !self
                    .shared
                    .resolvers
                    .lock()
                    .map_err(|_| StateError::SynchronizationPoisoned)?
                    .contains_key(name)
                {
                    return Err(StateError::ResolverNotRegistered { name: name.clone() });
                }
            }
        }
        let mut declarations = self
            .shared
            .declarations
            .lock()
            .map_err(|_| StateError::SynchronizationPoisoned)?;
        if declarations.contains_key(&component) {
            return Err(StateError::SchemaAlreadyDeclared { component });
        }
        declarations.insert(component, schema);
        Ok(())
    }
}