at protocol indexer with flexible filtering, xrpc queries, and a cursor-backed event stream, built on fjall
at-protocol
atproto
indexer
rust
fjall
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}