2.9 KiB
ResultStreamer
(no submodules)
Purpose
ResultStreamer delivers sandbox output incrementally rather than as a single end-of-execution dump. The most common use case is long-running scripts that emit logs over many seconds: subscribers receive StreamChunks as soon as the buffer either reaches maxBufferSize or the next flushIntervalMs tick fires, instead of waiting for the whole execution to finish. A final complete chunk carries the structured result envelope (success / duration / memoryUsed / error) so subscribers can tear down their UI state at the right moment.
The streamer enforces a strict completion lifecycle: once complete() has fired, any further pushChunk raises SandboxError(STREAM_CLOSED). This prevents post-completion writes from corrupting downstream state and surfaces programming errors immediately rather than letting them silently drop chunks.
Features
- Four chunk types:
log,result,error,complete. - Per-chunk wall-clock timestamp and elapsed-since-start milliseconds.
- Configurable
maxBufferSize(auto-flush threshold) andflushIntervalMs(auto-flush cadence). - Subscribe / unsubscribe with returned disposer.
completeis idempotent (second call is a no-op).- Strict post-complete
STREAM_CLOSEDenforcement.
Usage: Basic
import { ResultStreamer } from "@suaveplan/sandbox/result-streamer";
const s = new ResultStreamer();
s.subscribe((c) => console.log(c.type, c.data));
s.start();
s.pushLog("processing...");
s.complete({ success: true, result: 42, error: undefined, duration: 100, memoryUsed: 0, logs: [] });
Usage: Advanced
Pipe stream chunks straight into a Server-Sent-Events response:
import { ResultStreamer } from "@suaveplan/sandbox/result-streamer";
const s = new ResultStreamer({ flushIntervalMs: 50 });
s.subscribe((c) => {
res.write(`event: ${c.type}\ndata: ${c.data}\n\n`);
});
s.start();
// ...drive the sandbox, push chunks, await complete
API Reference
new ResultStreamer(config?: Partial<ResultStreamerConfig>).start(),stop(),destroy().subscribe(listener): () => void.pushLog(message),pushError(error),pushResult(data).complete(result: SandboxResult): void.flush(),bufferSize(),isCompleted(),listenerCount().DEFAULT_STREAMER_CONFIG— exported defaults.
Implementation Notes
pushChunk auto-flushes when the buffer reaches maxBufferSize, so callers
that don't run the interval flusher (flushIntervalMs: 0) still see chunks
land at subscribers without manual flush() calls — useful for tests that
want deterministic delivery. The elapsed field is recorded against
startTime; when the streamer is used without start(), elapsed carries
the value relative to performance.now() - 0, which is the host's high-res
timer origin — a slight oddity but a tolerable one because the documented
contract is to call start() first.