···250250| feature | default | description |
251251| :--- | :--- | :--- |
252252| `indexer` | yes | makes hydrant act as an indexer. incompatible with the relay feature. |
253253+| `indexer_stream` | yes | enables the event stream for the indexer. requires indexer feature. |
253254| `relay` | no | makes hydrant act as a relay. incompatible with the indexer feature. |
254255| `backlinks` | no | enables the backlinks indexer and XRPC endpoints (`blue.microcosm.links.*`). requires indexer feature. |
255256
+33-14
src/api/debug.rs
···11use crate::api::AppState;
22use crate::db::keys;
33-use crate::types::{RepoState, ResyncState, StoredEvent};
33+#[cfg(feature = "indexer_stream")]
44+use crate::types::StoredEvent;
55+use crate::types::{RepoState, ResyncState};
46use axum::routing::{get, post};
57use axum::{
68 Json,
···3234 let r = axum::Router::new()
3335 .route("/debug/get", get(handle_debug_get))
3436 .route("/debug/iter", get(handle_debug_iter))
3535- .route("/debug/compact", post(handle_debug_compact))
3737+ .route("/debug/compact", post(handle_debug_compact));
3838+3939+ #[cfg(any(feature = "indexer_stream", feature = "relay"))]
4040+ let r = r
3641 .route(
3742 "/debug/ephemeral_ttl_tick",
3843 post(handle_debug_ephemeral_ttl_tick),
···100105 return serde_json::to_value(state).unwrap_or(Value::Null);
101106 }
102107 }
108108+ #[cfg(feature = "indexer_stream")]
103109 "events" => {
104110 if let Ok(event) = rmp_serde::from_slice::<StoredEvent>(value) {
105111 return serde_json::to_value(event).unwrap_or(Value::Null);
···279285 "pending" => Ok(db.pending.clone()),
280286 #[cfg(feature = "indexer")]
281287 "resync" => Ok(db.resync.clone()),
282282- #[cfg(feature = "indexer")]
288288+ #[cfg(feature = "indexer_stream")]
283289 "events" => Ok(db.events.clone()),
284290 #[cfg(feature = "indexer")]
285291 "records" => Ok(db.records.clone()),
···312318 Ok(StatusCode::OK)
313319}
314320321321+#[cfg(any(feature = "indexer_stream", feature = "relay"))]
315322pub async fn handle_debug_ephemeral_ttl_tick(
316323 State(state): State<Arc<AppState>>,
317324) -> Result<StatusCode, StatusCode> {
318318- tokio::task::spawn_blocking(move || {
319319- #[cfg(feature = "indexer")]
320320- let res = crate::db::ephemeral::ephemeral_ttl_tick(&state.db, &state.ephemeral_ttl);
325325+ tokio::task::spawn_blocking(move || -> miette::Result<()> {
326326+ #[cfg(feature = "indexer_stream")]
327327+ crate::db::ephemeral::ephemeral_ttl_tick(&state.db, &state.ephemeral_ttl)?;
321328 #[cfg(feature = "relay")]
322322- let res = crate::db::ephemeral::relay_events_ttl_tick(&state.db, &state.ephemeral_ttl);
323323- res
329329+ crate::db::ephemeral::relay_events_ttl_tick(&state.db, &state.ephemeral_ttl)?;
330330+ Ok(())
324331 })
325332 .await
326333 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
···330337}
331338332339#[derive(Deserialize)]
340340+#[cfg(any(feature = "indexer_stream", feature = "relay"))]
333341pub struct DebugSeedWatermarkRequest {
334342 /// unix timestamp (seconds) to write the watermark at
335343 pub ts: u64,
···340348/// writes an event watermark entry directly to the cursors keyspace, using identical
341349/// key/value encoding to the real TTL worker. used in tests to plant a past watermark
342350/// so the real `ephemeral_ttl_tick` code path is exercised without waiting 3600 seconds.
351351+#[cfg(any(feature = "indexer_stream", feature = "relay"))]
343352pub async fn handle_debug_seed_watermark(
344353 State(state): State<Arc<AppState>>,
345354 Query(req): Query<DebugSeedWatermarkRequest>,
346355) -> Result<StatusCode, StatusCode> {
347347- tokio::task::spawn_blocking(move || {
348348- #[cfg(feature = "indexer")]
349349- let key = crate::db::keys::event_watermark_key(req.ts);
356356+ tokio::task::spawn_blocking(move || -> Result<(), StatusCode> {
357357+ #[cfg(feature = "indexer_stream")]
358358+ state
359359+ .db
360360+ .cursors
361361+ .insert(
362362+ crate::db::keys::event_watermark_key(req.ts),
363363+ req.event_id.to_be_bytes(),
364364+ )
365365+ .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
350366 #[cfg(feature = "relay")]
351351- let key = crate::db::keys::relay_event_watermark_key(req.ts);
352367 state
353368 .db
354369 .cursors
355355- .insert(key, req.event_id.to_be_bytes())
356356- .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
370370+ .insert(
371371+ crate::db::keys::relay_event_watermark_key(req.ts),
372372+ req.event_id.to_be_bytes(),
373373+ )
374374+ .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
375375+ Ok(())
357376 })
358377 .await
359378 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)??;
···11use super::*;
2233+#[cfg(feature = "indexer_stream")]
34/// a stream of [`Event`]s. returned by [`Hydrant::subscribe`].
45///
56/// implements [`futures::Stream`] and can be used with `StreamExt::next`,
···78/// the stream terminates when the underlying channel closes (i.e. hydrant shuts down).
89pub struct EventStream(mpsc::Receiver<Event>);
9101111+#[cfg(feature = "indexer_stream")]
1012impl Stream for EventStream {
1113 type Item = Event;
1214···4244 }
4345}
44464747+#[cfg(feature = "indexer_stream")]
4548impl Hydrant {
4649 /// subscribe to the ordered event stream.
4750 ///
+13-6
src/control/mod.rs
···4848use crate::ingest::indexer::FirehoseWorker;
4949use crate::pds_meta::{PdsMeta, PdsMetaHandle};
5050use crate::state::AppState;
5151+#[cfg(feature = "indexer_stream")]
5152use crate::types::MarshallableEvt;
5252-5353use firehose::FirehoseShared;
5454-#[cfg(feature = "indexer")]
5454+#[cfg(feature = "indexer_stream")]
5555use stream::event_stream_thread;
5656#[cfg(feature = "relay")]
5757use stream::relay_stream_thread;
···7777/// - `"account"`: a repo's active/inactive status changed. carries an [`AccountEvt`]. ephemeral, not replayable.
7878///
7979/// the `id` field is a monotonically increasing sequence number usable as a cursor for [`Hydrant::subscribe`].
8080+#[cfg(feature = "indexer_stream")]
8081pub type Event = MarshallableEvt<'static>;
81828283/// the top-level handle to a hydrant instance.
···294295 }
295296296297 // 7. ephemeral GC thread (not used in relay mode)
297297- #[cfg(feature = "indexer")]
298298+ #[cfg(feature = "indexer_stream")]
298299 if config.ephemeral {
299300 let state = state.clone();
300301 std::thread::Builder::new()
···344345 });
345346346347 // 9. events/sec stats ticker
348348+ #[cfg(any(feature = "indexer_stream", feature = "relay"))]
347349 tokio::spawn({
348350 let state = state.clone();
349351 let get_id = |state: &AppState| {
350350- #[cfg(feature = "indexer")]
352352+ #[cfg(feature = "indexer_stream")]
351353 let id = state.db.next_event_id.load(Ordering::Relaxed);
352354 #[cfg(feature = "relay")]
353355 let id = state.db.next_relay_seq.load(Ordering::Relaxed);
···681683 count_keys.push("resync");
682684 }
683685686686+ #[cfg_attr(
687687+ not(any(feature = "indexer_stream", feature = "relay")),
688688+ allow(unused_mut)
689689+ )]
684690 let mut counts: BTreeMap<&'static str, u64> =
685691 futures::future::join_all(count_keys.into_iter().map(|name| {
686692 let state = state.clone();
···690696 .into_iter()
691697 .collect();
692698693693- #[cfg(feature = "indexer")]
699699+ #[cfg(feature = "indexer_stream")]
694700 counts.insert("events", state.db.events.approximate_len() as u64);
695701696702 #[cfg(feature = "relay")]
···714720 s.insert("pending", state.db.pending.disk_space());
715721 s.insert("resync", state.db.resync.disk_space());
716722 s.insert("resync_buffer", state.db.resync_buffer.disk_space());
717717- s.insert("events", state.db.events.disk_space());
718723 }
724724+ #[cfg(feature = "indexer_stream")]
725725+ s.insert("events", state.db.events.disk_space());
719726720727 #[cfg(feature = "relay")]
721728 s.insert("relay_events", state.db.relay_events.disk_space());