//! Generic read/write for [`crate::models::game_ext`] opaque state. //! //! Core never interprets the payload. Writes happen INSIDE the owning entity's //! transaction (see `squad::replace_squad_with_extension`) so the canonical //! entity and its opaque extension commit atomically — there is deliberately no //! standalone "write extension" entry point that could desync the two. use crate::db::Pool; use crate::error::AppResult; use crate::models::game_ext::GameEntityExt; /// Fetch the stored opaque extension for a scoped entity, or `None`. The caller /// compares `canonical_fingerprint` against the entity's *current* fingerprint to /// decide freshness — this layer does not know how to fingerprint any entity. pub async fn get_ext( pool: &Pool, game_id: &str, entity_kind: &str, entity_id: &str, namespace: &str, ) -> AppResult> { let row = sqlx::query_as::<_, GameEntityExt>( "SELECT game_id, entity_kind, entity_id, namespace, schema_version, \ canonical_fingerprint, payload, updated_at FROM game_entity_ext \ WHERE game_id = ? AND entity_kind = ? AND entity_id = ? AND namespace = ?", ) .bind(game_id) .bind(entity_kind) .bind(entity_id) .bind(namespace) .fetch_optional(pool) .await?; Ok(row) }