···347347348348 query_builder = query_builder.bind(limit as i64);
349349350350- let records = query_builder.fetch_all(&self.pool).await?;
350350+ let mut records = query_builder.fetch_all(&self.pool).await?;
351351+352352+ // Deduplicate lexicon records by URI (same URI can exist with different slice_uri values)
353353+ if is_lexicon {
354354+ let mut seen_uris = std::collections::HashSet::new();
355355+ records.retain(|record| seen_uris.insert(record.uri.clone()));
356356+ }
351357352358 // Only return cursor if we got a full page, indicating there might be more
353359 let cursor = if records.len() < limit as usize {
+15-1
api/src/graphql/schema_builder.rs
···2929 slice_uri: String,
3030) -> Result<Schema, String> {
3131 // Fetch all lexicons for this slice
3232- let lexicons = database
3232+ let all_lexicons = database
3333 .get_lexicons_by_slice(&slice_uri)
3434 .await
3535 .map_err(|e| format!("Failed to load lexicons: {}", e))?;
3636+3737+ // Deduplicate by NSID for schema building (keep most recent due to ORDER BY indexed_at DESC)
3838+ // This prevents duplicate type registration errors without hiding duplicates from users
3939+ let mut seen_nsids = std::collections::HashSet::new();
4040+ let lexicons: Vec<serde_json::Value> = all_lexicons
4141+ .into_iter()
4242+ .filter(|lexicon| {
4343+ if let Some(nsid) = lexicon.get("id").and_then(|n| n.as_str()) {
4444+ seen_nsids.insert(nsid.to_string())
4545+ } else {
4646+ true // Keep lexicons without an ID (will fail validation later)
4747+ }
4848+ })
4949+ .collect();
36503751 // Build Query root type and collect all object types
3852 let mut query = Object::new("Query");