at protocol indexer with flexible filtering, xrpc queries, and a cursor-backed event stream, built on fjall
at-protocol atproto indexer rust fjall
at 4fe44f8a28620861fb772d40d55fbeb65716e5df 67 lines 2.0 kB view raw
1use serde::{Deserialize, Serialize}; 2use smol_str::SmolStr; 3use std::sync::Arc; 4 5pub type FilterHandle = Arc<arc_swap::ArcSwap<FilterConfig>>; 6 7pub fn new_handle(config: FilterConfig) -> FilterHandle { 8 Arc::new(arc_swap::ArcSwap::new(Arc::new(config))) 9} 10 11/// apply a bool patch or set replacement for a single set update. 12#[derive(Debug, Clone, Serialize, Deserialize)] 13#[serde(untagged)] 14pub enum SetUpdate { 15 /// replace the entire set with this list 16 Set(Vec<String>), 17 /// patch: true = add, false = remove 18 Patch(std::collections::HashMap<String, bool>), 19} 20 21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 22#[serde(rename_all = "snake_case")] 23pub enum FilterMode { 24 Filter = 0, 25 Full = 2, 26} 27 28/// hot-path in-memory config: only the small fields needed on every event. 29/// dids and excludes are large sets kept in the filter keyspace only. 30#[derive(Debug, Clone, Serialize)] 31pub struct FilterConfig { 32 pub mode: FilterMode, 33 pub signals: Vec<SmolStr>, 34 pub collections: Vec<SmolStr>, 35} 36 37impl FilterConfig { 38 pub fn new(mode: FilterMode) -> Self { 39 Self { 40 mode, 41 signals: Vec::new(), 42 collections: Vec::new(), 43 } 44 } 45 46 /// returns true if the collection matches the content filter. 47 /// if collections is empty, all collections match. 48 pub fn matches_collection(&self, collection: &str) -> bool { 49 if self.collections.is_empty() { 50 return true; 51 } 52 self.collections.iter().any(|p| nsid_matches(p, collection)) 53 } 54 55 /// returns true if the commit touches a collection covered by a signal. 56 pub fn matches_signal(&self, collection: &str) -> bool { 57 self.signals.iter().any(|p| nsid_matches(p, collection)) 58 } 59} 60 61fn nsid_matches(pattern: &str, collection: &str) -> bool { 62 if let Some(prefix) = pattern.strip_suffix(".*") { 63 collection == prefix || collection.starts_with(prefix) 64 } else { 65 collection == pattern 66 } 67}