fraiseql_core/cache/cascade_metadata.rs
1//! Cascade metadata for mapping mutations to entity types.
2//!
3//! This module builds a mapping from mutation names to the entity types they modify,
4//! extracted from the compiled schema. This enables tracking which entities are affected
5//! by each mutation, critical for entity-level cache invalidation.
6//!
7//! # Architecture
8//!
9//! ```text
10//! Compiled Schema
11//! ┌──────────────────────────────────┐
12//! │ mutations: │
13//! │ - createUser: { return: User } │
14//! │ - updatePost: { return: Post } │
15//! └──────────┬───────────────────────┘
16//! │
17//! ↓ build_from_schema()
18//! ┌──────────────────────────────────┐
19//! │ CascadeMetadata: │
20//! │ "createUser" → "User" │
21//! │ "updatePost" → "Post" │
22//! └──────────────────────────────────┘
23//! ```
24//!
25//! # Examples
26//!
27//! ```rust
28//! use fraiseql_core::cache::cascade_metadata::CascadeMetadata;
29//!
30//! let mut metadata = CascadeMetadata::new();
31//! metadata.add_mutation("createUser", "User");
32//! metadata.add_mutation("updatePost", "Post");
33//!
34//! assert_eq!(metadata.get_entity_type("createUser"), Some("User"));
35//! assert_eq!(metadata.get_entity_type("updatePost"), Some("Post"));
36//! ```
37
38use std::collections::HashMap;
39
40use crate::schema::CompiledSchema;
41
42/// Maps mutation names to the entity types they modify.
43///
44/// Built from compiled schema, this metadata enables determining which entities
45/// are affected by each mutation operation.
46#[derive(Debug, Clone)]
47pub struct CascadeMetadata {
48 /// Mutation name → Entity type mapping
49 ///
50 /// Example: { "createUser": "User", "updatePost": "Post" }
51 mutation_entity_map: HashMap<String, String>,
52
53 /// Entity type → List of mutations affecting it
54 /// Useful for reverse lookups (which mutations affect "User"?)
55 entity_mutations_map: HashMap<String, Vec<String>>,
56}
57
58impl CascadeMetadata {
59 /// Create empty cascade metadata.
60 ///
61 /// Useful when building metadata programmatically or in tests.
62 #[must_use]
63 pub fn new() -> Self {
64 Self {
65 mutation_entity_map: HashMap::new(),
66 entity_mutations_map: HashMap::new(),
67 }
68 }
69
70 /// Add a mutation-to-entity mapping.
71 ///
72 /// # Arguments
73 ///
74 /// * `mutation_name` - Name of the mutation (e.g., "createUser")
75 /// * `entity_type` - Type of entity it modifies (e.g., "User")
76 pub fn add_mutation(&mut self, mutation_name: &str, entity_type: &str) {
77 let mutation_name = mutation_name.to_string();
78 let entity_type = entity_type.to_string();
79
80 self.mutation_entity_map.insert(mutation_name.clone(), entity_type.clone());
81
82 self.entity_mutations_map.entry(entity_type).or_default().push(mutation_name);
83 }
84
85 /// Get the entity type modified by a mutation.
86 ///
87 /// # Arguments
88 ///
89 /// * `mutation_name` - Name of the mutation
90 ///
91 /// # Returns
92 ///
93 /// - `Some(&str)` - Entity type if mutation is known
94 /// - `None` - If mutation is not in schema
95 ///
96 /// # Examples
97 ///
98 /// ```rust
99 /// use fraiseql_core::cache::cascade_metadata::CascadeMetadata;
100 /// let mut metadata = CascadeMetadata::new();
101 /// metadata.add_mutation("createUser", "User");
102 /// let entity = metadata.get_entity_type("createUser");
103 /// assert_eq!(entity, Some("User"));
104 /// ```
105 #[must_use]
106 pub fn get_entity_type(&self, mutation_name: &str) -> Option<&str> {
107 self.mutation_entity_map.get(mutation_name).map(|s| s.as_str())
108 }
109
110 /// Get all mutations affecting a specific entity type.
111 ///
112 /// Useful for finding all caches that might be affected by changes to an entity type.
113 ///
114 /// # Arguments
115 ///
116 /// * `entity_type` - Type of entity to query
117 ///
118 /// # Returns
119 ///
120 /// List of mutation names affecting this entity, or empty list if none
121 #[must_use]
122 pub fn get_mutations_for_entity(&self, entity_type: &str) -> Vec<String> {
123 self.entity_mutations_map.get(entity_type).cloned().unwrap_or_default()
124 }
125
126 /// Get total number of mutation-entity mappings.
127 #[must_use]
128 pub fn count(&self) -> usize {
129 self.mutation_entity_map.len()
130 }
131
132 /// Check if metadata contains a mutation.
133 #[must_use]
134 pub fn contains_mutation(&self, mutation_name: &str) -> bool {
135 self.mutation_entity_map.contains_key(mutation_name)
136 }
137
138 /// Build cascade metadata from a compiled schema.
139 ///
140 /// Extracts all mutations from the schema and maps each to the entity type
141 /// it returns. Called during server initialization to pre-build the mapping
142 /// for use by the cache invalidation system.
143 #[must_use]
144 pub fn from_schema(schema: &CompiledSchema) -> Self {
145 let mut metadata = Self::new();
146 for mutation in &schema.mutations {
147 metadata.add_mutation(&mutation.name, &mutation.return_type);
148 }
149 metadata
150 }
151}
152
153impl Default for CascadeMetadata {
154 fn default() -> Self {
155 Self::new()
156 }
157}