WeaveDocs
Woven

Spaces and Feeds

How Woven spaces scope events and how Nexus-backed feeds merge events from many writers into one ordered view.

Purpose

This page covers two related concepts: SpaceId (the woven://<app>/<scope> namespace each event belongs to) and NexusEventFeed (the multi-writer feed that projects events from many single-writer Strands into a single time-ordered view). Read this when designing app scopes (woven://dsocial/global vs woven://chat/general) or when assembling a social-feed-style view across peers.

Spaces

A SpaceId is a validated string that must start with woven:// and have a non-empty suffix. Spaces are the unit of scoping that filters, transports, and feeds all key on. Two events with the same body but different spaces have different deterministic IDs and travel on different swarm topics.

use woven::SpaceId;

let dsocial = SpaceId::new("woven://dsocial/global")?;
let chat = SpaceId::new("woven://chat/general")?;
assert_ne!(dsocial.as_str(), chat.as_str());

Pick space identifiers that are stable for the lifetime of the app — renaming a space orphans every event published under the old name.

Single-writer storage

Each author writes their own signed events into their own Strand using EventStream::append. The Strand owns durability and replication; Woven owns the event envelope.

use std::sync::Arc;
use tokio::sync::Mutex;
use woven::{EventBuilder, EventStream, SpaceId};

let strand = Arc::new(Mutex::new(strand::Strand::new_in_memory()));
let space = SpaceId::new("woven://dsocial/global")?;
let event = EventBuilder::new("did:l1fe:agent:alice", space, "social.post")
    .body(serde_json::json!({ "text": "first post" }))
    .created_at(1_777_130_000)
    .sign(&alice_keypair)?;

let mut s = strand.lock().await;
let record = EventStream::append(&mut s, &event).await?;
println!("appended at strand seq {}", record.seq);

Multi-writer feeds with Nexus

NexusEventFeed wraps a nexus::Nexus view that merges many input Strands using a timestamp ordering policy. When you query the feed, Nexus produces an ordered stream of entries, Woven decodes each one back into an Event, verifies its signature against the embedded public key, and returns an AggregatedEventRecord carrying the source writer key and source sequence.

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

let feed = NexusEventFeed::new("did:l1fe:viewer:home").await?;
feed.add_strand(alice_strand).await?;
feed.add_strand(bob_strand).await?;

let filter = EventFilter::new()
    .space(SpaceId::new("woven://dsocial/global")?)
    .kind("social.post");

for record in feed.query(&filter).await? {
    println!(
        "[{}] {} from {} @ seq {}",
        record.event.created_at(),
        record.event.kind(),
        record.source_writer,
        record.seq,
    );
}

The merged stream is sorted by (created_at, source_writer, seq), so the same set of input Strands always produces the same ordering across peers.

Failure modes

  • A Strand entry that fails signature verification surfaces WovenError::VerificationFailed { seq } with the originating sequence, so callers can quarantine a specific writer.
  • A Strand whose contents are not Woven envelopes surfaces WovenError::InvalidStrandRecord.
  • Spaces that do not start with woven:// are rejected at SpaceId::new — invalid spaces never reach the network.

Primary types to know

  • SpaceId — namespace scoping for events, filters, and topics
  • EventStream — append/read/query helpers over a single Strand
  • NexusEventFeed — multi-writer merged view
  • EventRecord{ seq, event } from a single Strand
  • AggregatedEventRecord{ source_writer, seq, event } from a merged feed