WeaveDocs
Woven

Filters and Streams

Query filters for Woven event streams — match by space, kind, author, refs, and time windows.

Purpose

This page covers EventFilter, the predicate used to narrow event reads from EventStream::query, NexusEventFeed::query, and WovenTransportService::query. Read this when building a timeline, a topic feed, a reply tree, or any other view that needs to slice a Strand of signed events.

The filter contract

EventFilter is a builder over five sets and two optional timestamp bounds:

FieldMatches when
spacesevent's SpaceId is in the set (empty = wildcard)
kindsevent's kind is in the set (empty = wildcard)
authorsevent's author string is in the set (empty = wildcard)
referencesevery ref in the set appears in event.refs() (AND semantics)
sinceevent.created_at() >= since
untilevent.created_at() <= until

EventFilter::matches(event) returns true only when every populated field passes. Empty fields are wildcards, so EventFilter::new() matches every signed event.

Building a typed timeline

use woven::{EventFilter, EventStream, SpaceId};

let filter = EventFilter::new()
    .space(SpaceId::new("woven://dsocial/global")?)
    .kind("social.post")
    .author("did:l1fe:agent:alice")
    .since(1_777_000_000);

let strand = strand.lock().await;
let posts = EventStream::query(&strand, &filter).await?;

This returns every social post Alice authored in the global dSocial space since the supplied Unix timestamp.

Walking a reply tree

To follow replies and reactions, include the parent event's EventRef in the filter — Woven returns only events whose refs() contain it:

use woven::{EventFilter, EventRef};

let parent = EventRef::new("woven:event:abc123...")?;
let filter = EventFilter::new()
    .references(parent)
    .kind("social.reply");

let replies = EventStream::query(&strand, &filter).await?;

Subscription semantics

Filters are pure predicates — they do not subscribe to or block on new events. To consume events as they arrive over the network, wire the same filter into a WovenTransportService::handle_swarm_event loop and re-query the local Strand on each accepted payload. The combination gives you a "filter once at load, then incrementally append" pattern without polling.

loop {
    let swarm_event = inbound.recv().await?;
    if let Some(record) = service.handle_swarm_event(swarm_event).await? {
        if filter.matches(&record.event) {
            tx.send(record).await?;
        }
    }
}

Determinism

Two peers running the same filter over the same set of input Strands produce identical match sets: filter evaluation is purely structural, references use BTreeSet ordering, and timestamps are second-resolution integers. There is no language- or platform-dependent ordering.

Primary types to know

  • EventFilter — the builder and predicate
  • EventStream::query — apply a filter to a single Strand
  • NexusEventFeed::query — apply a filter to a merged multi-writer view
  • WovenTransportService::query — apply a filter to the local transport Strand