without¶
The narrow waist of the project: the contracts every plugin speaks, plus the
stream connectors and a with-scoped background task helper. See the
Philosophy for why the model is shaped this way, and the
without API reference for the full surface.
The substrate (without.contracts)¶
Three types carry the whole model:
- A
Stream[T]is an asynchronous sequence of values: the one shape every connection takes, whoever does the I/O (a socket, a file watcher, an in-memory list). - A
Processor[In, Out]transforms a stream of inputs into a stream of outputs. It is the only node type and the only thing a user writes; one processor's output is another's input, all the way down. - A
Context[T]is a stream viewed as its latest sampled value:current()reads the latest and never blocks, the way long-lived state (config, a pool) is read.
Processors are built, not subclassed. Four builders cover a 2×2 of stateful-vs-stateless and emitting-vs-terminal:
| emits one output per event | collapses to a final value | |
|---|---|---|
| stateless | from_map(step) |
from_sink(step) (effects only) |
| stateful | from_scan(initial, step) (a scan) |
from_fold(initial, step) (a reduce) |
A scan emits at every step; a fold yields only the final accumulated value when
the stream ends. The step is always async, so it MAY await contained I/O
(reading dependencies from injected Context values), but MUST complete the
effect within the call rather than handing a half-open resource back.
Wiring (without.wiring)¶
compose chains one processor into the next on the event edge: pure composition,
the only connector that needs nothing running. sample is the behavior edge: it
exposes a stream's latest value as a Context (latest-wins, no backpressure),
driven by a background_task for the life of its with block. The source and
terminal adapters sit alongside: stream_from_iterable lifts a fixed iterable
into a Stream, collect drains one to a list, stream_from_queue turns a
push-based queue (an accept loop, a callback client) into the pull-based stream
the rest of the system consumes, and buffer decouples a source's pace from its
consumer's by pumping it into a bounded queue on a background task. stack
composes middleware (any (handler, *context) -> handler) into one, serving both
server handlers and client exchanges.
Tasks (without.tasks)¶
The async task helpers: sleep_forever; the with-scoped background_task
(starts a task on entry, cancels-then-awaits it on exit, so nothing leaks);
limit_concurrency, a bounded-concurrency driver that pulls work from a source
only while below the limit (so a lazy source is never advanced past it); and its
building blocks cancel_futures (cancel a set, then await them all) and
as_async_iterator (normalize a sync or async iterable into one async iterator).