Files
skills/docs/devtools/tooling/sandbox/src/result-streamer/result-streamer.md
2026-05-29 08:20:56 +01:00

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) and flushIntervalMs (auto-flush cadence).
  • Subscribe / unsubscribe with returned disposer.
  • complete is idempotent (second call is a no-op).
  • Strict post-complete STREAM_CLOSED enforcement.

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.