Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@ahoo-wang/fetcher-eventstream

Package Overview
Dependencies
Maintainers
1
Versions
339
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ahoo-wang/fetcher-eventstream - npm Package Compare versions

Comparing version
3.16.4
to
3.16.5
+1
-1
dist/index.es.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.es.js","names":[],"sources":["../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/eventStreamResultExtractor.ts","../src/responses.ts","../src/readableStreamAsyncIterable.ts","../src/readableStreams.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Transformer that splits text into lines.\n *\n * This transformer accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. It handles partial lines that span multiple\n * input chunks by maintaining an internal buffer. Lines are emitted without the newline character.\n *\n * The transformer handles various edge cases:\n * - Lines that span multiple input chunks\n * - Empty lines (emitted as empty strings)\n * - Text without trailing newlines (buffered until stream ends)\n * - Mixed line endings (only '\\n' is recognized as line separator)\n *\n * @implements {Transformer<string, string>}\n *\n * @example\n * ```typescript\n * const transformer = new TextLineTransformer();\n * // Input: \"line1\\nline2\\npartial\"\n * // Output: [\"line1\", \"line2\"]\n * // Buffer: \"partial\"\n *\n * // Later input: \"line\\n\"\n * // Output: [\"partialline\"]\n * // Buffer: \"\"\n * ```\n */\nexport class TextLineTransformer implements Transformer<string, string> {\n private buffer = '';\n\n /**\n * Transform input string chunk by splitting it into lines.\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ) {\n try {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n controller.enqueue(line);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n\n /**\n * Flush remaining buffer when the stream ends.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<string>) {\n try {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n controller.enqueue(this.buffer);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * This class provides a convenient way to transform a stream of text chunks into a stream\n * of individual lines. It wraps the TextLineTransformer in a TransformStream for easy\n * integration with other stream processing pipelines.\n *\n * The stream processes text data and emits each line as a separate chunk, handling\n * lines that may span multiple input chunks automatically.\n *\n * @example\n * ```typescript\n * // Create a line-splitting stream\n * const lineStream = new TextLineTransformStream();\n *\n * // Pipe text through it\n * const lines = textStream.pipeThrough(lineStream);\n *\n * // Process each line\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Process SSE response line by line\n * const response = await fetch('/api/stream');\n * const lines = response.body!\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(new TextLineTransformStream());\n *\n * for await (const line of lines) {\n * if (line.startsWith('data: ')) {\n * console.log('SSE data:', line.substring(6));\n * }\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n /**\n * Creates a new TextLineTransformStream instance.\n *\n * Initializes the stream with a TextLineTransformer that handles the line splitting logic.\n */\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * This interface defines the structure of Server-Sent Events (SSE) as specified by the W3C.\n * Each event contains metadata and data that can be processed by clients to handle real-time\n * updates from the server.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\n/**\n * Constants for Server-Sent Event field names.\n *\n * This class provides string constants for the standard SSE field names as defined\n * in the W3C Server-Sent Events specification. These constants help ensure\n * consistent field name usage throughout the parsing logic.\n */\nexport class ServerSentEventFields {\n /** The field name for event ID */\n static readonly ID = 'id';\n /** The field name for retry interval */\n static readonly RETRY = 'retry';\n /** The field name for event type */\n static readonly EVENT = 'event';\n /** The field name for event data */\n static readonly DATA = 'data';\n}\n\n/**\n * Processes a field-value pair and updates the current event state accordingly.\n *\n * This internal function handles the parsing of individual SSE fields according to the\n * Server-Sent Events specification. It updates the provided event state object with\n * the parsed field values.\n *\n * @param field - The field name (e.g., 'event', 'data', 'id', 'retry')\n * @param value - The field value as a string\n * @param currentEvent - The current event state object to update\n *\n * @example\n * ```typescript\n * const eventState: EventState = { event: 'message', data: [] };\n * processFieldInternal('event', 'custom', eventState);\n * // eventState.event is now 'custom'\n * ```\n */\nfunction processFieldInternal(\n field: string,\n value: string,\n currentEvent: EventState,\n) {\n switch (field) {\n case ServerSentEventFields.EVENT:\n currentEvent.event = value;\n break;\n case ServerSentEventFields.DATA:\n currentEvent.data.push(value);\n break;\n case ServerSentEventFields.ID:\n currentEvent.id = value;\n break;\n case ServerSentEventFields.RETRY: {\n const retryValue = parseInt(value, 10);\n if (!isNaN(retryValue)) {\n currentEvent.retry = retryValue;\n }\n break;\n }\n default:\n // Ignore unknown fields\n break;\n }\n}\n\n/**\n * Internal state representation during Server-Sent Event parsing.\n *\n * This interface tracks the current state of an event being parsed from the SSE stream.\n * It accumulates field values until a complete event is ready to be emitted.\n */\ninterface EventState {\n /** The event type (defaults to 'message') */\n event?: string;\n /** The event ID */\n id?: string;\n /** The retry interval in milliseconds */\n retry?: number;\n /** Array of data lines that will be joined with newlines */\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n * This transformer handles the parsing of Server-Sent Events (SSE) according to the W3C specification.\n * It processes incoming text chunks and converts them into structured ServerSentEvent objects.\n */\nexport class ServerSentEventTransformer implements Transformer<\n string,\n ServerSentEvent\n> {\n // Initialize currentEventState with default values in a closure\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Reset the current event state to default values.\n * This method is called after processing each complete event or when an error occurs.\n */\n private resetEventState() {\n this.currentEventState.event = DEFAULT_EVENT_TYPE;\n this.currentEventState.id = undefined;\n this.currentEventState.retry = undefined;\n this.currentEventState.data = [];\n }\n\n /**\n * Transform input string chunk into ServerSentEvent object.\n * This method processes individual chunks of text data, parsing them according to the SSE format.\n * It handles:\n * - Empty lines (used as event separators)\n * - Comment lines (starting with ':')\n * - Field lines (field: value format)\n * - Event completion and emission\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ) {\n const currentEvent = this.currentEventState;\n try {\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n // If there is accumulated event data, send event\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n\n // Reset current event (preserve id and retry for subsequent events)\n currentEvent.event = DEFAULT_EVENT_TYPE;\n // Preserve id and retry for subsequent events (no need to reassign to themselves)\n currentEvent.data = [];\n }\n return;\n }\n\n // Ignore comment lines (starting with colon)\n if (chunk.startsWith(':')) {\n return;\n }\n\n // Parse fields\n const colonIndex = chunk.indexOf(':');\n let field: string;\n let value: string;\n\n if (colonIndex === -1) {\n // No colon, entire line as field name, value is empty\n field = chunk.toLowerCase();\n value = '';\n } else {\n // Extract field name and value\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n\n // If value starts with space, remove leading space\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n // Remove trailing newlines from field and value\n field = field.trim();\n value = value.trim();\n\n processFieldInternal(field, value, currentEvent);\n } catch (error) {\n const enhancedError = new Error(\n `Failed to process chunk: \"${chunk}\". ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n // Reset state\n this.resetEventState();\n }\n }\n\n /**\n * Called when the stream ends, used to process remaining data.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<ServerSentEvent>) {\n const currentEvent = this.currentEventState;\n try {\n // Send the last event (if any)\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n }\n } catch (error) {\n const enhancedError = new Error(\n `Failed to flush remaining data. ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n } finally {\n // Reset state\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n *\n * This class provides a convenient way to transform raw text streams containing Server-Sent Events\n * into structured event objects. It wraps the ServerSentEventTransformer in a TransformStream\n * for easy integration with other stream processing pipelines.\n *\n * The stream processes SSE format text and emits ServerSentEvent objects as they are completed.\n * Events are separated by empty lines, and the stream handles partial events across multiple chunks.\n *\n * @example\n * ```typescript\n * // Create a transform stream\n * const sseStream = new ServerSentEventTransformStream();\n *\n * // Pipe a text stream through it\n * const eventStream = textStream.pipeThrough(sseStream);\n *\n * // Consume the events\n * for await (const event of eventStream) {\n * console.log('Event:', event.event, 'Data:', event.data);\n * }\n * ```\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n /**\n * Creates a new ServerSentEventTransformStream instance.\n *\n * Initializes the stream with a ServerSentEventTransformer that handles\n * the parsing of SSE format text into structured events.\n */\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n type ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n *\n * This type represents a stream that yields Server-Sent Event objects as they are parsed\n * from a raw event stream. Each chunk in the stream contains a complete SSE event with\n * its metadata (event type, ID, retry interval) and data.\n *\n * @see {@link ServerSentEvent} for the structure of individual events\n * @see {@link toServerSentEventStream} for converting HTTP responses to this type\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n *\n * This error is thrown when there are issues converting an HTTP Response to a ServerSentEventStream.\n * It extends FetcherError to provide additional context about the failed conversion, including\n * the original Response object and any underlying cause.\n *\n * @extends {FetcherError}\n *\n * @example\n * ```typescript\n * try {\n * const eventStream = toServerSentEventStream(response);\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Failed to convert response to event stream:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n *\n * @param response - The Response object associated with the error, providing context\n * about the failed conversion (status, headers, etc.)\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this conversion error\n */\n constructor(\n public readonly response: Response,\n errorMsg?: string,\n cause?: Error | any,\n ) {\n super(errorMsg, cause);\n this.name = 'EventStreamConvertError';\n // Restore prototype chain for proper inheritance\n Object.setPrototypeOf(this, EventStreamConvertError.prototype);\n }\n}\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * This function takes an HTTP Response object and converts its body into a stream of\n * Server-Sent Event objects. The conversion process involves several transformation steps:\n *\n * 1. **TextDecoderStream**: Decodes the raw Uint8Array response body to UTF-8 strings\n * 2. **TextLineTransformStream**: Splits the text stream into individual lines\n * 3. **ServerSentEventTransformStream**: Parses the line-based SSE format into structured events\n *\n * The resulting stream can be consumed using async iteration or other stream methods.\n *\n * @param response - The HTTP Response object to convert. Must have a readable body stream.\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {EventStreamConvertError} If the response body is null or cannot be processed\n *\n * @example\n * ```typescript\n * // Convert an SSE response to an event stream\n * const response = await fetch('/api/events');\n * const eventStream = toServerSentEventStream(response);\n *\n * // Consume events asynchronously\n * for await (const event of eventStream) {\n * console.log(`Event: ${event.event}, Data: ${event.data}`);\n *\n * // Handle different event types\n * switch (event.event) {\n * case 'message':\n * handleMessage(event.data);\n * break;\n * case 'error':\n * handleError(event.data);\n * break;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Handle conversion errors\n * try {\n * const eventStream = toServerSentEventStream(response);\n * // Use the stream...\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Event stream conversion failed:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new EventStreamConvertError(response, 'Response body is null');\n }\n\n return response.body\n .pipeThrough(new TextDecoderStream('utf-8'))\n .pipeThrough(new TextLineTransformStream())\n .pipeThrough(new ServerSentEventTransformStream());\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type ServerSentEvent } from './serverSentEventTransformStream';\nimport type { ServerSentEventStream } from './eventStreamConverter';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * This detector function is called for each incoming ServerSentEvent. If it returns true,\n * the stream transformation will be terminated, preventing further events from being processed.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n *\n * @example\n * ```typescript\n * const terminateOnDone: TerminateDetector = (event) => {\n * return event.event === 'done' || event.data === '[DONE]';\n * };\n * ```\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\n *\n * This interface extends the base ServerSentEvent but replaces the string 'data' field\n * with a parsed JSON object of the specified generic type. This allows for type-safe\n * access to the event payload.\n *\n * @template DATA - The expected type of the parsed JSON data\n */\nexport interface JsonServerSentEvent<DATA> extends Omit<\n ServerSentEvent,\n 'data'\n> {\n /** The parsed JSON data from the event */\n data: DATA;\n}\n\n/**\n * A TransformStream transformer that converts ServerSentEvent to JsonServerSentEvent with optional termination detection.\n *\n * This transformer parses the JSON data from ServerSentEvent chunks and optionally terminates\n * the stream when a termination condition is met. It's designed to work within a TransformStream\n * to convert raw server-sent events into typed JSON events.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransform<DATA> implements Transformer<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransform instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * If provided, this function is called for each event and can terminate\n * the stream by returning true.\n */\n constructor(private readonly terminateDetector?: TerminateDetector) {}\n\n /**\n * Transforms a ServerSentEvent chunk into a JsonServerSentEvent.\n *\n * This method first checks if the event should terminate the stream using the terminateDetector.\n * If termination is required, the controller is terminated. Otherwise, the event data is parsed\n * as JSON and enqueued as a JsonServerSentEvent.\n *\n * If the terminateDetector throws an exception, the stream is terminated with an error to prevent\n * corrupted state.\n *\n * @param chunk - The ServerSentEvent to transform\n * @param controller - The TransformStream controller for managing the stream\n * @throws {SyntaxError} If the event data is not valid JSON\n * @throws {Error} If the terminateDetector throws an exception\n *\n * @example\n * ```typescript\n * const transformer = new JsonServerSentEventTransform<MyData>();\n * // This will be called automatically by the TransformStream\n * ```\n */\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ) {\n try {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n controller.terminate();\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n controller.enqueue({\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n } catch (error) {\n // If terminate detector throws or JSON parsing fails, terminate the stream to prevent corrupted state\n controller.error(error);\n return;\n }\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to JsonServerSentEvent streams with optional termination detection.\n *\n * This class extends TransformStream to provide a convenient way to transform streams of ServerSentEvent\n * objects into streams of JsonServerSentEvent objects. It supports optional termination detection to\n * automatically end the stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransformStream instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * When provided, the stream will automatically terminate when this\n * function returns true for any event.\n *\n * @example\n * ```typescript\n * // Create a stream that terminates on 'done' events\n * const terminateOnDone: TerminateDetector = (event) => event.event === 'done';\n * const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);\n *\n * // Create a stream without termination detection\n * const basicStream = new JsonServerSentEventTransformStream<MyData>();\n * ```\n */\n constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\n *\n * This type represents a stream that yields parsed JSON server-sent events.\n * Each chunk in the stream contains the event metadata along with parsed JSON data.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\n/**\n * Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.\n *\n * This function takes a stream of raw server-sent events and transforms it into a stream of\n * parsed JSON events. It optionally accepts a termination detector to automatically end the\n * stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform\n * @param terminateDetector - Optional function to detect when the stream should be terminated\n * @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data\n * @throws {SyntaxError} If any event data is not valid JSON (thrown during stream consumption)\n *\n * @example\n * ```typescript\n * // Basic usage without termination detection\n * const jsonStream = toJsonServerSentEventStream<MyData>(serverSentEventStream);\n *\n * // With termination detection\n * const terminateOnDone: TerminateDetector = (event) => event.data === '[DONE]';\n * const terminatingStream = toJsonServerSentEventStream<MyData>(\n * serverSentEventStream,\n * terminateOnDone\n * );\n *\n * // Consume the stream\n * for await (const event of jsonStream) {\n * console.log('Received:', event.data);\n * console.log('Event type:', event.event);\n * }\n * ```\n */\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n terminateDetector?: TerminateDetector,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(terminateDetector),\n );\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { FetchExchange, ResultExtractor } from '@ahoo-wang/fetcher';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport type { JsonServerSentEventStream } from './jsonServerSentEventTransformStream';\n\n/**\n * ServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a ServerSentEventStream from an HTTP response that contains Server-Sent Events.\n * The extractor validates that the response supports event streaming and converts the\n * response body into a properly typed event stream.\n *\n * This extractor should be used when you want to consume raw Server-Sent Events\n * without JSON parsing, maintaining the original string data format.\n *\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {ExchangeError} When the server response does not support ServerSentEventStream\n * (e.g., wrong content type, no response body)\n *\n *\n * @see {@link ServerSentEventStream} for the stream type\n * @see {@link JsonEventStreamResultExtractor} for JSON-parsed event streams\n */\nexport const EventStreamResultExtractor: ResultExtractor<\n ServerSentEventStream\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredEventStream();\n};\n\n/**\n * JsonServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a JsonServerSentEventStream from an HTTP response that contains Server-Sent Events\n * with JSON data. The extractor validates that the response supports event streaming and converts\n * the response body into a properly typed event stream with automatic JSON parsing.\n *\n * This extractor should be used when you want to consume Server-Sent Events where the event\n * data is JSON-formatted, providing type-safe access to parsed JSON objects instead of raw strings.\n *\n * @template DATA - The expected type of the JSON data in the server-sent events\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects with parsed JSON data as they are received\n * @throws {ExchangeError} When the server response does not support JsonServerSentEventStream\n * (e.g., wrong content type, no response body, invalid JSON)\n *\n *\n * @see {@link JsonServerSentEventStream} for the stream type with JSON data\n * @see {@link EventStreamResultExtractor} for raw string event streams\n */\nexport const JsonEventStreamResultExtractor: ResultExtractor<\n JsonServerSentEventStream<any>\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredJsonEventStream();\n};\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EventStreamConvertError,\n type ServerSentEventStream,\n toServerSentEventStream,\n} from './eventStreamConverter';\nimport {\n type JsonServerSentEventStream,\n type TerminateDetector,\n toJsonServerSentEventStream,\n} from './jsonServerSentEventTransformStream';\nimport { CONTENT_TYPE_HEADER, ContentTypeValues } from '@ahoo-wang/fetcher';\n\ndeclare global {\n interface Response {\n /**\n * Gets the content type of the response.\n *\n * This property provides access to the Content-Type header of the response,\n * which indicates the media type of the resource transmitted in the response.\n *\n * @returns The content type header value as a string, or null if the header is not set\n */\n get contentType(): string | null;\n\n /**\n * Checks if the response is an event stream.\n *\n * This property examines the Content-Type header to determine if the response\n * contains server-sent events data (text/event-stream).\n *\n * @returns true if the response is an event stream, false otherwise\n */\n get isEventStream(): boolean;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects, or null if not an event stream\n */\n eventStream(): ServerSentEventStream | null;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is similar to eventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws {Error} if the event stream is not available\n */\n requiredEventStream(): ServerSentEventStream;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data, or null if not an event stream\n */\n jsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA> | null;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is similar to jsonEventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream with JSON data.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data\n * @throws {Error} if the event stream is not available\n */\n requiredJsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA>;\n }\n}\n\nif (typeof Response !== 'undefined') {\n const CONTENT_TYPE_PROPERTY_NAME = 'contentType';\n /**\n * Defines the contentType property on Response prototype.\n * This property provides a convenient way to access the Content-Type header value.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n CONTENT_TYPE_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, CONTENT_TYPE_PROPERTY_NAME, {\n get() {\n return this.headers.get(CONTENT_TYPE_HEADER);\n },\n configurable: true,\n });\n }\n\n const IS_EVENT_STREAM_PROPERTY_NAME = 'isEventStream';\n /**\n * Defines the isEventStream property on Response prototype.\n * This property checks if the response has a Content-Type header indicating it's an event stream.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n IS_EVENT_STREAM_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, IS_EVENT_STREAM_PROPERTY_NAME, {\n get() {\n const contentType = this.contentType;\n if (!contentType) {\n return false;\n }\n return contentType.includes(ContentTypeValues.TEXT_EVENT_STREAM);\n },\n configurable: true,\n });\n }\n\n /**\n * Implementation of the eventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream.\n *\n * @returns A ServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'eventStream')\n ) {\n Response.prototype.eventStream = function () {\n if (!this.isEventStream) {\n return null;\n }\n return toServerSentEventStream(this);\n };\n }\n\n /**\n * Implementation of the requiredEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @returns A ServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredEventStream',\n )\n ) {\n Response.prototype.requiredEventStream = function () {\n const eventStream = this.eventStream();\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n\n /**\n * Implementation of the jsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'jsonEventStream')\n ) {\n Response.prototype.jsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.eventStream();\n if (!eventStream) {\n return null;\n }\n return toJsonServerSentEventStream<DATA>(eventStream, terminateDetector);\n };\n }\n\n /**\n * Implementation of the requiredJsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredJsonEventStream',\n )\n ) {\n Response.prototype.requiredJsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.jsonEventStream<DATA>(terminateDetector);\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper class that converts a ReadableStream into an AsyncIterable.\n *\n * This class enables the use of ReadableStream objects with async iteration syntax\n * (for-await...of loops), providing a more ergonomic way to consume streaming data.\n * It implements the AsyncIterable interface and manages the underlying stream reader,\n * handling proper resource cleanup and error propagation.\n *\n * The wrapper automatically handles stream locking, ensuring that only one consumer\n * can read from the stream at a time, and provides safe cleanup when iteration ends\n * or errors occur.\n *\n * @template T - The type of data yielded by the stream\n *\n * @example\n * ```typescript\n * // Direct usage\n * const response = await fetch('/api/stream');\n * const stream = response.body;\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * console.log('Received:', chunk);\n * }\n * // Stream is automatically cleaned up after iteration\n * ```\n *\n * @example\n * ```typescript\n * // With early termination\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * if (someCondition) {\n * asyncIterable.releaseLock(); // Manually release if needed\n * break;\n * }\n * }\n * ```\n */\nexport class ReadableStreamAsyncIterable<T> implements AsyncIterable<T> {\n private readonly reader: ReadableStreamDefaultReader<T>;\n private _locked: boolean = true;\n\n /**\n * Creates a new ReadableStreamAsyncIterable instance.\n * @param stream - The ReadableStream to wrap.\n */\n constructor(private readonly stream: ReadableStream<T>) {\n this.reader = stream.getReader();\n }\n\n /**\n * Gets the lock status of the reader.\n * @returns True if the reader is currently locked, false otherwise.\n */\n get locked(): boolean {\n return this._locked;\n }\n\n /**\n * Releases the reader lock if currently locked.\n * This method safely releases the reader lock by catching any potential errors.\n */\n releaseLock() {\n if (!this._locked) return false;\n this._locked = false;\n try {\n this.reader.releaseLock();\n return true;\n } catch (error) {\n console.debug('Failed to release reader lock:', error);\n return false;\n }\n }\n\n /**\n * Implements the AsyncIterable interface by returning this iterator.\n * @returns The async iterator for this instance.\n */\n [Symbol.asyncIterator]() {\n return this;\n }\n\n /**\n * Gets the next value from the stream.\n * Reads the next chunk from the stream and returns it as an IteratorResult.\n * If the stream is done, releases the lock and returns a done result.\n * @returns A promise that resolves to an IteratorResult containing the next value or done status.\n * @throws If an error occurs while reading from the stream.\n */\n async next(): Promise<IteratorResult<T>> {\n try {\n const { done, value } = await this.reader.read();\n if (done) {\n this.releaseLock();\n return { done: true, value: undefined };\n }\n\n return { done: false, value };\n } catch (error) {\n this.releaseLock();\n throw error;\n }\n }\n\n /**\n * Implements the return method of the async iterator.\n * Cancels the stream reader and releases the lock.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async return(): Promise<IteratorResult<T>> {\n try {\n await this.reader.cancel();\n } catch (error) {\n console.debug('Failed to cancel stream reader:', error);\n } finally {\n this.releaseLock();\n }\n return { done: true, value: undefined };\n }\n\n /**\n * Implements the throw method of the async iterator.\n * Releases the lock and returns a done result.\n * @param error - The error to be thrown.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async throw(error: any): Promise<IteratorResult<T>> {\n // Ensure the reader lock is released before throwing\n console.debug('Throwing error:', error);\n this.releaseLock();\n return { done: true, value: undefined };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReadableStreamAsyncIterable } from './readableStreamAsyncIterable';\n\ndeclare global {\n interface ReadableStream<R = any> {\n /**\n * Makes ReadableStream async iterable for use with for-await loops.\n *\n * This allows the stream to be consumed using `for await (const chunk of stream)` syntax.\n *\n * @returns An async iterator for the stream\n */\n [Symbol.asyncIterator](): AsyncIterator<R>;\n }\n}\n\n/**\n * Checks if the current environment natively supports async iteration on ReadableStream.\n *\n * This constant determines whether the browser or runtime already provides\n * built-in support for using ReadableStream with for-await...of loops.\n * If not supported, this library will polyfill the functionality by adding\n * the [Symbol.asyncIterator] method to ReadableStream.prototype.\n *\n * @returns true if native async iteration is supported, false if polyfill is needed\n *\n * @example\n * ```typescript\n * import { isReadableStreamAsyncIterableSupported } from '@ahoo-wang/fetcher-eventstream';\n *\n * if (isReadableStreamAsyncIterableSupported) {\n * console.log('Native support available');\n * } else {\n * console.log('Using polyfill');\n * }\n * ```\n */\nexport const isReadableStreamAsyncIterableSupported =\n typeof ReadableStream !== 'undefined' &&\n typeof ReadableStream.prototype[Symbol.asyncIterator] === 'function';\n\n// Add [Symbol.asyncIterator] to ReadableStream if not already implemented\nif (!isReadableStreamAsyncIterableSupported) {\n ReadableStream.prototype[Symbol.asyncIterator] = function <R = any>() {\n return new ReadableStreamAsyncIterable<R>(this as ReadableStream<R>);\n };\n}\n"],"mappings":"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"}
{"version":3,"file":"index.es.js","names":[],"sources":["../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/eventStreamResultExtractor.ts","../src/responses.ts","../src/readableStreamAsyncIterable.ts","../src/readableStreams.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Transformer that splits text into lines.\n *\n * This transformer accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. It handles partial lines that span multiple\n * input chunks by maintaining an internal buffer. Lines are emitted without the newline character.\n *\n * The transformer handles various edge cases:\n * - Lines that span multiple input chunks\n * - Empty lines (emitted as empty strings)\n * - Text without trailing newlines (buffered until stream ends)\n * - Mixed line endings (only '\\n' is recognized as line separator)\n *\n * @implements {Transformer<string, string>}\n *\n * @example\n * ```typescript\n * const transformer = new TextLineTransformer();\n * // Input: \"line1\\nline2\\npartial\"\n * // Output: [\"line1\", \"line2\"]\n * // Buffer: \"partial\"\n *\n * // Later input: \"line\\n\"\n * // Output: [\"partialline\"]\n * // Buffer: \"\"\n * ```\n */\nexport class TextLineTransformer implements Transformer<string, string> {\n private buffer = '';\n\n /**\n * Transform input string chunk by splitting it into lines.\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ) {\n try {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n controller.enqueue(line);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n\n /**\n * Flush remaining buffer when the stream ends.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<string>) {\n try {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n controller.enqueue(this.buffer);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * This class provides a convenient way to transform a stream of text chunks into a stream\n * of individual lines. It wraps the TextLineTransformer in a TransformStream for easy\n * integration with other stream processing pipelines.\n *\n * The stream processes text data and emits each line as a separate chunk, handling\n * lines that may span multiple input chunks automatically.\n *\n * @example\n * ```typescript\n * // Create a line-splitting stream\n * const lineStream = new TextLineTransformStream();\n *\n * // Pipe text through it\n * const lines = textStream.pipeThrough(lineStream);\n *\n * // Process each line\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Process SSE response line by line\n * const response = await fetch('/api/stream');\n * const lines = response.body!\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(new TextLineTransformStream());\n *\n * for await (const line of lines) {\n * if (line.startsWith('data: ')) {\n * console.log('SSE data:', line.substring(6));\n * }\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n /**\n * Creates a new TextLineTransformStream instance.\n *\n * Initializes the stream with a TextLineTransformer that handles the line splitting logic.\n */\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * This interface defines the structure of Server-Sent Events (SSE) as specified by the W3C.\n * Each event contains metadata and data that can be processed by clients to handle real-time\n * updates from the server.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\n/**\n * Constants for Server-Sent Event field names.\n *\n * This class provides string constants for the standard SSE field names as defined\n * in the W3C Server-Sent Events specification. These constants help ensure\n * consistent field name usage throughout the parsing logic.\n */\nexport class ServerSentEventFields {\n /** The field name for event ID */\n static readonly ID = 'id';\n /** The field name for retry interval */\n static readonly RETRY = 'retry';\n /** The field name for event type */\n static readonly EVENT = 'event';\n /** The field name for event data */\n static readonly DATA = 'data';\n}\n\n/**\n * Processes a field-value pair and updates the current event state accordingly.\n *\n * This internal function handles the parsing of individual SSE fields according to the\n * Server-Sent Events specification. It updates the provided event state object with\n * the parsed field values.\n *\n * @param field - The field name (e.g., 'event', 'data', 'id', 'retry')\n * @param value - The field value as a string\n * @param currentEvent - The current event state object to update\n *\n * @example\n * ```typescript\n * const eventState: EventState = { event: 'message', data: [] };\n * processFieldInternal('event', 'custom', eventState);\n * // eventState.event is now 'custom'\n * ```\n */\nfunction processFieldInternal(\n field: string,\n value: string,\n currentEvent: EventState,\n) {\n switch (field) {\n case ServerSentEventFields.EVENT:\n currentEvent.event = value;\n break;\n case ServerSentEventFields.DATA:\n currentEvent.data.push(value);\n break;\n case ServerSentEventFields.ID:\n currentEvent.id = value;\n break;\n case ServerSentEventFields.RETRY: {\n const retryValue = parseInt(value, 10);\n if (!isNaN(retryValue)) {\n currentEvent.retry = retryValue;\n }\n break;\n }\n default:\n // Ignore unknown fields\n break;\n }\n}\n\n/**\n * Internal state representation during Server-Sent Event parsing.\n *\n * This interface tracks the current state of an event being parsed from the SSE stream.\n * It accumulates field values until a complete event is ready to be emitted.\n */\ninterface EventState {\n /** The event type (defaults to 'message') */\n event?: string;\n /** The event ID */\n id?: string;\n /** The retry interval in milliseconds */\n retry?: number;\n /** Array of data lines that will be joined with newlines */\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n * This transformer handles the parsing of Server-Sent Events (SSE) according to the W3C specification.\n * It processes incoming text chunks and converts them into structured ServerSentEvent objects.\n */\nexport class ServerSentEventTransformer implements Transformer<\n string,\n ServerSentEvent\n> {\n // Initialize currentEventState with default values in a closure\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Reset the current event state to default values.\n * This method is called after processing each complete event or when an error occurs.\n */\n private resetEventState() {\n this.currentEventState.event = DEFAULT_EVENT_TYPE;\n this.currentEventState.id = undefined;\n this.currentEventState.retry = undefined;\n this.currentEventState.data = [];\n }\n\n /**\n * Transform input string chunk into ServerSentEvent object.\n * This method processes individual chunks of text data, parsing them according to the SSE format.\n * It handles:\n * - Empty lines (used as event separators)\n * - Comment lines (starting with ':')\n * - Field lines (field: value format)\n * - Event completion and emission\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ) {\n const currentEvent = this.currentEventState;\n try {\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n // If there is accumulated event data, send event\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n\n // Reset current event (preserve id and retry for subsequent events)\n currentEvent.event = DEFAULT_EVENT_TYPE;\n // Preserve id and retry for subsequent events (no need to reassign to themselves)\n currentEvent.data = [];\n }\n return;\n }\n\n // Ignore comment lines (starting with colon)\n if (chunk.startsWith(':')) {\n return;\n }\n\n // Parse fields\n const colonIndex = chunk.indexOf(':');\n let field: string;\n let value: string;\n\n if (colonIndex === -1) {\n // No colon, entire line as field name, value is empty\n field = chunk.toLowerCase();\n value = '';\n } else {\n // Extract field name and value\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n\n // If value starts with space, remove leading space\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n // Remove trailing newlines from field and value\n field = field.trim();\n value = value.trim();\n\n processFieldInternal(field, value, currentEvent);\n } catch (error) {\n const enhancedError = new Error(\n `Failed to process chunk: \"${chunk}\". ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n // Reset state\n this.resetEventState();\n }\n }\n\n /**\n * Called when the stream ends, used to process remaining data.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<ServerSentEvent>) {\n const currentEvent = this.currentEventState;\n try {\n // Send the last event (if any)\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n }\n } catch (error) {\n const enhancedError = new Error(\n `Failed to flush remaining data. ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n } finally {\n // Reset state\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n *\n * This class provides a convenient way to transform raw text streams containing Server-Sent Events\n * into structured event objects. It wraps the ServerSentEventTransformer in a TransformStream\n * for easy integration with other stream processing pipelines.\n *\n * The stream processes SSE format text and emits ServerSentEvent objects as they are completed.\n * Events are separated by empty lines, and the stream handles partial events across multiple chunks.\n *\n * @example\n * ```typescript\n * // Create a transform stream\n * const sseStream = new ServerSentEventTransformStream();\n *\n * // Pipe a text stream through it\n * const eventStream = textStream.pipeThrough(sseStream);\n *\n * // Consume the events\n * for await (const event of eventStream) {\n * console.log('Event:', event.event, 'Data:', event.data);\n * }\n * ```\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n /**\n * Creates a new ServerSentEventTransformStream instance.\n *\n * Initializes the stream with a ServerSentEventTransformer that handles\n * the parsing of SSE format text into structured events.\n */\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n type ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n *\n * This type represents a stream that yields Server-Sent Event objects as they are parsed\n * from a raw event stream. Each chunk in the stream contains a complete SSE event with\n * its metadata (event type, ID, retry interval) and data.\n *\n * @see {@link ServerSentEvent} for the structure of individual events\n * @see {@link toServerSentEventStream} for converting HTTP responses to this type\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n *\n * This error is thrown when there are issues converting an HTTP Response to a ServerSentEventStream.\n * It extends FetcherError to provide additional context about the failed conversion, including\n * the original Response object and any underlying cause.\n *\n * @extends {FetcherError}\n *\n * @example\n * ```typescript\n * try {\n * const eventStream = toServerSentEventStream(response);\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Failed to convert response to event stream:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n *\n * @param response - The Response object associated with the error, providing context\n * about the failed conversion (status, headers, etc.)\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this conversion error\n */\n constructor(\n public readonly response: Response,\n errorMsg?: string,\n cause?: Error | any,\n ) {\n super(errorMsg, cause);\n this.name = 'EventStreamConvertError';\n // Restore prototype chain for proper inheritance\n Object.setPrototypeOf(this, EventStreamConvertError.prototype);\n }\n}\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * This function takes an HTTP Response object and converts its body into a stream of\n * Server-Sent Event objects. The conversion process involves several transformation steps:\n *\n * 1. **TextDecoderStream**: Decodes the raw Uint8Array response body to UTF-8 strings\n * 2. **TextLineTransformStream**: Splits the text stream into individual lines\n * 3. **ServerSentEventTransformStream**: Parses the line-based SSE format into structured events\n *\n * The resulting stream can be consumed using async iteration or other stream methods.\n *\n * @param response - The HTTP Response object to convert. Must have a readable body stream.\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {EventStreamConvertError} If the response body is null or cannot be processed\n *\n * @example\n * ```typescript\n * // Convert an SSE response to an event stream\n * const response = await fetch('/api/events');\n * const eventStream = toServerSentEventStream(response);\n *\n * // Consume events asynchronously\n * for await (const event of eventStream) {\n * console.log(`Event: ${event.event}, Data: ${event.data}`);\n *\n * // Handle different event types\n * switch (event.event) {\n * case 'message':\n * handleMessage(event.data);\n * break;\n * case 'error':\n * handleError(event.data);\n * break;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Handle conversion errors\n * try {\n * const eventStream = toServerSentEventStream(response);\n * // Use the stream...\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Event stream conversion failed:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new EventStreamConvertError(response, 'Response body is null');\n }\n\n return response.body\n .pipeThrough(new TextDecoderStream('utf-8'))\n .pipeThrough(new TextLineTransformStream())\n .pipeThrough(new ServerSentEventTransformStream());\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type ServerSentEvent } from './serverSentEventTransformStream';\nimport type { ServerSentEventStream } from './eventStreamConverter';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * This detector function is called for each incoming ServerSentEvent. If it returns true,\n * the stream transformation will be terminated, preventing further events from being processed.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n *\n * @example\n * ```typescript\n * const terminateOnDone: TerminateDetector = (event) => {\n * return event.event === 'done' || event.data === '[DONE]';\n * };\n * ```\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\n *\n * This interface extends the base ServerSentEvent but replaces the string 'data' field\n * with a parsed JSON object of the specified generic type. This allows for type-safe\n * access to the event payload.\n *\n * @template DATA - The expected type of the parsed JSON data\n */\nexport interface JsonServerSentEvent<DATA> extends Omit<\n ServerSentEvent,\n 'data'\n> {\n /** The parsed JSON data from the event */\n data: DATA;\n}\n\n/**\n * A TransformStream transformer that converts ServerSentEvent to JsonServerSentEvent with optional termination detection.\n *\n * This transformer parses the JSON data from ServerSentEvent chunks and optionally terminates\n * the stream when a termination condition is met. It's designed to work within a TransformStream\n * to convert raw server-sent events into typed JSON events.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransform<DATA> implements Transformer<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransform instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * If provided, this function is called for each event and can terminate\n * the stream by returning true.\n */\n constructor(private readonly terminateDetector?: TerminateDetector) {}\n\n /**\n * Transforms a ServerSentEvent chunk into a JsonServerSentEvent.\n *\n * This method first checks if the event should terminate the stream using the terminateDetector.\n * If termination is required, the controller is terminated. Otherwise, the event data is parsed\n * as JSON and enqueued as a JsonServerSentEvent.\n *\n * If the terminateDetector throws an exception, the stream is terminated with an error to prevent\n * corrupted state.\n *\n * @param chunk - The ServerSentEvent to transform\n * @param controller - The TransformStream controller for managing the stream\n * @throws {SyntaxError} If the event data is not valid JSON\n * @throws {Error} If the terminateDetector throws an exception\n *\n * @example\n * ```typescript\n * const transformer = new JsonServerSentEventTransform<MyData>();\n * // This will be called automatically by the TransformStream\n * ```\n */\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ) {\n try {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n controller.terminate();\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n controller.enqueue({\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n } catch (error) {\n // If terminate detector throws or JSON parsing fails, terminate the stream to prevent corrupted state\n controller.error(error);\n return;\n }\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to JsonServerSentEvent streams with optional termination detection.\n *\n * This class extends TransformStream to provide a convenient way to transform streams of ServerSentEvent\n * objects into streams of JsonServerSentEvent objects. It supports optional termination detection to\n * automatically end the stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransformStream instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * When provided, the stream will automatically terminate when this\n * function returns true for any event.\n *\n * @example\n * ```typescript\n * // Create a stream that terminates on 'done' events\n * const terminateOnDone: TerminateDetector = (event) => event.event === 'done';\n * const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);\n *\n * // Create a stream without termination detection\n * const basicStream = new JsonServerSentEventTransformStream<MyData>();\n * ```\n */\n constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\n *\n * This type represents a stream that yields parsed JSON server-sent events.\n * Each chunk in the stream contains the event metadata along with parsed JSON data.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\n/**\n * Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.\n *\n * This function takes a stream of raw server-sent events and transforms it into a stream of\n * parsed JSON events. It optionally accepts a termination detector to automatically end the\n * stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform\n * @param terminateDetector - Optional function to detect when the stream should be terminated\n * @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data\n * @throws {SyntaxError} If any event data is not valid JSON (thrown during stream consumption)\n *\n * @example\n * ```typescript\n * // Basic usage without termination detection\n * const jsonStream = toJsonServerSentEventStream<MyData>(serverSentEventStream);\n *\n * // With termination detection\n * const terminateOnDone: TerminateDetector = (event) => event.data === '[DONE]';\n * const terminatingStream = toJsonServerSentEventStream<MyData>(\n * serverSentEventStream,\n * terminateOnDone\n * );\n *\n * // Consume the stream\n * for await (const event of jsonStream) {\n * console.log('Received:', event.data);\n * console.log('Event type:', event.event);\n * }\n * ```\n */\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n terminateDetector?: TerminateDetector,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(terminateDetector),\n );\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { FetchExchange, ResultExtractor } from '@ahoo-wang/fetcher';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport type { JsonServerSentEventStream } from './jsonServerSentEventTransformStream';\n\n/**\n * ServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a ServerSentEventStream from an HTTP response that contains Server-Sent Events.\n * The extractor validates that the response supports event streaming and converts the\n * response body into a properly typed event stream.\n *\n * This extractor should be used when you want to consume raw Server-Sent Events\n * without JSON parsing, maintaining the original string data format.\n *\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {ExchangeError} When the server response does not support ServerSentEventStream\n * (e.g., wrong content type, no response body)\n *\n *\n * @see {@link ServerSentEventStream} for the stream type\n * @see {@link JsonEventStreamResultExtractor} for JSON-parsed event streams\n */\nexport const EventStreamResultExtractor: ResultExtractor<\n ServerSentEventStream\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredEventStream();\n};\n\n/**\n * JsonServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a JsonServerSentEventStream from an HTTP response that contains Server-Sent Events\n * with JSON data. The extractor validates that the response supports event streaming and converts\n * the response body into a properly typed event stream with automatic JSON parsing.\n *\n * This extractor should be used when you want to consume Server-Sent Events where the event\n * data is JSON-formatted, providing type-safe access to parsed JSON objects instead of raw strings.\n *\n * @template DATA - The expected type of the JSON data in the server-sent events\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects with parsed JSON data as they are received\n * @throws {ExchangeError} When the server response does not support JsonServerSentEventStream\n * (e.g., wrong content type, no response body, invalid JSON)\n *\n *\n * @see {@link JsonServerSentEventStream} for the stream type with JSON data\n * @see {@link EventStreamResultExtractor} for raw string event streams\n */\nexport const JsonEventStreamResultExtractor: ResultExtractor<\n JsonServerSentEventStream<any>\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredJsonEventStream();\n};\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EventStreamConvertError,\n type ServerSentEventStream,\n toServerSentEventStream,\n} from './eventStreamConverter';\nimport {\n type JsonServerSentEventStream,\n type TerminateDetector,\n toJsonServerSentEventStream,\n} from './jsonServerSentEventTransformStream';\nimport { CONTENT_TYPE_HEADER, ContentTypeValues } from '@ahoo-wang/fetcher';\n\ndeclare global {\n interface Response {\n /**\n * Gets the content type of the response.\n *\n * This property provides access to the Content-Type header of the response,\n * which indicates the media type of the resource transmitted in the response.\n *\n * @returns The content type header value as a string, or null if the header is not set\n */\n get contentType(): string | null;\n\n /**\n * Checks if the response is an event stream.\n *\n * This property examines the Content-Type header to determine if the response\n * contains server-sent events data (text/event-stream).\n *\n * @returns true if the response is an event stream, false otherwise\n */\n get isEventStream(): boolean;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects, or null if not an event stream\n */\n eventStream(): ServerSentEventStream | null;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is similar to eventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws {Error} if the event stream is not available\n */\n requiredEventStream(): ServerSentEventStream;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data, or null if not an event stream\n */\n jsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA> | null;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is similar to jsonEventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream with JSON data.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data\n * @throws {Error} if the event stream is not available\n */\n requiredJsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA>;\n }\n}\n\nif (typeof Response !== 'undefined') {\n const CONTENT_TYPE_PROPERTY_NAME = 'contentType';\n /**\n * Defines the contentType property on Response prototype.\n * This property provides a convenient way to access the Content-Type header value.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n CONTENT_TYPE_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, CONTENT_TYPE_PROPERTY_NAME, {\n get() {\n return this.headers.get(CONTENT_TYPE_HEADER);\n },\n configurable: true,\n });\n }\n\n const IS_EVENT_STREAM_PROPERTY_NAME = 'isEventStream';\n /**\n * Defines the isEventStream property on Response prototype.\n * This property checks if the response has a Content-Type header indicating it's an event stream.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n IS_EVENT_STREAM_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, IS_EVENT_STREAM_PROPERTY_NAME, {\n get() {\n const contentType = this.contentType;\n if (!contentType) {\n return false;\n }\n return contentType.includes(ContentTypeValues.TEXT_EVENT_STREAM);\n },\n configurable: true,\n });\n }\n\n /**\n * Implementation of the eventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream.\n *\n * @returns A ServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'eventStream')\n ) {\n Response.prototype.eventStream = function () {\n if (!this.isEventStream) {\n return null;\n }\n return toServerSentEventStream(this);\n };\n }\n\n /**\n * Implementation of the requiredEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @returns A ServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredEventStream',\n )\n ) {\n Response.prototype.requiredEventStream = function () {\n const eventStream = this.eventStream();\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n\n /**\n * Implementation of the jsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'jsonEventStream')\n ) {\n Response.prototype.jsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.eventStream();\n if (!eventStream) {\n return null;\n }\n return toJsonServerSentEventStream<DATA>(eventStream, terminateDetector);\n };\n }\n\n /**\n * Implementation of the requiredJsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredJsonEventStream',\n )\n ) {\n Response.prototype.requiredJsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.jsonEventStream<DATA>(terminateDetector);\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper class that converts a ReadableStream into an AsyncIterable.\n *\n * This class enables the use of ReadableStream objects with async iteration syntax\n * (for-await...of loops), providing a more ergonomic way to consume streaming data.\n * It implements the AsyncIterable interface and manages the underlying stream reader,\n * handling proper resource cleanup and error propagation.\n *\n * The wrapper automatically handles stream locking, ensuring that only one consumer\n * can read from the stream at a time, and provides safe cleanup when iteration ends\n * or errors occur.\n *\n * @template T - The type of data yielded by the stream\n *\n * @example\n * ```typescript\n * // Direct usage\n * const response = await fetch('/api/stream');\n * const stream = response.body;\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * console.log('Received:', chunk);\n * }\n * // Stream is automatically cleaned up after iteration\n * ```\n *\n * @example\n * ```typescript\n * // With early termination\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * if (someCondition) {\n * asyncIterable.releaseLock(); // Manually release if needed\n * break;\n * }\n * }\n * ```\n */\nexport class ReadableStreamAsyncIterable<T> implements AsyncIterable<T> {\n private readonly reader: ReadableStreamDefaultReader<T>;\n private _locked: boolean = true;\n\n /**\n * Creates a new ReadableStreamAsyncIterable instance.\n * @param stream - The ReadableStream to wrap.\n */\n constructor(private readonly stream: ReadableStream<T>) {\n this.reader = stream.getReader();\n }\n\n /**\n * Gets the lock status of the reader.\n * @returns True if the reader is currently locked, false otherwise.\n */\n get locked(): boolean {\n return this._locked;\n }\n\n /**\n * Releases the reader lock if currently locked.\n * This method safely releases the reader lock by catching any potential errors.\n */\n releaseLock() {\n if (!this._locked) return false;\n this._locked = false;\n try {\n this.reader.releaseLock();\n return true;\n } catch (error) {\n console.debug('Failed to release reader lock:', error);\n return false;\n }\n }\n\n /**\n * Implements the AsyncIterable interface by returning this iterator.\n * @returns The async iterator for this instance.\n */\n [Symbol.asyncIterator]() {\n return this;\n }\n\n /**\n * Gets the next value from the stream.\n * Reads the next chunk from the stream and returns it as an IteratorResult.\n * If the stream is done, releases the lock and returns a done result.\n * @returns A promise that resolves to an IteratorResult containing the next value or done status.\n * @throws If an error occurs while reading from the stream.\n */\n async next(): Promise<IteratorResult<T>> {\n try {\n const { done, value } = await this.reader.read();\n if (done) {\n this.releaseLock();\n return { done: true, value: undefined };\n }\n\n return { done: false, value };\n } catch (error) {\n this.releaseLock();\n throw error;\n }\n }\n\n /**\n * Implements the return method of the async iterator.\n * Cancels the stream reader and releases the lock.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async return(): Promise<IteratorResult<T>> {\n try {\n await this.reader.cancel();\n } catch (error) {\n console.debug('Failed to cancel stream reader:', error);\n } finally {\n this.releaseLock();\n }\n return { done: true, value: undefined };\n }\n\n /**\n * Implements the throw method of the async iterator.\n * Releases the lock and returns a done result.\n * @param error - The error to be thrown.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async throw(error: any): Promise<IteratorResult<T>> {\n // Ensure the reader lock is released before throwing\n console.debug('Throwing error:', error);\n this.releaseLock();\n return { done: true, value: undefined };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReadableStreamAsyncIterable } from './readableStreamAsyncIterable';\n\n/**\n * Checks if the current environment natively supports async iteration on ReadableStream.\n *\n * This constant determines whether the browser or runtime already provides\n * built-in support for using ReadableStream with for-await...of loops.\n * If not supported, this library will polyfill the functionality by adding\n * the [Symbol.asyncIterator] method to ReadableStream.prototype.\n *\n * @returns true if native async iteration is supported, false if polyfill is needed\n *\n * @example\n * ```typescript\n * import { isReadableStreamAsyncIterableSupported } from '@ahoo-wang/fetcher-eventstream';\n *\n * if (isReadableStreamAsyncIterableSupported) {\n * console.log('Native support available');\n * } else {\n * console.log('Using polyfill');\n * }\n * ```\n */\nexport const isReadableStreamAsyncIterableSupported =\n typeof ReadableStream !== 'undefined' &&\n typeof ReadableStream.prototype[Symbol.asyncIterator] === 'function';\n\n// Add [Symbol.asyncIterator] to ReadableStream if not already implemented\nif (!isReadableStreamAsyncIterableSupported) {\n ReadableStream.prototype[Symbol.asyncIterator] = function <R = any>() {\n return new ReadableStreamAsyncIterable<R>(this as ReadableStream<R>);\n };\n}\n"],"mappings":"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"}

@@ -1,1 +0,1 @@

{"version":3,"file":"index.umd.js","names":[],"sources":["../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/eventStreamResultExtractor.ts","../src/responses.ts","../src/readableStreamAsyncIterable.ts","../src/readableStreams.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Transformer that splits text into lines.\n *\n * This transformer accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. It handles partial lines that span multiple\n * input chunks by maintaining an internal buffer. Lines are emitted without the newline character.\n *\n * The transformer handles various edge cases:\n * - Lines that span multiple input chunks\n * - Empty lines (emitted as empty strings)\n * - Text without trailing newlines (buffered until stream ends)\n * - Mixed line endings (only '\\n' is recognized as line separator)\n *\n * @implements {Transformer<string, string>}\n *\n * @example\n * ```typescript\n * const transformer = new TextLineTransformer();\n * // Input: \"line1\\nline2\\npartial\"\n * // Output: [\"line1\", \"line2\"]\n * // Buffer: \"partial\"\n *\n * // Later input: \"line\\n\"\n * // Output: [\"partialline\"]\n * // Buffer: \"\"\n * ```\n */\nexport class TextLineTransformer implements Transformer<string, string> {\n private buffer = '';\n\n /**\n * Transform input string chunk by splitting it into lines.\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ) {\n try {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n controller.enqueue(line);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n\n /**\n * Flush remaining buffer when the stream ends.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<string>) {\n try {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n controller.enqueue(this.buffer);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * This class provides a convenient way to transform a stream of text chunks into a stream\n * of individual lines. It wraps the TextLineTransformer in a TransformStream for easy\n * integration with other stream processing pipelines.\n *\n * The stream processes text data and emits each line as a separate chunk, handling\n * lines that may span multiple input chunks automatically.\n *\n * @example\n * ```typescript\n * // Create a line-splitting stream\n * const lineStream = new TextLineTransformStream();\n *\n * // Pipe text through it\n * const lines = textStream.pipeThrough(lineStream);\n *\n * // Process each line\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Process SSE response line by line\n * const response = await fetch('/api/stream');\n * const lines = response.body!\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(new TextLineTransformStream());\n *\n * for await (const line of lines) {\n * if (line.startsWith('data: ')) {\n * console.log('SSE data:', line.substring(6));\n * }\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n /**\n * Creates a new TextLineTransformStream instance.\n *\n * Initializes the stream with a TextLineTransformer that handles the line splitting logic.\n */\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * This interface defines the structure of Server-Sent Events (SSE) as specified by the W3C.\n * Each event contains metadata and data that can be processed by clients to handle real-time\n * updates from the server.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\n/**\n * Constants for Server-Sent Event field names.\n *\n * This class provides string constants for the standard SSE field names as defined\n * in the W3C Server-Sent Events specification. These constants help ensure\n * consistent field name usage throughout the parsing logic.\n */\nexport class ServerSentEventFields {\n /** The field name for event ID */\n static readonly ID = 'id';\n /** The field name for retry interval */\n static readonly RETRY = 'retry';\n /** The field name for event type */\n static readonly EVENT = 'event';\n /** The field name for event data */\n static readonly DATA = 'data';\n}\n\n/**\n * Processes a field-value pair and updates the current event state accordingly.\n *\n * This internal function handles the parsing of individual SSE fields according to the\n * Server-Sent Events specification. It updates the provided event state object with\n * the parsed field values.\n *\n * @param field - The field name (e.g., 'event', 'data', 'id', 'retry')\n * @param value - The field value as a string\n * @param currentEvent - The current event state object to update\n *\n * @example\n * ```typescript\n * const eventState: EventState = { event: 'message', data: [] };\n * processFieldInternal('event', 'custom', eventState);\n * // eventState.event is now 'custom'\n * ```\n */\nfunction processFieldInternal(\n field: string,\n value: string,\n currentEvent: EventState,\n) {\n switch (field) {\n case ServerSentEventFields.EVENT:\n currentEvent.event = value;\n break;\n case ServerSentEventFields.DATA:\n currentEvent.data.push(value);\n break;\n case ServerSentEventFields.ID:\n currentEvent.id = value;\n break;\n case ServerSentEventFields.RETRY: {\n const retryValue = parseInt(value, 10);\n if (!isNaN(retryValue)) {\n currentEvent.retry = retryValue;\n }\n break;\n }\n default:\n // Ignore unknown fields\n break;\n }\n}\n\n/**\n * Internal state representation during Server-Sent Event parsing.\n *\n * This interface tracks the current state of an event being parsed from the SSE stream.\n * It accumulates field values until a complete event is ready to be emitted.\n */\ninterface EventState {\n /** The event type (defaults to 'message') */\n event?: string;\n /** The event ID */\n id?: string;\n /** The retry interval in milliseconds */\n retry?: number;\n /** Array of data lines that will be joined with newlines */\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n * This transformer handles the parsing of Server-Sent Events (SSE) according to the W3C specification.\n * It processes incoming text chunks and converts them into structured ServerSentEvent objects.\n */\nexport class ServerSentEventTransformer implements Transformer<\n string,\n ServerSentEvent\n> {\n // Initialize currentEventState with default values in a closure\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Reset the current event state to default values.\n * This method is called after processing each complete event or when an error occurs.\n */\n private resetEventState() {\n this.currentEventState.event = DEFAULT_EVENT_TYPE;\n this.currentEventState.id = undefined;\n this.currentEventState.retry = undefined;\n this.currentEventState.data = [];\n }\n\n /**\n * Transform input string chunk into ServerSentEvent object.\n * This method processes individual chunks of text data, parsing them according to the SSE format.\n * It handles:\n * - Empty lines (used as event separators)\n * - Comment lines (starting with ':')\n * - Field lines (field: value format)\n * - Event completion and emission\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ) {\n const currentEvent = this.currentEventState;\n try {\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n // If there is accumulated event data, send event\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n\n // Reset current event (preserve id and retry for subsequent events)\n currentEvent.event = DEFAULT_EVENT_TYPE;\n // Preserve id and retry for subsequent events (no need to reassign to themselves)\n currentEvent.data = [];\n }\n return;\n }\n\n // Ignore comment lines (starting with colon)\n if (chunk.startsWith(':')) {\n return;\n }\n\n // Parse fields\n const colonIndex = chunk.indexOf(':');\n let field: string;\n let value: string;\n\n if (colonIndex === -1) {\n // No colon, entire line as field name, value is empty\n field = chunk.toLowerCase();\n value = '';\n } else {\n // Extract field name and value\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n\n // If value starts with space, remove leading space\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n // Remove trailing newlines from field and value\n field = field.trim();\n value = value.trim();\n\n processFieldInternal(field, value, currentEvent);\n } catch (error) {\n const enhancedError = new Error(\n `Failed to process chunk: \"${chunk}\". ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n // Reset state\n this.resetEventState();\n }\n }\n\n /**\n * Called when the stream ends, used to process remaining data.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<ServerSentEvent>) {\n const currentEvent = this.currentEventState;\n try {\n // Send the last event (if any)\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n }\n } catch (error) {\n const enhancedError = new Error(\n `Failed to flush remaining data. ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n } finally {\n // Reset state\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n *\n * This class provides a convenient way to transform raw text streams containing Server-Sent Events\n * into structured event objects. It wraps the ServerSentEventTransformer in a TransformStream\n * for easy integration with other stream processing pipelines.\n *\n * The stream processes SSE format text and emits ServerSentEvent objects as they are completed.\n * Events are separated by empty lines, and the stream handles partial events across multiple chunks.\n *\n * @example\n * ```typescript\n * // Create a transform stream\n * const sseStream = new ServerSentEventTransformStream();\n *\n * // Pipe a text stream through it\n * const eventStream = textStream.pipeThrough(sseStream);\n *\n * // Consume the events\n * for await (const event of eventStream) {\n * console.log('Event:', event.event, 'Data:', event.data);\n * }\n * ```\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n /**\n * Creates a new ServerSentEventTransformStream instance.\n *\n * Initializes the stream with a ServerSentEventTransformer that handles\n * the parsing of SSE format text into structured events.\n */\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n type ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n *\n * This type represents a stream that yields Server-Sent Event objects as they are parsed\n * from a raw event stream. Each chunk in the stream contains a complete SSE event with\n * its metadata (event type, ID, retry interval) and data.\n *\n * @see {@link ServerSentEvent} for the structure of individual events\n * @see {@link toServerSentEventStream} for converting HTTP responses to this type\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n *\n * This error is thrown when there are issues converting an HTTP Response to a ServerSentEventStream.\n * It extends FetcherError to provide additional context about the failed conversion, including\n * the original Response object and any underlying cause.\n *\n * @extends {FetcherError}\n *\n * @example\n * ```typescript\n * try {\n * const eventStream = toServerSentEventStream(response);\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Failed to convert response to event stream:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n *\n * @param response - The Response object associated with the error, providing context\n * about the failed conversion (status, headers, etc.)\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this conversion error\n */\n constructor(\n public readonly response: Response,\n errorMsg?: string,\n cause?: Error | any,\n ) {\n super(errorMsg, cause);\n this.name = 'EventStreamConvertError';\n // Restore prototype chain for proper inheritance\n Object.setPrototypeOf(this, EventStreamConvertError.prototype);\n }\n}\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * This function takes an HTTP Response object and converts its body into a stream of\n * Server-Sent Event objects. The conversion process involves several transformation steps:\n *\n * 1. **TextDecoderStream**: Decodes the raw Uint8Array response body to UTF-8 strings\n * 2. **TextLineTransformStream**: Splits the text stream into individual lines\n * 3. **ServerSentEventTransformStream**: Parses the line-based SSE format into structured events\n *\n * The resulting stream can be consumed using async iteration or other stream methods.\n *\n * @param response - The HTTP Response object to convert. Must have a readable body stream.\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {EventStreamConvertError} If the response body is null or cannot be processed\n *\n * @example\n * ```typescript\n * // Convert an SSE response to an event stream\n * const response = await fetch('/api/events');\n * const eventStream = toServerSentEventStream(response);\n *\n * // Consume events asynchronously\n * for await (const event of eventStream) {\n * console.log(`Event: ${event.event}, Data: ${event.data}`);\n *\n * // Handle different event types\n * switch (event.event) {\n * case 'message':\n * handleMessage(event.data);\n * break;\n * case 'error':\n * handleError(event.data);\n * break;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Handle conversion errors\n * try {\n * const eventStream = toServerSentEventStream(response);\n * // Use the stream...\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Event stream conversion failed:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new EventStreamConvertError(response, 'Response body is null');\n }\n\n return response.body\n .pipeThrough(new TextDecoderStream('utf-8'))\n .pipeThrough(new TextLineTransformStream())\n .pipeThrough(new ServerSentEventTransformStream());\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type ServerSentEvent } from './serverSentEventTransformStream';\nimport type { ServerSentEventStream } from './eventStreamConverter';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * This detector function is called for each incoming ServerSentEvent. If it returns true,\n * the stream transformation will be terminated, preventing further events from being processed.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n *\n * @example\n * ```typescript\n * const terminateOnDone: TerminateDetector = (event) => {\n * return event.event === 'done' || event.data === '[DONE]';\n * };\n * ```\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\n *\n * This interface extends the base ServerSentEvent but replaces the string 'data' field\n * with a parsed JSON object of the specified generic type. This allows for type-safe\n * access to the event payload.\n *\n * @template DATA - The expected type of the parsed JSON data\n */\nexport interface JsonServerSentEvent<DATA> extends Omit<\n ServerSentEvent,\n 'data'\n> {\n /** The parsed JSON data from the event */\n data: DATA;\n}\n\n/**\n * A TransformStream transformer that converts ServerSentEvent to JsonServerSentEvent with optional termination detection.\n *\n * This transformer parses the JSON data from ServerSentEvent chunks and optionally terminates\n * the stream when a termination condition is met. It's designed to work within a TransformStream\n * to convert raw server-sent events into typed JSON events.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransform<DATA> implements Transformer<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransform instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * If provided, this function is called for each event and can terminate\n * the stream by returning true.\n */\n constructor(private readonly terminateDetector?: TerminateDetector) {}\n\n /**\n * Transforms a ServerSentEvent chunk into a JsonServerSentEvent.\n *\n * This method first checks if the event should terminate the stream using the terminateDetector.\n * If termination is required, the controller is terminated. Otherwise, the event data is parsed\n * as JSON and enqueued as a JsonServerSentEvent.\n *\n * If the terminateDetector throws an exception, the stream is terminated with an error to prevent\n * corrupted state.\n *\n * @param chunk - The ServerSentEvent to transform\n * @param controller - The TransformStream controller for managing the stream\n * @throws {SyntaxError} If the event data is not valid JSON\n * @throws {Error} If the terminateDetector throws an exception\n *\n * @example\n * ```typescript\n * const transformer = new JsonServerSentEventTransform<MyData>();\n * // This will be called automatically by the TransformStream\n * ```\n */\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ) {\n try {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n controller.terminate();\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n controller.enqueue({\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n } catch (error) {\n // If terminate detector throws or JSON parsing fails, terminate the stream to prevent corrupted state\n controller.error(error);\n return;\n }\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to JsonServerSentEvent streams with optional termination detection.\n *\n * This class extends TransformStream to provide a convenient way to transform streams of ServerSentEvent\n * objects into streams of JsonServerSentEvent objects. It supports optional termination detection to\n * automatically end the stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransformStream instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * When provided, the stream will automatically terminate when this\n * function returns true for any event.\n *\n * @example\n * ```typescript\n * // Create a stream that terminates on 'done' events\n * const terminateOnDone: TerminateDetector = (event) => event.event === 'done';\n * const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);\n *\n * // Create a stream without termination detection\n * const basicStream = new JsonServerSentEventTransformStream<MyData>();\n * ```\n */\n constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\n *\n * This type represents a stream that yields parsed JSON server-sent events.\n * Each chunk in the stream contains the event metadata along with parsed JSON data.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\n/**\n * Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.\n *\n * This function takes a stream of raw server-sent events and transforms it into a stream of\n * parsed JSON events. It optionally accepts a termination detector to automatically end the\n * stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform\n * @param terminateDetector - Optional function to detect when the stream should be terminated\n * @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data\n * @throws {SyntaxError} If any event data is not valid JSON (thrown during stream consumption)\n *\n * @example\n * ```typescript\n * // Basic usage without termination detection\n * const jsonStream = toJsonServerSentEventStream<MyData>(serverSentEventStream);\n *\n * // With termination detection\n * const terminateOnDone: TerminateDetector = (event) => event.data === '[DONE]';\n * const terminatingStream = toJsonServerSentEventStream<MyData>(\n * serverSentEventStream,\n * terminateOnDone\n * );\n *\n * // Consume the stream\n * for await (const event of jsonStream) {\n * console.log('Received:', event.data);\n * console.log('Event type:', event.event);\n * }\n * ```\n */\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n terminateDetector?: TerminateDetector,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(terminateDetector),\n );\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { FetchExchange, ResultExtractor } from '@ahoo-wang/fetcher';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport type { JsonServerSentEventStream } from './jsonServerSentEventTransformStream';\n\n/**\n * ServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a ServerSentEventStream from an HTTP response that contains Server-Sent Events.\n * The extractor validates that the response supports event streaming and converts the\n * response body into a properly typed event stream.\n *\n * This extractor should be used when you want to consume raw Server-Sent Events\n * without JSON parsing, maintaining the original string data format.\n *\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {ExchangeError} When the server response does not support ServerSentEventStream\n * (e.g., wrong content type, no response body)\n *\n *\n * @see {@link ServerSentEventStream} for the stream type\n * @see {@link JsonEventStreamResultExtractor} for JSON-parsed event streams\n */\nexport const EventStreamResultExtractor: ResultExtractor<\n ServerSentEventStream\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredEventStream();\n};\n\n/**\n * JsonServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a JsonServerSentEventStream from an HTTP response that contains Server-Sent Events\n * with JSON data. The extractor validates that the response supports event streaming and converts\n * the response body into a properly typed event stream with automatic JSON parsing.\n *\n * This extractor should be used when you want to consume Server-Sent Events where the event\n * data is JSON-formatted, providing type-safe access to parsed JSON objects instead of raw strings.\n *\n * @template DATA - The expected type of the JSON data in the server-sent events\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects with parsed JSON data as they are received\n * @throws {ExchangeError} When the server response does not support JsonServerSentEventStream\n * (e.g., wrong content type, no response body, invalid JSON)\n *\n *\n * @see {@link JsonServerSentEventStream} for the stream type with JSON data\n * @see {@link EventStreamResultExtractor} for raw string event streams\n */\nexport const JsonEventStreamResultExtractor: ResultExtractor<\n JsonServerSentEventStream<any>\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredJsonEventStream();\n};\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EventStreamConvertError,\n type ServerSentEventStream,\n toServerSentEventStream,\n} from './eventStreamConverter';\nimport {\n type JsonServerSentEventStream,\n type TerminateDetector,\n toJsonServerSentEventStream,\n} from './jsonServerSentEventTransformStream';\nimport { CONTENT_TYPE_HEADER, ContentTypeValues } from '@ahoo-wang/fetcher';\n\ndeclare global {\n interface Response {\n /**\n * Gets the content type of the response.\n *\n * This property provides access to the Content-Type header of the response,\n * which indicates the media type of the resource transmitted in the response.\n *\n * @returns The content type header value as a string, or null if the header is not set\n */\n get contentType(): string | null;\n\n /**\n * Checks if the response is an event stream.\n *\n * This property examines the Content-Type header to determine if the response\n * contains server-sent events data (text/event-stream).\n *\n * @returns true if the response is an event stream, false otherwise\n */\n get isEventStream(): boolean;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects, or null if not an event stream\n */\n eventStream(): ServerSentEventStream | null;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is similar to eventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws {Error} if the event stream is not available\n */\n requiredEventStream(): ServerSentEventStream;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data, or null if not an event stream\n */\n jsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA> | null;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is similar to jsonEventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream with JSON data.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data\n * @throws {Error} if the event stream is not available\n */\n requiredJsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA>;\n }\n}\n\nif (typeof Response !== 'undefined') {\n const CONTENT_TYPE_PROPERTY_NAME = 'contentType';\n /**\n * Defines the contentType property on Response prototype.\n * This property provides a convenient way to access the Content-Type header value.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n CONTENT_TYPE_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, CONTENT_TYPE_PROPERTY_NAME, {\n get() {\n return this.headers.get(CONTENT_TYPE_HEADER);\n },\n configurable: true,\n });\n }\n\n const IS_EVENT_STREAM_PROPERTY_NAME = 'isEventStream';\n /**\n * Defines the isEventStream property on Response prototype.\n * This property checks if the response has a Content-Type header indicating it's an event stream.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n IS_EVENT_STREAM_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, IS_EVENT_STREAM_PROPERTY_NAME, {\n get() {\n const contentType = this.contentType;\n if (!contentType) {\n return false;\n }\n return contentType.includes(ContentTypeValues.TEXT_EVENT_STREAM);\n },\n configurable: true,\n });\n }\n\n /**\n * Implementation of the eventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream.\n *\n * @returns A ServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'eventStream')\n ) {\n Response.prototype.eventStream = function () {\n if (!this.isEventStream) {\n return null;\n }\n return toServerSentEventStream(this);\n };\n }\n\n /**\n * Implementation of the requiredEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @returns A ServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredEventStream',\n )\n ) {\n Response.prototype.requiredEventStream = function () {\n const eventStream = this.eventStream();\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n\n /**\n * Implementation of the jsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'jsonEventStream')\n ) {\n Response.prototype.jsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.eventStream();\n if (!eventStream) {\n return null;\n }\n return toJsonServerSentEventStream<DATA>(eventStream, terminateDetector);\n };\n }\n\n /**\n * Implementation of the requiredJsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredJsonEventStream',\n )\n ) {\n Response.prototype.requiredJsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.jsonEventStream<DATA>(terminateDetector);\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper class that converts a ReadableStream into an AsyncIterable.\n *\n * This class enables the use of ReadableStream objects with async iteration syntax\n * (for-await...of loops), providing a more ergonomic way to consume streaming data.\n * It implements the AsyncIterable interface and manages the underlying stream reader,\n * handling proper resource cleanup and error propagation.\n *\n * The wrapper automatically handles stream locking, ensuring that only one consumer\n * can read from the stream at a time, and provides safe cleanup when iteration ends\n * or errors occur.\n *\n * @template T - The type of data yielded by the stream\n *\n * @example\n * ```typescript\n * // Direct usage\n * const response = await fetch('/api/stream');\n * const stream = response.body;\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * console.log('Received:', chunk);\n * }\n * // Stream is automatically cleaned up after iteration\n * ```\n *\n * @example\n * ```typescript\n * // With early termination\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * if (someCondition) {\n * asyncIterable.releaseLock(); // Manually release if needed\n * break;\n * }\n * }\n * ```\n */\nexport class ReadableStreamAsyncIterable<T> implements AsyncIterable<T> {\n private readonly reader: ReadableStreamDefaultReader<T>;\n private _locked: boolean = true;\n\n /**\n * Creates a new ReadableStreamAsyncIterable instance.\n * @param stream - The ReadableStream to wrap.\n */\n constructor(private readonly stream: ReadableStream<T>) {\n this.reader = stream.getReader();\n }\n\n /**\n * Gets the lock status of the reader.\n * @returns True if the reader is currently locked, false otherwise.\n */\n get locked(): boolean {\n return this._locked;\n }\n\n /**\n * Releases the reader lock if currently locked.\n * This method safely releases the reader lock by catching any potential errors.\n */\n releaseLock() {\n if (!this._locked) return false;\n this._locked = false;\n try {\n this.reader.releaseLock();\n return true;\n } catch (error) {\n console.debug('Failed to release reader lock:', error);\n return false;\n }\n }\n\n /**\n * Implements the AsyncIterable interface by returning this iterator.\n * @returns The async iterator for this instance.\n */\n [Symbol.asyncIterator]() {\n return this;\n }\n\n /**\n * Gets the next value from the stream.\n * Reads the next chunk from the stream and returns it as an IteratorResult.\n * If the stream is done, releases the lock and returns a done result.\n * @returns A promise that resolves to an IteratorResult containing the next value or done status.\n * @throws If an error occurs while reading from the stream.\n */\n async next(): Promise<IteratorResult<T>> {\n try {\n const { done, value } = await this.reader.read();\n if (done) {\n this.releaseLock();\n return { done: true, value: undefined };\n }\n\n return { done: false, value };\n } catch (error) {\n this.releaseLock();\n throw error;\n }\n }\n\n /**\n * Implements the return method of the async iterator.\n * Cancels the stream reader and releases the lock.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async return(): Promise<IteratorResult<T>> {\n try {\n await this.reader.cancel();\n } catch (error) {\n console.debug('Failed to cancel stream reader:', error);\n } finally {\n this.releaseLock();\n }\n return { done: true, value: undefined };\n }\n\n /**\n * Implements the throw method of the async iterator.\n * Releases the lock and returns a done result.\n * @param error - The error to be thrown.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async throw(error: any): Promise<IteratorResult<T>> {\n // Ensure the reader lock is released before throwing\n console.debug('Throwing error:', error);\n this.releaseLock();\n return { done: true, value: undefined };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReadableStreamAsyncIterable } from './readableStreamAsyncIterable';\n\ndeclare global {\n interface ReadableStream<R = any> {\n /**\n * Makes ReadableStream async iterable for use with for-await loops.\n *\n * This allows the stream to be consumed using `for await (const chunk of stream)` syntax.\n *\n * @returns An async iterator for the stream\n */\n [Symbol.asyncIterator](): AsyncIterator<R>;\n }\n}\n\n/**\n * Checks if the current environment natively supports async iteration on ReadableStream.\n *\n * This constant determines whether the browser or runtime already provides\n * built-in support for using ReadableStream with for-await...of loops.\n * If not supported, this library will polyfill the functionality by adding\n * the [Symbol.asyncIterator] method to ReadableStream.prototype.\n *\n * @returns true if native async iteration is supported, false if polyfill is needed\n *\n * @example\n * ```typescript\n * import { isReadableStreamAsyncIterableSupported } from '@ahoo-wang/fetcher-eventstream';\n *\n * if (isReadableStreamAsyncIterableSupported) {\n * console.log('Native support available');\n * } else {\n * console.log('Using polyfill');\n * }\n * ```\n */\nexport const isReadableStreamAsyncIterableSupported =\n typeof ReadableStream !== 'undefined' &&\n typeof ReadableStream.prototype[Symbol.asyncIterator] === 'function';\n\n// Add [Symbol.asyncIterator] to ReadableStream if not already implemented\nif (!isReadableStreamAsyncIterableSupported) {\n ReadableStream.prototype[Symbol.asyncIterator] = function <R = any>() {\n return new ReadableStreamAsyncIterable<R>(this as ReadableStream<R>);\n };\n}\n"],"mappings":"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"}
{"version":3,"file":"index.umd.js","names":[],"sources":["../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/eventStreamResultExtractor.ts","../src/responses.ts","../src/readableStreamAsyncIterable.ts","../src/readableStreams.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Transformer that splits text into lines.\n *\n * This transformer accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. It handles partial lines that span multiple\n * input chunks by maintaining an internal buffer. Lines are emitted without the newline character.\n *\n * The transformer handles various edge cases:\n * - Lines that span multiple input chunks\n * - Empty lines (emitted as empty strings)\n * - Text without trailing newlines (buffered until stream ends)\n * - Mixed line endings (only '\\n' is recognized as line separator)\n *\n * @implements {Transformer<string, string>}\n *\n * @example\n * ```typescript\n * const transformer = new TextLineTransformer();\n * // Input: \"line1\\nline2\\npartial\"\n * // Output: [\"line1\", \"line2\"]\n * // Buffer: \"partial\"\n *\n * // Later input: \"line\\n\"\n * // Output: [\"partialline\"]\n * // Buffer: \"\"\n * ```\n */\nexport class TextLineTransformer implements Transformer<string, string> {\n private buffer = '';\n\n /**\n * Transform input string chunk by splitting it into lines.\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ) {\n try {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n controller.enqueue(line);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n\n /**\n * Flush remaining buffer when the stream ends.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<string>) {\n try {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n controller.enqueue(this.buffer);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * This class provides a convenient way to transform a stream of text chunks into a stream\n * of individual lines. It wraps the TextLineTransformer in a TransformStream for easy\n * integration with other stream processing pipelines.\n *\n * The stream processes text data and emits each line as a separate chunk, handling\n * lines that may span multiple input chunks automatically.\n *\n * @example\n * ```typescript\n * // Create a line-splitting stream\n * const lineStream = new TextLineTransformStream();\n *\n * // Pipe text through it\n * const lines = textStream.pipeThrough(lineStream);\n *\n * // Process each line\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Process SSE response line by line\n * const response = await fetch('/api/stream');\n * const lines = response.body!\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(new TextLineTransformStream());\n *\n * for await (const line of lines) {\n * if (line.startsWith('data: ')) {\n * console.log('SSE data:', line.substring(6));\n * }\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n /**\n * Creates a new TextLineTransformStream instance.\n *\n * Initializes the stream with a TextLineTransformer that handles the line splitting logic.\n */\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * This interface defines the structure of Server-Sent Events (SSE) as specified by the W3C.\n * Each event contains metadata and data that can be processed by clients to handle real-time\n * updates from the server.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\n/**\n * Constants for Server-Sent Event field names.\n *\n * This class provides string constants for the standard SSE field names as defined\n * in the W3C Server-Sent Events specification. These constants help ensure\n * consistent field name usage throughout the parsing logic.\n */\nexport class ServerSentEventFields {\n /** The field name for event ID */\n static readonly ID = 'id';\n /** The field name for retry interval */\n static readonly RETRY = 'retry';\n /** The field name for event type */\n static readonly EVENT = 'event';\n /** The field name for event data */\n static readonly DATA = 'data';\n}\n\n/**\n * Processes a field-value pair and updates the current event state accordingly.\n *\n * This internal function handles the parsing of individual SSE fields according to the\n * Server-Sent Events specification. It updates the provided event state object with\n * the parsed field values.\n *\n * @param field - The field name (e.g., 'event', 'data', 'id', 'retry')\n * @param value - The field value as a string\n * @param currentEvent - The current event state object to update\n *\n * @example\n * ```typescript\n * const eventState: EventState = { event: 'message', data: [] };\n * processFieldInternal('event', 'custom', eventState);\n * // eventState.event is now 'custom'\n * ```\n */\nfunction processFieldInternal(\n field: string,\n value: string,\n currentEvent: EventState,\n) {\n switch (field) {\n case ServerSentEventFields.EVENT:\n currentEvent.event = value;\n break;\n case ServerSentEventFields.DATA:\n currentEvent.data.push(value);\n break;\n case ServerSentEventFields.ID:\n currentEvent.id = value;\n break;\n case ServerSentEventFields.RETRY: {\n const retryValue = parseInt(value, 10);\n if (!isNaN(retryValue)) {\n currentEvent.retry = retryValue;\n }\n break;\n }\n default:\n // Ignore unknown fields\n break;\n }\n}\n\n/**\n * Internal state representation during Server-Sent Event parsing.\n *\n * This interface tracks the current state of an event being parsed from the SSE stream.\n * It accumulates field values until a complete event is ready to be emitted.\n */\ninterface EventState {\n /** The event type (defaults to 'message') */\n event?: string;\n /** The event ID */\n id?: string;\n /** The retry interval in milliseconds */\n retry?: number;\n /** Array of data lines that will be joined with newlines */\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n * This transformer handles the parsing of Server-Sent Events (SSE) according to the W3C specification.\n * It processes incoming text chunks and converts them into structured ServerSentEvent objects.\n */\nexport class ServerSentEventTransformer implements Transformer<\n string,\n ServerSentEvent\n> {\n // Initialize currentEventState with default values in a closure\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Reset the current event state to default values.\n * This method is called after processing each complete event or when an error occurs.\n */\n private resetEventState() {\n this.currentEventState.event = DEFAULT_EVENT_TYPE;\n this.currentEventState.id = undefined;\n this.currentEventState.retry = undefined;\n this.currentEventState.data = [];\n }\n\n /**\n * Transform input string chunk into ServerSentEvent object.\n * This method processes individual chunks of text data, parsing them according to the SSE format.\n * It handles:\n * - Empty lines (used as event separators)\n * - Comment lines (starting with ':')\n * - Field lines (field: value format)\n * - Event completion and emission\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ) {\n const currentEvent = this.currentEventState;\n try {\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n // If there is accumulated event data, send event\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n\n // Reset current event (preserve id and retry for subsequent events)\n currentEvent.event = DEFAULT_EVENT_TYPE;\n // Preserve id and retry for subsequent events (no need to reassign to themselves)\n currentEvent.data = [];\n }\n return;\n }\n\n // Ignore comment lines (starting with colon)\n if (chunk.startsWith(':')) {\n return;\n }\n\n // Parse fields\n const colonIndex = chunk.indexOf(':');\n let field: string;\n let value: string;\n\n if (colonIndex === -1) {\n // No colon, entire line as field name, value is empty\n field = chunk.toLowerCase();\n value = '';\n } else {\n // Extract field name and value\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n\n // If value starts with space, remove leading space\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n // Remove trailing newlines from field and value\n field = field.trim();\n value = value.trim();\n\n processFieldInternal(field, value, currentEvent);\n } catch (error) {\n const enhancedError = new Error(\n `Failed to process chunk: \"${chunk}\". ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n // Reset state\n this.resetEventState();\n }\n }\n\n /**\n * Called when the stream ends, used to process remaining data.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<ServerSentEvent>) {\n const currentEvent = this.currentEventState;\n try {\n // Send the last event (if any)\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n }\n } catch (error) {\n const enhancedError = new Error(\n `Failed to flush remaining data. ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n } finally {\n // Reset state\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n *\n * This class provides a convenient way to transform raw text streams containing Server-Sent Events\n * into structured event objects. It wraps the ServerSentEventTransformer in a TransformStream\n * for easy integration with other stream processing pipelines.\n *\n * The stream processes SSE format text and emits ServerSentEvent objects as they are completed.\n * Events are separated by empty lines, and the stream handles partial events across multiple chunks.\n *\n * @example\n * ```typescript\n * // Create a transform stream\n * const sseStream = new ServerSentEventTransformStream();\n *\n * // Pipe a text stream through it\n * const eventStream = textStream.pipeThrough(sseStream);\n *\n * // Consume the events\n * for await (const event of eventStream) {\n * console.log('Event:', event.event, 'Data:', event.data);\n * }\n * ```\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n /**\n * Creates a new ServerSentEventTransformStream instance.\n *\n * Initializes the stream with a ServerSentEventTransformer that handles\n * the parsing of SSE format text into structured events.\n */\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n type ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n *\n * This type represents a stream that yields Server-Sent Event objects as they are parsed\n * from a raw event stream. Each chunk in the stream contains a complete SSE event with\n * its metadata (event type, ID, retry interval) and data.\n *\n * @see {@link ServerSentEvent} for the structure of individual events\n * @see {@link toServerSentEventStream} for converting HTTP responses to this type\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n *\n * This error is thrown when there are issues converting an HTTP Response to a ServerSentEventStream.\n * It extends FetcherError to provide additional context about the failed conversion, including\n * the original Response object and any underlying cause.\n *\n * @extends {FetcherError}\n *\n * @example\n * ```typescript\n * try {\n * const eventStream = toServerSentEventStream(response);\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Failed to convert response to event stream:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n *\n * @param response - The Response object associated with the error, providing context\n * about the failed conversion (status, headers, etc.)\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this conversion error\n */\n constructor(\n public readonly response: Response,\n errorMsg?: string,\n cause?: Error | any,\n ) {\n super(errorMsg, cause);\n this.name = 'EventStreamConvertError';\n // Restore prototype chain for proper inheritance\n Object.setPrototypeOf(this, EventStreamConvertError.prototype);\n }\n}\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * This function takes an HTTP Response object and converts its body into a stream of\n * Server-Sent Event objects. The conversion process involves several transformation steps:\n *\n * 1. **TextDecoderStream**: Decodes the raw Uint8Array response body to UTF-8 strings\n * 2. **TextLineTransformStream**: Splits the text stream into individual lines\n * 3. **ServerSentEventTransformStream**: Parses the line-based SSE format into structured events\n *\n * The resulting stream can be consumed using async iteration or other stream methods.\n *\n * @param response - The HTTP Response object to convert. Must have a readable body stream.\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {EventStreamConvertError} If the response body is null or cannot be processed\n *\n * @example\n * ```typescript\n * // Convert an SSE response to an event stream\n * const response = await fetch('/api/events');\n * const eventStream = toServerSentEventStream(response);\n *\n * // Consume events asynchronously\n * for await (const event of eventStream) {\n * console.log(`Event: ${event.event}, Data: ${event.data}`);\n *\n * // Handle different event types\n * switch (event.event) {\n * case 'message':\n * handleMessage(event.data);\n * break;\n * case 'error':\n * handleError(event.data);\n * break;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Handle conversion errors\n * try {\n * const eventStream = toServerSentEventStream(response);\n * // Use the stream...\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Event stream conversion failed:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new EventStreamConvertError(response, 'Response body is null');\n }\n\n return response.body\n .pipeThrough(new TextDecoderStream('utf-8'))\n .pipeThrough(new TextLineTransformStream())\n .pipeThrough(new ServerSentEventTransformStream());\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type ServerSentEvent } from './serverSentEventTransformStream';\nimport type { ServerSentEventStream } from './eventStreamConverter';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * This detector function is called for each incoming ServerSentEvent. If it returns true,\n * the stream transformation will be terminated, preventing further events from being processed.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n *\n * @example\n * ```typescript\n * const terminateOnDone: TerminateDetector = (event) => {\n * return event.event === 'done' || event.data === '[DONE]';\n * };\n * ```\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\n *\n * This interface extends the base ServerSentEvent but replaces the string 'data' field\n * with a parsed JSON object of the specified generic type. This allows for type-safe\n * access to the event payload.\n *\n * @template DATA - The expected type of the parsed JSON data\n */\nexport interface JsonServerSentEvent<DATA> extends Omit<\n ServerSentEvent,\n 'data'\n> {\n /** The parsed JSON data from the event */\n data: DATA;\n}\n\n/**\n * A TransformStream transformer that converts ServerSentEvent to JsonServerSentEvent with optional termination detection.\n *\n * This transformer parses the JSON data from ServerSentEvent chunks and optionally terminates\n * the stream when a termination condition is met. It's designed to work within a TransformStream\n * to convert raw server-sent events into typed JSON events.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransform<DATA> implements Transformer<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransform instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * If provided, this function is called for each event and can terminate\n * the stream by returning true.\n */\n constructor(private readonly terminateDetector?: TerminateDetector) {}\n\n /**\n * Transforms a ServerSentEvent chunk into a JsonServerSentEvent.\n *\n * This method first checks if the event should terminate the stream using the terminateDetector.\n * If termination is required, the controller is terminated. Otherwise, the event data is parsed\n * as JSON and enqueued as a JsonServerSentEvent.\n *\n * If the terminateDetector throws an exception, the stream is terminated with an error to prevent\n * corrupted state.\n *\n * @param chunk - The ServerSentEvent to transform\n * @param controller - The TransformStream controller for managing the stream\n * @throws {SyntaxError} If the event data is not valid JSON\n * @throws {Error} If the terminateDetector throws an exception\n *\n * @example\n * ```typescript\n * const transformer = new JsonServerSentEventTransform<MyData>();\n * // This will be called automatically by the TransformStream\n * ```\n */\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ) {\n try {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n controller.terminate();\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n controller.enqueue({\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n } catch (error) {\n // If terminate detector throws or JSON parsing fails, terminate the stream to prevent corrupted state\n controller.error(error);\n return;\n }\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to JsonServerSentEvent streams with optional termination detection.\n *\n * This class extends TransformStream to provide a convenient way to transform streams of ServerSentEvent\n * objects into streams of JsonServerSentEvent objects. It supports optional termination detection to\n * automatically end the stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransformStream instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * When provided, the stream will automatically terminate when this\n * function returns true for any event.\n *\n * @example\n * ```typescript\n * // Create a stream that terminates on 'done' events\n * const terminateOnDone: TerminateDetector = (event) => event.event === 'done';\n * const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);\n *\n * // Create a stream without termination detection\n * const basicStream = new JsonServerSentEventTransformStream<MyData>();\n * ```\n */\n constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\n *\n * This type represents a stream that yields parsed JSON server-sent events.\n * Each chunk in the stream contains the event metadata along with parsed JSON data.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\n/**\n * Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.\n *\n * This function takes a stream of raw server-sent events and transforms it into a stream of\n * parsed JSON events. It optionally accepts a termination detector to automatically end the\n * stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform\n * @param terminateDetector - Optional function to detect when the stream should be terminated\n * @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data\n * @throws {SyntaxError} If any event data is not valid JSON (thrown during stream consumption)\n *\n * @example\n * ```typescript\n * // Basic usage without termination detection\n * const jsonStream = toJsonServerSentEventStream<MyData>(serverSentEventStream);\n *\n * // With termination detection\n * const terminateOnDone: TerminateDetector = (event) => event.data === '[DONE]';\n * const terminatingStream = toJsonServerSentEventStream<MyData>(\n * serverSentEventStream,\n * terminateOnDone\n * );\n *\n * // Consume the stream\n * for await (const event of jsonStream) {\n * console.log('Received:', event.data);\n * console.log('Event type:', event.event);\n * }\n * ```\n */\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n terminateDetector?: TerminateDetector,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(terminateDetector),\n );\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { FetchExchange, ResultExtractor } from '@ahoo-wang/fetcher';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport type { JsonServerSentEventStream } from './jsonServerSentEventTransformStream';\n\n/**\n * ServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a ServerSentEventStream from an HTTP response that contains Server-Sent Events.\n * The extractor validates that the response supports event streaming and converts the\n * response body into a properly typed event stream.\n *\n * This extractor should be used when you want to consume raw Server-Sent Events\n * without JSON parsing, maintaining the original string data format.\n *\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {ExchangeError} When the server response does not support ServerSentEventStream\n * (e.g., wrong content type, no response body)\n *\n *\n * @see {@link ServerSentEventStream} for the stream type\n * @see {@link JsonEventStreamResultExtractor} for JSON-parsed event streams\n */\nexport const EventStreamResultExtractor: ResultExtractor<\n ServerSentEventStream\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredEventStream();\n};\n\n/**\n * JsonServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a JsonServerSentEventStream from an HTTP response that contains Server-Sent Events\n * with JSON data. The extractor validates that the response supports event streaming and converts\n * the response body into a properly typed event stream with automatic JSON parsing.\n *\n * This extractor should be used when you want to consume Server-Sent Events where the event\n * data is JSON-formatted, providing type-safe access to parsed JSON objects instead of raw strings.\n *\n * @template DATA - The expected type of the JSON data in the server-sent events\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects with parsed JSON data as they are received\n * @throws {ExchangeError} When the server response does not support JsonServerSentEventStream\n * (e.g., wrong content type, no response body, invalid JSON)\n *\n *\n * @see {@link JsonServerSentEventStream} for the stream type with JSON data\n * @see {@link EventStreamResultExtractor} for raw string event streams\n */\nexport const JsonEventStreamResultExtractor: ResultExtractor<\n JsonServerSentEventStream<any>\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredJsonEventStream();\n};\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EventStreamConvertError,\n type ServerSentEventStream,\n toServerSentEventStream,\n} from './eventStreamConverter';\nimport {\n type JsonServerSentEventStream,\n type TerminateDetector,\n toJsonServerSentEventStream,\n} from './jsonServerSentEventTransformStream';\nimport { CONTENT_TYPE_HEADER, ContentTypeValues } from '@ahoo-wang/fetcher';\n\ndeclare global {\n interface Response {\n /**\n * Gets the content type of the response.\n *\n * This property provides access to the Content-Type header of the response,\n * which indicates the media type of the resource transmitted in the response.\n *\n * @returns The content type header value as a string, or null if the header is not set\n */\n get contentType(): string | null;\n\n /**\n * Checks if the response is an event stream.\n *\n * This property examines the Content-Type header to determine if the response\n * contains server-sent events data (text/event-stream).\n *\n * @returns true if the response is an event stream, false otherwise\n */\n get isEventStream(): boolean;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects, or null if not an event stream\n */\n eventStream(): ServerSentEventStream | null;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is similar to eventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws {Error} if the event stream is not available\n */\n requiredEventStream(): ServerSentEventStream;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data, or null if not an event stream\n */\n jsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA> | null;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is similar to jsonEventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream with JSON data.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data\n * @throws {Error} if the event stream is not available\n */\n requiredJsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA>;\n }\n}\n\nif (typeof Response !== 'undefined') {\n const CONTENT_TYPE_PROPERTY_NAME = 'contentType';\n /**\n * Defines the contentType property on Response prototype.\n * This property provides a convenient way to access the Content-Type header value.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n CONTENT_TYPE_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, CONTENT_TYPE_PROPERTY_NAME, {\n get() {\n return this.headers.get(CONTENT_TYPE_HEADER);\n },\n configurable: true,\n });\n }\n\n const IS_EVENT_STREAM_PROPERTY_NAME = 'isEventStream';\n /**\n * Defines the isEventStream property on Response prototype.\n * This property checks if the response has a Content-Type header indicating it's an event stream.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n IS_EVENT_STREAM_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, IS_EVENT_STREAM_PROPERTY_NAME, {\n get() {\n const contentType = this.contentType;\n if (!contentType) {\n return false;\n }\n return contentType.includes(ContentTypeValues.TEXT_EVENT_STREAM);\n },\n configurable: true,\n });\n }\n\n /**\n * Implementation of the eventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream.\n *\n * @returns A ServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'eventStream')\n ) {\n Response.prototype.eventStream = function () {\n if (!this.isEventStream) {\n return null;\n }\n return toServerSentEventStream(this);\n };\n }\n\n /**\n * Implementation of the requiredEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @returns A ServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredEventStream',\n )\n ) {\n Response.prototype.requiredEventStream = function () {\n const eventStream = this.eventStream();\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n\n /**\n * Implementation of the jsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'jsonEventStream')\n ) {\n Response.prototype.jsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.eventStream();\n if (!eventStream) {\n return null;\n }\n return toJsonServerSentEventStream<DATA>(eventStream, terminateDetector);\n };\n }\n\n /**\n * Implementation of the requiredJsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredJsonEventStream',\n )\n ) {\n Response.prototype.requiredJsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.jsonEventStream<DATA>(terminateDetector);\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper class that converts a ReadableStream into an AsyncIterable.\n *\n * This class enables the use of ReadableStream objects with async iteration syntax\n * (for-await...of loops), providing a more ergonomic way to consume streaming data.\n * It implements the AsyncIterable interface and manages the underlying stream reader,\n * handling proper resource cleanup and error propagation.\n *\n * The wrapper automatically handles stream locking, ensuring that only one consumer\n * can read from the stream at a time, and provides safe cleanup when iteration ends\n * or errors occur.\n *\n * @template T - The type of data yielded by the stream\n *\n * @example\n * ```typescript\n * // Direct usage\n * const response = await fetch('/api/stream');\n * const stream = response.body;\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * console.log('Received:', chunk);\n * }\n * // Stream is automatically cleaned up after iteration\n * ```\n *\n * @example\n * ```typescript\n * // With early termination\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * if (someCondition) {\n * asyncIterable.releaseLock(); // Manually release if needed\n * break;\n * }\n * }\n * ```\n */\nexport class ReadableStreamAsyncIterable<T> implements AsyncIterable<T> {\n private readonly reader: ReadableStreamDefaultReader<T>;\n private _locked: boolean = true;\n\n /**\n * Creates a new ReadableStreamAsyncIterable instance.\n * @param stream - The ReadableStream to wrap.\n */\n constructor(private readonly stream: ReadableStream<T>) {\n this.reader = stream.getReader();\n }\n\n /**\n * Gets the lock status of the reader.\n * @returns True if the reader is currently locked, false otherwise.\n */\n get locked(): boolean {\n return this._locked;\n }\n\n /**\n * Releases the reader lock if currently locked.\n * This method safely releases the reader lock by catching any potential errors.\n */\n releaseLock() {\n if (!this._locked) return false;\n this._locked = false;\n try {\n this.reader.releaseLock();\n return true;\n } catch (error) {\n console.debug('Failed to release reader lock:', error);\n return false;\n }\n }\n\n /**\n * Implements the AsyncIterable interface by returning this iterator.\n * @returns The async iterator for this instance.\n */\n [Symbol.asyncIterator]() {\n return this;\n }\n\n /**\n * Gets the next value from the stream.\n * Reads the next chunk from the stream and returns it as an IteratorResult.\n * If the stream is done, releases the lock and returns a done result.\n * @returns A promise that resolves to an IteratorResult containing the next value or done status.\n * @throws If an error occurs while reading from the stream.\n */\n async next(): Promise<IteratorResult<T>> {\n try {\n const { done, value } = await this.reader.read();\n if (done) {\n this.releaseLock();\n return { done: true, value: undefined };\n }\n\n return { done: false, value };\n } catch (error) {\n this.releaseLock();\n throw error;\n }\n }\n\n /**\n * Implements the return method of the async iterator.\n * Cancels the stream reader and releases the lock.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async return(): Promise<IteratorResult<T>> {\n try {\n await this.reader.cancel();\n } catch (error) {\n console.debug('Failed to cancel stream reader:', error);\n } finally {\n this.releaseLock();\n }\n return { done: true, value: undefined };\n }\n\n /**\n * Implements the throw method of the async iterator.\n * Releases the lock and returns a done result.\n * @param error - The error to be thrown.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async throw(error: any): Promise<IteratorResult<T>> {\n // Ensure the reader lock is released before throwing\n console.debug('Throwing error:', error);\n this.releaseLock();\n return { done: true, value: undefined };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReadableStreamAsyncIterable } from './readableStreamAsyncIterable';\n\n/**\n * Checks if the current environment natively supports async iteration on ReadableStream.\n *\n * This constant determines whether the browser or runtime already provides\n * built-in support for using ReadableStream with for-await...of loops.\n * If not supported, this library will polyfill the functionality by adding\n * the [Symbol.asyncIterator] method to ReadableStream.prototype.\n *\n * @returns true if native async iteration is supported, false if polyfill is needed\n *\n * @example\n * ```typescript\n * import { isReadableStreamAsyncIterableSupported } from '@ahoo-wang/fetcher-eventstream';\n *\n * if (isReadableStreamAsyncIterableSupported) {\n * console.log('Native support available');\n * } else {\n * console.log('Using polyfill');\n * }\n * ```\n */\nexport const isReadableStreamAsyncIterableSupported =\n typeof ReadableStream !== 'undefined' &&\n typeof ReadableStream.prototype[Symbol.asyncIterator] === 'function';\n\n// Add [Symbol.asyncIterator] to ReadableStream if not already implemented\nif (!isReadableStreamAsyncIterableSupported) {\n ReadableStream.prototype[Symbol.asyncIterator] = function <R = any>() {\n return new ReadableStreamAsyncIterable<R>(this as ReadableStream<R>);\n };\n}\n"],"mappings":"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"}

@@ -1,13 +0,1 @@

declare global {
interface ReadableStream<R = any> {
/**
* Makes ReadableStream async iterable for use with for-await loops.
*
* This allows the stream to be consumed using `for await (const chunk of stream)` syntax.
*
* @returns An async iterator for the stream
*/
[Symbol.asyncIterator](): AsyncIterator<R>;
}
}
/**

@@ -14,0 +2,0 @@ * Checks if the current environment natively supports async iteration on ReadableStream.

@@ -1,1 +0,1 @@

{"version":3,"file":"readableStreams.d.ts","sourceRoot":"","sources":["../src/readableStreams.ts"],"names":[],"mappings":"AAeA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,cAAc,CAAC,CAAC,GAAG,GAAG;QAC9B;;;;;;WAMG;QACH,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC;KAC5C;CACF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,sCAAsC,SAEmB,CAAC"}
{"version":3,"file":"readableStreams.d.ts","sourceRoot":"","sources":["../src/readableStreams.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,sCAAsC,SAEmB,CAAC"}
{
"name": "@ahoo-wang/fetcher-eventstream",
"version": "3.16.4",
"version": "3.16.5",
"description": "Server-Sent Events (SSE) support for Fetcher HTTP client with native LLM streaming API support. Enables real-time data streaming and token-by-token LLM response handling.",

@@ -5,0 +5,0 @@ "keywords": [