first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
# ResultStreamer
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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 `StreamChunk`s 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
```ts
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:
```ts
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.