The event stream
Why a stitch returns an async iterable of typed events — start, progress, drift, result, done — instead of Promise<bytes>.
A stitch returns an async iterable of typed events —
start → progress → drift → result → done — not Promise<bytes>. Awaiting it
gives you just the final value; iterating its stream gives you everything that
happened on the way there.
Why it's shaped this way
A single Promise<bytes> collapses a call to two outcomes — it resolved, or it
threw — and throws away the story in between: the retry that succeeded on the
third attempt, the throttle that paused for 400ms, the field that silently
changed shape. That story is exactly what an agent needs to reason about a call,
and what an operator needs to debug one.
So a stitch is a single source that you can read at the altitude you need:
import { } from 'stitchapi';
const = ({ : 'https://api.example.com', : '/search' });
// Await it for just the value…
const = await ({ : { : 'mango' } });
// …or iterate the typed event stream for everything that happened.
for await (const of ({ : { : 'mango' } }).()) {
if (. === 'progress') .(., .);
if (. === 'drift')
.(.., ..);
if (. === 'result') .(.);
}The events form a fixed spine — start, then any number of progress (auth,
throttle, retry, pagination) and drift findings, then exactly one terminal
result or error, then done. The union is discriminated on type, so
narrowing one field tells the compiler the shape of the rest.
Awaiting without the throw
Awaiting collapses the stream to its terminal — the result value, or a thrown
StitchError when the terminal is an error. When you'd rather
branch on failure than wrap the call in try/catch, .safe() consumes the
same stream and never throws: it resolves to a discriminated { ok, data, error }
where error is the StitchError, or null on success.
import { } from 'stitchapi';
const = ({ : 'https://api.example.com', : '/search' });
const { , } = await ({ : { : 'mango' } }).();
if () {
.(., .); // a StitchError
} else {
.(); // the validated result; error is null here
}.unwrap() is the explicit throwing twin — the value, or a thrown StitchError,
exactly what awaiting the bare call does. Each also has an eager form on the
stitch itself (search.safe(input) / search.unwrap(input)) that mirrors
search.stream(input).
How it relates
The stream is what every cross-cutting feature reports through:
retry, throttle,
and auth emit progress; leveled drift emits
drift. It is also the seam observability plugs into — a
trace sink is just a consumer of these
same events, which is how console logs, JSONL, and
OTLP spans all come from one source without
the call site knowing.
See also
The seam
The shared-runtime primitive a set of stitches belong to — one store, vault, throttle bucket, and trace sink behind a shared base config and a trusted principal boundary.
Run identity & the trace tree
How a stitch identifies one call and its place in a tree — a shared traceId, the span id (spanId), and a parentSpanId — so composed runs form one OpenTelemetry span tree, and how that maps onto the traceparent header on the wire.