import { BaseStream, type StreamMessage, type StreamResponse } from './base-stream.ts'; /** * One-shot connection stream used to run normal HTTP through the route API. * * Sending stores a response until the handler returns; no bytes are committed * early, so route errors can still replace it with the shared public error. */ export class HttpRequestStream extends BaseStream { readonly streaming = false; readonly bidirectional = false; /** Buffered route response, committed only after dispatch completes. */ private response: StreamResponse | undefined; /** Observers notified when the logical request ends. */ private readonly closeCallbacks: Array<() => void> = []; /** Guards against sends after the request lifecycle ends. */ private closed = false; /** * Buffer exactly one route response for later HTTP encoding. * * @param message - Must be a response envelope, not an event. */ async send(message: StreamMessage): Promise { if (this.closed) { throw new Error('Cannot send to a closed HTTP request'); } if (!('body' in message)) { throw new Error('Normal HTTP requests can only send a response'); } if (this.response) { throw new Error('Normal HTTP requests can only send one response'); } this.response = message; } /** * Return the buffered response for the transport adapter to encode. * * @returns The stored response, or undefined if the handler sent nothing. */ getResponse(): StreamResponse | undefined { return this.response; } /** End the logical request and notify lifecycle observers. */ close(): void { if (this.closed) { return; } this.closed = true; const callbacks = this.closeCallbacks.splice(0); callbacks.forEach((callback) => callback()); } /** * Register a callback invoked when the request closes. * * @param callback - Called immediately if the request is already closed. */ onClose(callback: () => void): void { if (this.closed) { callback(); return; } this.closeCallbacks.push(callback); } }