feat(squad): generic game-scoped opaque extension + server fingerprint, atomic in replace_squad tx
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
//! 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<Option<GameEntityExt>> {
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user