Repository navigation
API
π Communciation through rzeka is carried through a set of specialised API methods.
All API methods accept a who object and return IDisposable to unregister. Observables and lambda functions you pass into them are called spells.
who is the component that owns the subscription. Eris records it on every spell occurrence so the debugger can group spells under their source. Passing this from inside a Node or component is the default.

ππ§π»ββοΈ rzeka code will make the characteristic waterfall 2D structures that go deep into your indentation while remaining very clear and readable. This depth might not be for everyone though. Personally I really prefer that to a 1D top-down wall of code-text, maybe you might like it too! Screenshot info: nvim, theme Aquavium, semitransparent background, CSharpier formatter.
Each method has its own page:
| Method | What it does | Reach for it when |
|---|---|---|
| 𧬠Strand | publisher | you already have an IObservable<T> source to register into the river |
| 𧬠Pluck | fire once publisher | you want to drop a single matter into the river, right now |
| 𧬠Loom | transform | you map, combine or transform matter into new matter |
| 𧬠Weave | subscriber | you do the final effect - rendering, audio, persistence |
| 𧬠Scry | raw observable | you need read-only access to a stream from inside another spell |
| 𧬠Shuttle | request/response | you perform an operation and report its outcome |
A common pattern is to collect them into rzeka's CollectibleDisposable:
CollectibleDisposable Q = new();
Q += rzeka.Loom<PlayerInputState, PlayerMovementState>(...)
// on destroy / cleanup:
Q.Dispose();CollectibleDisposable is a wrapper around CompositeDisposable, it overloads + operator allowing you to neatly add your rzeka subscriptions to it. It does not implement .Clear() method of the CompositeDisposable because that would likely lead you to accidental memory leaks.
π Quick reference for when rzeka attaches circumstances automatically vs when you must attach them yourself.
Where automatic tracking works:
- Synchronous Loom chains - the default, most common, no action needed.
-
Shuttle responses - the request is always recorded for you, because it travels on the response (
Response<T>carries itsT). This holds even across async boundaries and with multiple requests in flight.
Where you must attach circumstances manually:
- Inside
PluckandAskcalls - pre-stamp the matter you're sending via.WithCircumstances<T>(trigger)so it carries its own cause. - Inside async boundaries within Loom - see Async Operations.
- Shuttle responses - only the additional context you Scry in. The request is already handled; stamp just the extras (if you include the request too, rzeka dedupes it).
Where circumstances are not touched:
-
Strand- it is used for root matter emissions (e.g. caused by user input), so there can be no circumstances there.
Manual stamping inside a Loom lambda (via .WithCircumstances()) is an active decision to override the default tracking - rzeka detects pre-attached circumstances and skips its automatic step.