UNPKG

@snps/streaming

Version:

Zero-dependency streaming utilities for SSE, async streams, and AI integration

459 lines 10.9 kB
/** * Streaming API - Modern streaming support for Server-Sent Events, async iteration, and real-time data * Perfect for AI streaming responses, live updates, and data processing pipelines * @packageDocumentation */ /** * Server-Sent Events (SSE) Stream * Enables real-time updates from server to client */ export class SSEStream { controller; closed = false; /** * Create a new SSE stream */ constructor() { // Stream is created on demand } /** * Get the ReadableStream for this SSE connection */ getStream() { return new ReadableStream({ start: (controller) => { this.controller = controller; }, cancel: () => { this.closed = true; } }); } /** * Send a message to the client */ send(message) { if (this.closed || !this.controller) return; let data = ''; if (message.event) { data += `event: ${message.event}\n`; } if (message.id) { data += `id: ${message.id}\n`; } if (message.retry !== undefined) { data += `retry: ${message.retry}\n`; } // Handle multi-line data const lines = message.data.split('\n'); for (const line of lines) { data += `data: ${line}\n`; } data += '\n'; try { this.controller.enqueue(data); } catch (error) { console.error('SSE send error:', error); this.closed = true; } } /** * Send a simple message (shorthand) */ message(data) { this.send({ data }); } /** * Send an event with data */ event(event, data) { this.send({ event, data }); } /** * Send JSON data */ json(data) { this.send({ data: JSON.stringify(data) }); } /** * Close the stream */ close() { if (this.closed) return; this.closed = true; if (this.controller) { this.controller.close(); } } /** * Check if stream is closed */ isClosed() { return this.closed; } } /** * Create a Server-Sent Events response */ export function createSSEResponse(stream) { return new Response(stream.getStream(), { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } }); } /** * Async Stream - Modern async iterator-based streaming */ export class AsyncStream { queue = []; resolvers = []; done = false; error; /** * Push a value to the stream */ push(value) { if (this.done) { throw new Error('Cannot push to closed stream'); } const resolver = this.resolvers.shift(); if (resolver) { resolver({ value, done: false }); } else { this.queue.push(value); } } /** * End the stream */ end() { this.done = true; this.resolvers.forEach(resolve => { resolve({ value: undefined, done: true }); }); this.resolvers = []; } /** * Error the stream */ throw(error) { this.error = error; this.done = true; this.resolvers.forEach(resolve => { resolve({ value: undefined, done: true }); }); this.resolvers = []; } /** * Async iterator interface */ async *[Symbol.asyncIterator]() { while (true) { if (this.error) { throw this.error; } if (this.queue.length > 0) { const value = this.queue.shift(); if (value !== undefined) { yield value; } continue; } if (this.done) { break; } // Wait for next value const result = await new Promise((resolve) => { this.resolvers.push(resolve); }); if (result.done) { break; } yield result.value; } } /** * Transform the stream */ map(fn) { const output = new AsyncStream(); (async () => { try { for await (const chunk of this) { const transformed = await fn(chunk); output.push(transformed); } output.end(); } catch (error) { output.throw(error); } })(); return output; } /** * Filter the stream */ filter(predicate) { const output = new AsyncStream(); (async () => { try { for await (const chunk of this) { if (await predicate(chunk)) { output.push(chunk); } } output.end(); } catch (error) { output.throw(error); } })(); return output; } /** * Take only n items */ take(n) { const output = new AsyncStream(); (async () => { try { let count = 0; for await (const chunk of this) { if (count >= n) break; output.push(chunk); count++; } output.end(); } catch (error) { output.throw(error); } })(); return output; } /** * Collect all values into an array */ async collect() { const result = []; for await (const chunk of this) { result.push(chunk); } return result; } /** * Reduce the stream to a single value */ async reduce(fn, initial) { let acc = initial; for await (const chunk of this) { acc = await fn(acc, chunk); } return acc; } /** * Pipe to another stream */ pipeTo(destination) { (async () => { try { for await (const chunk of this) { destination.push(chunk); } destination.end(); } catch (error) { destination.throw(error); } })(); } } /** * Create a stream from an array */ export function fromArray(array) { const stream = new AsyncStream(); (async () => { for (const item of array) { stream.push(item); } stream.end(); })(); return stream; } /** * Create a stream from an async iterable */ export function fromAsyncIterable(iterable) { const stream = new AsyncStream(); (async () => { try { for await (const item of iterable) { stream.push(item); } stream.end(); } catch (error) { stream.throw(error); } })(); return stream; } /** * Merge multiple streams into one */ export function merge(...streams) { const output = new AsyncStream(); let active = streams.length; for (const stream of streams) { (async () => { try { for await (const chunk of stream) { output.push(chunk); } } catch (error) { output.throw(error); return; } active--; if (active === 0) { output.end(); } })(); } return output; } /** * Create a stream from a generator function */ export function fromGenerator(generator) { return fromAsyncIterable(generator()); } /** * Create an interval stream that emits values periodically */ export function interval(ms, count) { const stream = new AsyncStream(); let counter = 0; const timer = setInterval(() => { stream.push(counter++); if (count !== undefined && counter >= count) { clearInterval(timer); stream.end(); } }, ms); return stream; } /** * Stream for AI/LLM responses */ export class AIStream extends AsyncStream { /** * Send to SSE stream */ toSSE(sse) { (async () => { for await (const chunk of this) { sse.message(chunk); } sse.close(); })(); } /** * Concatenate all chunks */ async text() { const chunks = []; for await (const chunk of this) { chunks.push(chunk); } return chunks.join(''); } /** * Stream JSON objects */ async *json() { let buffer = ''; for await (const chunk of this) { buffer += chunk; // Try to parse complete JSON objects const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.trim()) { try { yield JSON.parse(line); } catch { // Not valid JSON, skip } } } } // Try to parse remaining buffer if (buffer.trim()) { try { yield JSON.parse(buffer); } catch { // Ignore parse errors } } } } /** * Create an AI stream for streaming AI responses */ export function createAIStream() { return new AIStream(); } /** * Transform stream - processes chunks through a transform function */ export class TransformStream { input = new AsyncStream(); output = new AsyncStream(); constructor(transform) { (async () => { try { for await (const chunk of this.input) { const transformed = await transform(chunk); this.output.push(transformed); } this.output.end(); } catch (error) { this.output.throw(error); } })(); } /** * Get the writable side */ writable() { return this.input; } /** * Get the readable side */ readable() { return this.output; } } /** * Create a transform stream */ export function transform(fn) { return new TransformStream(fn); } //# sourceMappingURL=index.js.map