Highly ambitious ATProtocol AppView service and sdks

fix duplicate lexicons in read, dedupe nsids in gql builder

Changed files
+22 -2
api
src
database
graphql
+7 -1
api/src/database/records.rs
··· 347 347 348 348 query_builder = query_builder.bind(limit as i64); 349 349 350 - let records = query_builder.fetch_all(&self.pool).await?; 350 + let mut records = query_builder.fetch_all(&self.pool).await?; 351 + 352 + // Deduplicate lexicon records by URI (same URI can exist with different slice_uri values) 353 + if is_lexicon { 354 + let mut seen_uris = std::collections::HashSet::new(); 355 + records.retain(|record| seen_uris.insert(record.uri.clone())); 356 + } 351 357 352 358 // Only return cursor if we got a full page, indicating there might be more 353 359 let cursor = if records.len() < limit as usize {
+15 -1
api/src/graphql/schema_builder.rs
··· 29 29 slice_uri: String, 30 30 ) -> Result<Schema, String> { 31 31 // Fetch all lexicons for this slice 32 - let lexicons = database 32 + let all_lexicons = database 33 33 .get_lexicons_by_slice(&slice_uri) 34 34 .await 35 35 .map_err(|e| format!("Failed to load lexicons: {}", e))?; 36 + 37 + // Deduplicate by NSID for schema building (keep most recent due to ORDER BY indexed_at DESC) 38 + // This prevents duplicate type registration errors without hiding duplicates from users 39 + let mut seen_nsids = std::collections::HashSet::new(); 40 + let lexicons: Vec<serde_json::Value> = all_lexicons 41 + .into_iter() 42 + .filter(|lexicon| { 43 + if let Some(nsid) = lexicon.get("id").and_then(|n| n.as_str()) { 44 + seen_nsids.insert(nsid.to_string()) 45 + } else { 46 + true // Keep lexicons without an ID (will fail validation later) 47 + } 48 + }) 49 + .collect(); 36 50 37 51 // Build Query root type and collect all object types 38 52 let mut query = Object::new("Query");