import { Effect, Stream } from "effect"; export const toReadableByteStream = ( source: Stream.Stream, ): ReadableStream => { const encoder = new TextEncoder(); return Stream.toReadableStream(Stream.map(source, (value) => encoder.encode(value))); }; const abortEffect = (signal: AbortSignal): Effect.Effect => Effect.callback((resume) => { const abort = (): void => resume(Effect.void); if (signal.aborted) { abort(); return Effect.void; } signal.addEventListener("abort", abort, { once: true }); return Effect.sync(() => signal.removeEventListener("abort", abort)); }); export const withSseHeartbeat = ( frames: Stream.Stream, intervalMs: number, signal?: AbortSignal, ): Stream.Stream => { const heartbeat: Stream.Stream = Stream.map( Stream.tick(intervalMs), () => ": keepalive\n\n", ); const stream: Stream.Stream = Stream.merge(frames, heartbeat, { haltStrategy: "left", }); return signal ? stream.pipe(Stream.interruptWhen(abortEffect(signal))) : stream; }; export const buildSseHeaders = (extra: Record = {}): Record => ({ "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", "X-Accel-Buffering": "no", ...extra, });