Highly ambitious ATProtocol AppView service and sdks

fix lexicon record jetstream index logic:

Now network.slices.lexicon records will:
1. Be treated as primary collections for the target slice
2. Get indexed without requiring the DID to be an existing actor
3. Not create an actor entry (since lexicons are metadata, not user activity)

Changed files
+36 -28
api
+36 -28
api/src/jetstream.rs
··· 294 294 if collections.contains(&commit.collection) { 295 295 // Special handling for network.slices.lexicon records 296 296 // These should only be indexed to the slice specified in their JSON data 297 - if commit.collection == "network.slices.lexicon" { 297 + let is_lexicon_for_this_slice = if commit.collection == "network.slices.lexicon" { 298 298 if let Some(target_slice_uri) = 299 299 commit.record.get("slice").and_then(|v| v.as_str()) 300 300 { ··· 302 302 if slice_uri != target_slice_uri { 303 303 continue; 304 304 } 305 + true // This is a lexicon record for this specific slice 305 306 } else { 306 307 // No target slice specified, skip this lexicon record entirely 307 308 continue; 308 309 } 309 - } 310 + } else { 311 + false 312 + }; 313 + 310 314 // Get the domain for this slice (with caching) 311 315 let domain = match self.get_slice_domain(&slice_uri).await { 312 316 Ok(Some(domain)) => domain, ··· 318 322 }; 319 323 320 324 // Check if this is a primary collection (starts with slice domain) 321 - let is_primary_collection = commit.collection.starts_with(&domain); 325 + // Lexicon records for this slice are always treated as primary 326 + let is_primary_collection = commit.collection.starts_with(&domain) || is_lexicon_for_this_slice; 322 327 323 328 // For external collections, check actor status BEFORE expensive validation 324 329 if !is_primary_collection { ··· 401 406 commit.collection, slice_uri, domain 402 407 ); 403 408 404 - // Ensure actor exists for primary collections 405 - let is_cached = 406 - matches!(self.is_actor_cached(did, &slice_uri).await, Ok(Some(_))); 409 + // Ensure actor exists for primary collections (except lexicons) 410 + // Lexicons don't create actors - they just get indexed 411 + if !is_lexicon_for_this_slice { 412 + let is_cached = 413 + matches!(self.is_actor_cached(did, &slice_uri).await, Ok(Some(_))); 407 414 408 - if !is_cached { 409 - // Actor not in cache - create it 410 - info!("Creating new actor {} for slice {}", did, slice_uri); 415 + if !is_cached { 416 + // Actor not in cache - create it 417 + info!("Creating new actor {} for slice {}", did, slice_uri); 411 418 412 - // Resolve actor data (handle, PDS) 413 - match resolve_actor_data(&self.http_client, did).await { 414 - Ok(actor_data) => { 415 - let actor = Actor { 416 - did: actor_data.did.clone(), 417 - handle: actor_data.handle, 418 - slice_uri: slice_uri.clone(), 419 - indexed_at: Utc::now().to_rfc3339(), 420 - }; 419 + // Resolve actor data (handle, PDS) 420 + match resolve_actor_data(&self.http_client, did).await { 421 + Ok(actor_data) => { 422 + let actor = Actor { 423 + did: actor_data.did.clone(), 424 + handle: actor_data.handle, 425 + slice_uri: slice_uri.clone(), 426 + indexed_at: Utc::now().to_rfc3339(), 427 + }; 421 428 422 - // Insert into database 423 - if let Err(e) = self.database.batch_insert_actors(&[actor]).await { 424 - error!("Failed to create actor {}: {}", did, e); 425 - } else { 426 - // Add to cache after successful database insert 427 - self.cache_actor_exists(did, &slice_uri).await; 428 - info!("Created actor {} for slice {}", did, slice_uri); 429 + // Insert into database 430 + if let Err(e) = self.database.batch_insert_actors(&[actor]).await { 431 + error!("Failed to create actor {}: {}", did, e); 432 + } else { 433 + // Add to cache after successful database insert 434 + self.cache_actor_exists(did, &slice_uri).await; 435 + info!("Created actor {} for slice {}", did, slice_uri); 436 + } 429 437 } 430 - } 431 - Err(e) => { 432 - error!("Failed to resolve actor data for {}: {}", did, e); 438 + Err(e) => { 439 + error!("Failed to resolve actor data for {}: {}", did, e); 440 + } 433 441 } 434 442 } 435 443 }