@ahoo-wang/fetcher-eventstream 3.16.6 → 3.16.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +103 -86
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +4 -4
- package/dist/index.umd.js.map +1 -1
- package/dist/jsonServerSentEventTransformStream.d.ts +8 -99
- package/dist/jsonServerSentEventTransformStream.d.ts.map +1 -1
- package/dist/safeTransformer.d.ts +89 -0
- package/dist/safeTransformer.d.ts.map +1 -0
- package/dist/serverSentEventTransformStream.d.ts +12 -65
- package/dist/serverSentEventTransformStream.d.ts.map +1 -1
- package/dist/textLineTransformStream.d.ts +11 -46
- package/dist/textLineTransformStream.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.umd.js","names":[],"sources":["../src/streamController.ts","../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 * Union type representing a stream controller that supports safe operations.\n *\n * Both ReadableStreamDefaultController and TransformStreamDefaultController\n * share the `enqueue()` and `error()` methods. TransformStreamDefaultController\n * additionally provides `terminate()`. This union type allows the safe\n * utility functions to work with either controller kind.\n *\n * @template T - The type of chunks the controller handles\n */\nexport type StreamController<T> =\n | ReadableStreamDefaultController<T>\n | TransformStreamDefaultController<T>;\n\n/**\n * Executes an action and suppresses TypeError, returning a boolean indicating success.\n *\n * This is the shared implementation for all safe* controller functions.\n * Stream controller methods (terminate, enqueue, error) throw TypeError when\n * the stream is already closed or errored. This helper catches that TypeError\n * and returns false, while re-throwing any non-TypeError exceptions.\n *\n * Uses both `instanceof TypeError` and `Object.prototype.toString` checks\n * to handle cross-realm TypeErrors (e.g. from iframes or worker threads)\n * where `instanceof` alone would fail. The toString check matches values\n * with the internal TypeError class tag, which covers cross-realm TypeErrors\n * while rejecting plain objects that merely have a `name` property.\n *\n * @param action - The controller operation to attempt\n * @returns true if the action succeeded, false if a TypeError was caught\n */\nfunction suppressTypeError(action: () => void): boolean {\n try {\n action();\n return true;\n } catch (error) {\n if (\n error instanceof TypeError ||\n Object.prototype.toString.call(error) === '[object TypeError]'\n ) {\n return false;\n }\n throw error;\n }\n}\n\n/**\n * Safely terminates a TransformStream controller, ignoring the TypeError\n * that occurs if the stream has already been terminated.\n *\n * After controller.terminate() is called, upstream may still push chunks\n * before the backpressure signal propagates, causing transform() to be\n * invoked again. A second call to controller.terminate() throws TypeError,\n * which this function suppresses.\n *\n * @param controller - The TransformStream controller to terminate\n * @returns true if termination succeeded, false if the stream was already closed\n */\nexport function safeTerminate<T>(\n controller: TransformStreamDefaultController<T>,\n): boolean {\n return suppressTypeError(() => controller.terminate());\n}\n\n/**\n * Safely enqueues a chunk to a stream controller, ignoring the TypeError\n * that occurs if the stream has already been closed or errored.\n *\n * After a stream is terminated or errored, upstream may still push chunks\n * before the signal propagates. Calling controller.enqueue() on a closed\n * stream throws TypeError, which this function suppresses.\n *\n * @param controller - The stream controller to enqueue to\n * @param chunk - The chunk to enqueue\n * @returns true if the chunk was enqueued, false if the stream was already closed\n */\nexport function safeEnqueue<T>(\n controller: StreamController<T>,\n chunk: T,\n): boolean {\n return suppressTypeError(() => controller.enqueue(chunk));\n}\n\n/**\n * Safely errors a stream controller, ignoring the TypeError\n * that occurs if the stream has already been closed or errored.\n *\n * After a stream is terminated or errored, subsequent error signals may\n * arrive before the backpressure propagates. Calling controller.error()\n * on an already-closed stream throws TypeError, which this function\n * suppresses. Non-TypeError exceptions are re-thrown.\n *\n * @param controller - The stream controller to error\n * @param reason - The error reason to pass to the controller\n * @returns true if the error was set, false if the stream was already closed\n */\nexport function safeError<T>(\n controller: StreamController<T>,\n reason: any,\n): boolean {\n return suppressTypeError(() => controller.error(reason));\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 * 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 */\nimport { safeEnqueue, safeError } from './streamController';\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 safeEnqueue(controller, line);\n }\n } catch (error) {\n safeError(controller, 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 safeEnqueue(controller, this.buffer);\n }\n } catch (error) {\n safeError(controller, 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 */\nimport { safeEnqueue, safeError } from './streamController';\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 safeEnqueue(controller, {\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 safeError(controller, 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 safeEnqueue(controller, {\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 safeError(controller, 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';\nimport { safeEnqueue, safeError, safeTerminate } from './streamController';\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 * Guard flag to prevent any controller operations after the stream has been\n * terminated or errored. Once set, all subsequent chunks are silently dropped.\n */\n private terminated = false;\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 stream has already been terminated. If so,\n * the chunk is silently dropped. Otherwise, it checks if the event should\n * terminate the stream using the terminateDetector. If termination is required,\n * the controller is safely terminated. Otherwise, the event data is parsed\n * as JSON and enqueued as a JsonServerSentEvent.\n *\n * All controller operations use safe wrappers (safeTerminate, safeEnqueue,\n * safeError) that suppress TypeError from already-closed streams as normal\n * control flow. Any error thrown during detection or parsing (including\n * TypeError from detector/parse logic) is caught, the stream is errored\n * via safeError, and subsequent chunks are dropped.\n *\n * @param chunk - The ServerSentEvent to transform\n * @param controller - The TransformStream controller for managing the stream\n */\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ) {\n if (this.terminated) {\n return;\n }\n\n try {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n this.terminated = true;\n safeTerminate(controller);\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n safeEnqueue(controller, {\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n } catch (error) {\n this.terminated = true;\n safeError(controller, error);\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
|
+
{"version":3,"file":"index.umd.js","names":[],"sources":["../src/streamController.ts","../src/safeTransformer.ts","../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 * Union type representing a stream controller that supports safe operations.\n *\n * Both ReadableStreamDefaultController and TransformStreamDefaultController\n * share the `enqueue()` and `error()` methods. TransformStreamDefaultController\n * additionally provides `terminate()`. This union type allows the safe\n * utility functions to work with either controller kind.\n *\n * @template T - The type of chunks the controller handles\n */\nexport type StreamController<T> =\n | ReadableStreamDefaultController<T>\n | TransformStreamDefaultController<T>;\n\n/**\n * Executes an action and suppresses TypeError, returning a boolean indicating success.\n *\n * This is the shared implementation for all safe* controller functions.\n * Stream controller methods (terminate, enqueue, error) throw TypeError when\n * the stream is already closed or errored. This helper catches that TypeError\n * and returns false, while re-throwing any non-TypeError exceptions.\n *\n * Uses both `instanceof TypeError` and `Object.prototype.toString` checks\n * to handle cross-realm TypeErrors (e.g. from iframes or worker threads)\n * where `instanceof` alone would fail. The toString check matches values\n * with the internal TypeError class tag, which covers cross-realm TypeErrors\n * while rejecting plain objects that merely have a `name` property.\n *\n * @param action - The controller operation to attempt\n * @returns true if the action succeeded, false if a TypeError was caught\n */\nfunction suppressTypeError(action: () => void): boolean {\n try {\n action();\n return true;\n } catch (error) {\n if (\n error instanceof TypeError ||\n Object.prototype.toString.call(error) === '[object TypeError]'\n ) {\n return false;\n }\n throw error;\n }\n}\n\n/**\n * Safely terminates a TransformStream controller, ignoring the TypeError\n * that occurs if the stream has already been terminated.\n *\n * After controller.terminate() is called, upstream may still push chunks\n * before the backpressure signal propagates, causing transform() to be\n * invoked again. A second call to controller.terminate() throws TypeError,\n * which this function suppresses.\n *\n * @param controller - The TransformStream controller to terminate\n * @returns true if termination succeeded, false if the stream was already closed\n */\nexport function safeTerminate<T>(\n controller: TransformStreamDefaultController<T>,\n): boolean {\n return suppressTypeError(() => controller.terminate());\n}\n\n/**\n * Safely enqueues a chunk to a stream controller, ignoring the TypeError\n * that occurs if the stream has already been closed or errored.\n *\n * After a stream is terminated or errored, upstream may still push chunks\n * before the signal propagates. Calling controller.enqueue() on a closed\n * stream throws TypeError, which this function suppresses.\n *\n * @param controller - The stream controller to enqueue to\n * @param chunk - The chunk to enqueue\n * @returns true if the chunk was enqueued, false if the stream was already closed\n */\nexport function safeEnqueue<T>(\n controller: StreamController<T>,\n chunk: T,\n): boolean {\n return suppressTypeError(() => controller.enqueue(chunk));\n}\n\n/**\n * Safely errors a stream controller, ignoring the TypeError\n * that occurs if the stream has already been closed or errored.\n *\n * After a stream is terminated or errored, subsequent error signals may\n * arrive before the backpressure propagates. Calling controller.error()\n * on an already-closed stream throws TypeError, which this function\n * suppresses. Non-TypeError exceptions are re-thrown.\n *\n * @param controller - The stream controller to error\n * @param reason - The error reason to pass to the controller\n * @returns true if the error was set, false if the stream was already closed\n */\nexport function safeError<T>(\n controller: StreamController<T>,\n reason: any,\n): boolean {\n return suppressTypeError(() => controller.error(reason));\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 { safeEnqueue, safeError, safeTerminate } from './streamController';\n\n/**\n * Identifies the lifecycle phase where an error occurred.\n */\nexport type TransformerPhase = 'transform' | 'flush';\n\n/**\n * Abstract base class for TransformStream transformers with built-in error safety\n * and termination guard.\n *\n * Provides three guarantees that every concrete transformer inherits:\n *\n * 1. **Termination guard** — Once `terminated` is set (via `terminate()` or an\n * unhandled error), all subsequent chunks are silently dropped in `transform()`.\n *\n * 2. **Safe controller operations** — `enqueue()` delegates to\n * `safeEnqueue` which suppresses TypeError from already-closed streams.\n * `terminate()` delegates to `safeTerminate`.\n *\n * 3. **Error boundary** — Unhandled errors in `onTransform()` / `onFlush()`\n * are caught, the transformer is terminated, and the error is forwarded\n * via `safeError()`.\n *\n * Subclasses implement `onTransform()` and optionally `onFlush()` instead of\n * the raw `transform()` / `flush()` methods.\n *\n * @template I - The type of input chunks\n * @template O - The type of output chunks\n */\nexport abstract class SafeTransformer<I, O> implements Transformer<I, O> {\n /**\n * Guard flag indicating the stream has been terminated or errored.\n * Once set, subsequent chunks are silently dropped in `transform()`.\n */\n\n protected terminated = false;\n\n /**\n * Transforms an input chunk. Drops immediately if terminated.\n * Delegates to `onTransform()` with error protection.\n * Supports both synchronous and asynchronous `onTransform()` implementations.\n */\n async transform(\n chunk: I,\n controller: TransformStreamDefaultController<O>,\n ): Promise<void> {\n if (this.terminated) {\n return;\n }\n\n try {\n await this.onTransform(chunk, controller);\n } catch (error) {\n this.terminated = true;\n this.safeOnError(error, 'transform');\n safeError(controller, error);\n }\n }\n\n /**\n * Called when the stream ends. Always invokes `onFlush()` for cleanup,\n * even if already terminated. Errors in `onFlush()` are caught and\n * forwarded via `safeError()`.\n * Supports both synchronous and asynchronous `onFlush()` implementations.\n */\n async flush(controller: TransformStreamDefaultController<O>): Promise<void> {\n try {\n await this.onFlush(controller);\n } catch (error) {\n this.terminated = true;\n this.safeOnError(error, 'flush');\n safeError(controller, error);\n } finally {\n this.terminated = true;\n }\n }\n\n /**\n * Safely invokes `onError()`, catching any exception to prevent\n * cleanup logic from breaking the error boundary guarantee.\n */\n private safeOnError(error: unknown, phase: TransformerPhase): void {\n try {\n this.onError(error, phase);\n } catch {\n // onError must not break the error boundary\n }\n }\n\n /**\n * Marks the transformer as terminated and safely terminates the controller.\n * After calling this, all subsequent chunks are silently dropped.\n */\n protected terminate(controller: TransformStreamDefaultController<O>): boolean {\n this.terminated = true;\n return safeTerminate(controller);\n }\n\n /**\n * Safely enqueues a chunk to the controller.\n * Suppresses TypeError if the stream is already closed.\n */\n protected enqueue(\n controller: TransformStreamDefaultController<O>,\n chunk: O,\n ): boolean {\n return safeEnqueue(controller, chunk);\n }\n\n /**\n * Called when an error occurs during `onTransform()` or `onFlush()`.\n * Subclasses can override to clean up internal state (e.g. reset buffers).\n * The stream is already marked as terminated when this is called.\n *\n * @param error - The error that was caught\n * @param phase - The lifecycle phase where the error occurred\n */\n protected onError(error: unknown, phase: TransformerPhase): void {\n }\n\n /**\n * Transform an input chunk into output chunk(s).\n * Use `this.enqueue(controller, chunk)` instead of `controller.enqueue()`.\n * May return a Promise for asynchronous processing.\n *\n * @param chunk - The input chunk to transform\n * @param controller - The stream controller (use `this.enqueue()` for output)\n */\n protected abstract onTransform(\n chunk: I,\n controller: TransformStreamDefaultController<O>,\n ): void | Promise<void>;\n\n /**\n * Called when the stream is ending. Override to flush remaining state.\n * May return a Promise for asynchronous processing.\n * Default implementation does nothing.\n *\n * @param controller\n */\n protected onFlush(\n controller: TransformStreamDefaultController<O>,\n ): void | Promise<void> {\n // Default: nothing to flush\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 { SafeTransformer } from './safeTransformer';\n\n/**\n * Transformer that splits text into lines.\n *\n * Accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. Handles partial lines\n * that span multiple input chunks by maintaining an internal buffer.\n */\nexport class TextLineTransformer extends SafeTransformer<string, string> {\n private buffer = '';\n\n protected onTransform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ): void {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n this.enqueue(controller, line);\n }\n }\n\n protected onFlush(\n controller: TransformStreamDefaultController<string>,\n ): void {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n this.enqueue(controller, this.buffer);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * @example\n * ```typescript\n * const lineStream = new TextLineTransformStream();\n * const lines = textStream.pipeThrough(lineStream);\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\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\nimport { SafeTransformer, type TransformerPhase } from './safeTransformer';\n\n/**\n * Represents a message sent in an event stream.\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 */\nexport class ServerSentEventFields {\n static readonly ID = 'id';\n static readonly RETRY = 'retry';\n static readonly EVENT = 'event';\n static readonly DATA = 'data';\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 // Per W3C SSE spec: \"If the field value does not contain U+0000 NULL,\n // then set the last event ID buffer to the field value.\n // Otherwise, ignore the field.\"\n if (!value.includes('\\0')) {\n currentEvent.id = value;\n }\n break;\n case ServerSentEventFields.RETRY: {\n // Per W3C SSE spec: \"If the field value consists of only ASCII digits,\n // then interpret the field value as an integer in base ten.\"\n if (/^\\d+$/.test(value)) {\n currentEvent.retry = parseInt(value, 10);\n }\n break;\n }\n default:\n break;\n }\n}\n\ninterface EventState {\n event?: string;\n id?: string;\n retry?: number;\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a\n * ServerSentEvent object stream, following the W3C SSE specification.\n */\nexport class ServerSentEventTransformer extends SafeTransformer<\n string,\n ServerSentEvent\n> {\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\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 protected override onError(_error: unknown, _phase: TransformerPhase): void {\n this.resetEventState();\n }\n\n protected onTransform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ): void {\n const currentEvent = this.currentEventState;\n\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n if (currentEvent.data.length > 0) {\n this.enqueue(controller, {\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 currentEvent.event = DEFAULT_EVENT_TYPE;\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 field = chunk.toLowerCase();\n value = '';\n } else {\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n\n processFieldInternal(field, value, currentEvent);\n }\n\n protected onFlush(\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ): void {\n const currentEvent = this.currentEventState;\n try {\n if (currentEvent.data.length > 0) {\n this.enqueue(controller, {\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 } finally {\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of\n * ServerSentEvent objects.\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\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';\nimport { SafeTransformer } from './safeTransformer';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\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\n * JsonServerSentEvent with optional termination detection.\n *\n * Inherits termination guard and safe controller operations from SafeTransformer.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransform<DATA> extends SafeTransformer<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n constructor(private readonly terminateDetector?: TerminateDetector) {\n super();\n }\n\n protected onTransform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ): void {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n this.terminate(controller);\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n this.enqueue(controller, {\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to\n * JsonServerSentEvent streams with optional termination detection.\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 constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform<DATA>(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\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 * @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 */\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,29 +1,16 @@
|
|
|
1
1
|
import { ServerSentEvent } from './serverSentEventTransformStream';
|
|
2
2
|
import { ServerSentEventStream } from './eventStreamConverter';
|
|
3
|
+
import { SafeTransformer } from './safeTransformer';
|
|
3
4
|
/**
|
|
4
5
|
* A function type that determines whether a Server-Sent Event should terminate the stream.
|
|
5
6
|
*
|
|
6
|
-
* This detector function is called for each incoming ServerSentEvent. If it returns true,
|
|
7
|
-
* the stream transformation will be terminated, preventing further events from being processed.
|
|
8
|
-
*
|
|
9
7
|
* @param event - The ServerSentEvent to evaluate for termination
|
|
10
8
|
* @returns true if the stream should be terminated, false otherwise
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* const terminateOnDone: TerminateDetector = (event) => {
|
|
15
|
-
* return event.event === 'done' || event.data === '[DONE]';
|
|
16
|
-
* };
|
|
17
|
-
* ```
|
|
18
9
|
*/
|
|
19
10
|
export type TerminateDetector = (event: ServerSentEvent) => boolean;
|
|
20
11
|
/**
|
|
21
12
|
* Represents a Server-Sent Event with parsed JSON data.
|
|
22
13
|
*
|
|
23
|
-
* This interface extends the base ServerSentEvent but replaces the string 'data' field
|
|
24
|
-
* with a parsed JSON object of the specified generic type. This allows for type-safe
|
|
25
|
-
* access to the event payload.
|
|
26
|
-
*
|
|
27
14
|
* @template DATA - The expected type of the parsed JSON data
|
|
28
15
|
*/
|
|
29
16
|
export interface JsonServerSentEvent<DATA> extends Omit<ServerSentEvent, 'data'> {
|
|
@@ -31,118 +18,40 @@ export interface JsonServerSentEvent<DATA> extends Omit<ServerSentEvent, 'data'>
|
|
|
31
18
|
data: DATA;
|
|
32
19
|
}
|
|
33
20
|
/**
|
|
34
|
-
* A TransformStream transformer that converts ServerSentEvent to
|
|
21
|
+
* A TransformStream transformer that converts ServerSentEvent to
|
|
22
|
+
* JsonServerSentEvent with optional termination detection.
|
|
35
23
|
*
|
|
36
|
-
*
|
|
37
|
-
* the stream when a termination condition is met. It's designed to work within a TransformStream
|
|
38
|
-
* to convert raw server-sent events into typed JSON events.
|
|
24
|
+
* Inherits termination guard and safe controller operations from SafeTransformer.
|
|
39
25
|
*
|
|
40
26
|
* @template DATA - The expected type of the parsed JSON data in each event
|
|
41
27
|
*/
|
|
42
|
-
export declare class JsonServerSentEventTransform<DATA>
|
|
28
|
+
export declare class JsonServerSentEventTransform<DATA> extends SafeTransformer<ServerSentEvent, JsonServerSentEvent<DATA>> {
|
|
43
29
|
private readonly terminateDetector?;
|
|
44
|
-
/**
|
|
45
|
-
* Guard flag to prevent any controller operations after the stream has been
|
|
46
|
-
* terminated or errored. Once set, all subsequent chunks are silently dropped.
|
|
47
|
-
*/
|
|
48
|
-
private terminated;
|
|
49
|
-
/**
|
|
50
|
-
* Creates a new JsonServerSentEventTransform instance.
|
|
51
|
-
*
|
|
52
|
-
* @param terminateDetector - Optional function to detect when the stream should be terminated.
|
|
53
|
-
* If provided, this function is called for each event and can terminate
|
|
54
|
-
* the stream by returning true.
|
|
55
|
-
*/
|
|
56
30
|
constructor(terminateDetector?: TerminateDetector | undefined);
|
|
57
|
-
|
|
58
|
-
* Transforms a ServerSentEvent chunk into a JsonServerSentEvent.
|
|
59
|
-
*
|
|
60
|
-
* This method first checks if the stream has already been terminated. If so,
|
|
61
|
-
* the chunk is silently dropped. Otherwise, it checks if the event should
|
|
62
|
-
* terminate the stream using the terminateDetector. If termination is required,
|
|
63
|
-
* the controller is safely terminated. Otherwise, the event data is parsed
|
|
64
|
-
* as JSON and enqueued as a JsonServerSentEvent.
|
|
65
|
-
*
|
|
66
|
-
* All controller operations use safe wrappers (safeTerminate, safeEnqueue,
|
|
67
|
-
* safeError) that suppress TypeError from already-closed streams as normal
|
|
68
|
-
* control flow. Any error thrown during detection or parsing (including
|
|
69
|
-
* TypeError from detector/parse logic) is caught, the stream is errored
|
|
70
|
-
* via safeError, and subsequent chunks are dropped.
|
|
71
|
-
*
|
|
72
|
-
* @param chunk - The ServerSentEvent to transform
|
|
73
|
-
* @param controller - The TransformStream controller for managing the stream
|
|
74
|
-
*/
|
|
75
|
-
transform(chunk: ServerSentEvent, controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>): void;
|
|
31
|
+
protected onTransform(chunk: ServerSentEvent, controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>): void;
|
|
76
32
|
}
|
|
77
33
|
/**
|
|
78
|
-
* A TransformStream that converts ServerSentEvent streams to
|
|
79
|
-
*
|
|
80
|
-
* This class extends TransformStream to provide a convenient way to transform streams of ServerSentEvent
|
|
81
|
-
* objects into streams of JsonServerSentEvent objects. It supports optional termination detection to
|
|
82
|
-
* automatically end the stream when certain conditions are met.
|
|
34
|
+
* A TransformStream that converts ServerSentEvent streams to
|
|
35
|
+
* JsonServerSentEvent streams with optional termination detection.
|
|
83
36
|
*
|
|
84
37
|
* @template DATA - The expected type of the parsed JSON data in each event
|
|
85
38
|
*/
|
|
86
39
|
export declare class JsonServerSentEventTransformStream<DATA> extends TransformStream<ServerSentEvent, JsonServerSentEvent<DATA>> {
|
|
87
|
-
/**
|
|
88
|
-
* Creates a new JsonServerSentEventTransformStream instance.
|
|
89
|
-
*
|
|
90
|
-
* @param terminateDetector - Optional function to detect when the stream should be terminated.
|
|
91
|
-
* When provided, the stream will automatically terminate when this
|
|
92
|
-
* function returns true for any event.
|
|
93
|
-
*
|
|
94
|
-
* @example
|
|
95
|
-
* ```typescript
|
|
96
|
-
* // Create a stream that terminates on 'done' events
|
|
97
|
-
* const terminateOnDone: TerminateDetector = (event) => event.event === 'done';
|
|
98
|
-
* const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);
|
|
99
|
-
*
|
|
100
|
-
* // Create a stream without termination detection
|
|
101
|
-
* const basicStream = new JsonServerSentEventTransformStream<MyData>();
|
|
102
|
-
* ```
|
|
103
|
-
*/
|
|
104
40
|
constructor(terminateDetector?: TerminateDetector);
|
|
105
41
|
}
|
|
106
42
|
/**
|
|
107
43
|
* A ReadableStream of JsonServerSentEvent objects.
|
|
108
44
|
*
|
|
109
|
-
* This type represents a stream that yields parsed JSON server-sent events.
|
|
110
|
-
* Each chunk in the stream contains the event metadata along with parsed JSON data.
|
|
111
|
-
*
|
|
112
45
|
* @template DATA - The expected type of the parsed JSON data in each event
|
|
113
46
|
*/
|
|
114
47
|
export type JsonServerSentEventStream<DATA> = ReadableStream<JsonServerSentEvent<DATA>>;
|
|
115
48
|
/**
|
|
116
49
|
* Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.
|
|
117
50
|
*
|
|
118
|
-
* This function takes a stream of raw server-sent events and transforms it into a stream of
|
|
119
|
-
* parsed JSON events. It optionally accepts a termination detector to automatically end the
|
|
120
|
-
* stream when certain conditions are met.
|
|
121
|
-
*
|
|
122
51
|
* @template DATA - The expected type of the parsed JSON data in each event
|
|
123
52
|
* @param serverSentEventStream - The input stream of ServerSentEvent objects to transform
|
|
124
53
|
* @param terminateDetector - Optional function to detect when the stream should be terminated
|
|
125
54
|
* @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data
|
|
126
|
-
* @throws {SyntaxError} If any event data is not valid JSON (thrown during stream consumption)
|
|
127
|
-
*
|
|
128
|
-
* @example
|
|
129
|
-
* ```typescript
|
|
130
|
-
* // Basic usage without termination detection
|
|
131
|
-
* const jsonStream = toJsonServerSentEventStream<MyData>(serverSentEventStream);
|
|
132
|
-
*
|
|
133
|
-
* // With termination detection
|
|
134
|
-
* const terminateOnDone: TerminateDetector = (event) => event.data === '[DONE]';
|
|
135
|
-
* const terminatingStream = toJsonServerSentEventStream<MyData>(
|
|
136
|
-
* serverSentEventStream,
|
|
137
|
-
* terminateOnDone
|
|
138
|
-
* );
|
|
139
|
-
*
|
|
140
|
-
* // Consume the stream
|
|
141
|
-
* for await (const event of jsonStream) {
|
|
142
|
-
* console.log('Received:', event.data);
|
|
143
|
-
* console.log('Event type:', event.event);
|
|
144
|
-
* }
|
|
145
|
-
* ```
|
|
146
55
|
*/
|
|
147
56
|
export declare function toJsonServerSentEventStream<DATA>(serverSentEventStream: ServerSentEventStream, terminateDetector?: TerminateDetector): JsonServerSentEventStream<DATA>;
|
|
148
57
|
//# sourceMappingURL=jsonServerSentEventTransformStream.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonServerSentEventTransformStream.d.ts","sourceRoot":"","sources":["../src/jsonServerSentEventTransformStream.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"jsonServerSentEventTransformStream.d.ts","sourceRoot":"","sources":["../src/jsonServerSentEventTransformStream.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC;AAEpE;;;;GAIG;AACH,MAAM,WAAW,mBAAmB,CAAC,IAAI,CAAE,SAAQ,IAAI,CACrD,eAAe,EACf,MAAM,CACP;IACC,0CAA0C;IAC1C,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;;;;;GAOG;AACH,qBAAa,4BAA4B,CAAC,IAAI,CAAE,SAAQ,eAAe,CACrE,eAAe,EACf,mBAAmB,CAAC,IAAI,CAAC,CAC1B;IACa,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;gBAAlB,iBAAiB,CAAC,EAAE,iBAAiB,YAAA;IAIlE,SAAS,CAAC,WAAW,CACnB,KAAK,EAAE,eAAe,EACtB,UAAU,EAAE,gCAAgC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,GACtE,IAAI;CAeR;AAED;;;;;GAKG;AACH,qBAAa,kCAAkC,CAAC,IAAI,CAAE,SAAQ,eAAe,CAC3E,eAAe,EACf,mBAAmB,CAAC,IAAI,CAAC,CAC1B;gBACa,iBAAiB,CAAC,EAAE,iBAAiB;CAGlD;AAED;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,CAAC,IAAI,IAAI,cAAc,CAC1D,mBAAmB,CAAC,IAAI,CAAC,CAC1B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAC9C,qBAAqB,EAAE,qBAAqB,EAC5C,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,yBAAyB,CAAC,IAAI,CAAC,CAIjC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identifies the lifecycle phase where an error occurred.
|
|
3
|
+
*/
|
|
4
|
+
export type TransformerPhase = 'transform' | 'flush';
|
|
5
|
+
/**
|
|
6
|
+
* Abstract base class for TransformStream transformers with built-in error safety
|
|
7
|
+
* and termination guard.
|
|
8
|
+
*
|
|
9
|
+
* Provides three guarantees that every concrete transformer inherits:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Termination guard** — Once `terminated` is set (via `terminate()` or an
|
|
12
|
+
* unhandled error), all subsequent chunks are silently dropped in `transform()`.
|
|
13
|
+
*
|
|
14
|
+
* 2. **Safe controller operations** — `enqueue()` delegates to
|
|
15
|
+
* `safeEnqueue` which suppresses TypeError from already-closed streams.
|
|
16
|
+
* `terminate()` delegates to `safeTerminate`.
|
|
17
|
+
*
|
|
18
|
+
* 3. **Error boundary** — Unhandled errors in `onTransform()` / `onFlush()`
|
|
19
|
+
* are caught, the transformer is terminated, and the error is forwarded
|
|
20
|
+
* via `safeError()`.
|
|
21
|
+
*
|
|
22
|
+
* Subclasses implement `onTransform()` and optionally `onFlush()` instead of
|
|
23
|
+
* the raw `transform()` / `flush()` methods.
|
|
24
|
+
*
|
|
25
|
+
* @template I - The type of input chunks
|
|
26
|
+
* @template O - The type of output chunks
|
|
27
|
+
*/
|
|
28
|
+
export declare abstract class SafeTransformer<I, O> implements Transformer<I, O> {
|
|
29
|
+
/**
|
|
30
|
+
* Guard flag indicating the stream has been terminated or errored.
|
|
31
|
+
* Once set, subsequent chunks are silently dropped in `transform()`.
|
|
32
|
+
*/
|
|
33
|
+
protected terminated: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Transforms an input chunk. Drops immediately if terminated.
|
|
36
|
+
* Delegates to `onTransform()` with error protection.
|
|
37
|
+
* Supports both synchronous and asynchronous `onTransform()` implementations.
|
|
38
|
+
*/
|
|
39
|
+
transform(chunk: I, controller: TransformStreamDefaultController<O>): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Called when the stream ends. Always invokes `onFlush()` for cleanup,
|
|
42
|
+
* even if already terminated. Errors in `onFlush()` are caught and
|
|
43
|
+
* forwarded via `safeError()`.
|
|
44
|
+
* Supports both synchronous and asynchronous `onFlush()` implementations.
|
|
45
|
+
*/
|
|
46
|
+
flush(controller: TransformStreamDefaultController<O>): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Safely invokes `onError()`, catching any exception to prevent
|
|
49
|
+
* cleanup logic from breaking the error boundary guarantee.
|
|
50
|
+
*/
|
|
51
|
+
private safeOnError;
|
|
52
|
+
/**
|
|
53
|
+
* Marks the transformer as terminated and safely terminates the controller.
|
|
54
|
+
* After calling this, all subsequent chunks are silently dropped.
|
|
55
|
+
*/
|
|
56
|
+
protected terminate(controller: TransformStreamDefaultController<O>): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Safely enqueues a chunk to the controller.
|
|
59
|
+
* Suppresses TypeError if the stream is already closed.
|
|
60
|
+
*/
|
|
61
|
+
protected enqueue(controller: TransformStreamDefaultController<O>, chunk: O): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Called when an error occurs during `onTransform()` or `onFlush()`.
|
|
64
|
+
* Subclasses can override to clean up internal state (e.g. reset buffers).
|
|
65
|
+
* The stream is already marked as terminated when this is called.
|
|
66
|
+
*
|
|
67
|
+
* @param error - The error that was caught
|
|
68
|
+
* @param phase - The lifecycle phase where the error occurred
|
|
69
|
+
*/
|
|
70
|
+
protected onError(error: unknown, phase: TransformerPhase): void;
|
|
71
|
+
/**
|
|
72
|
+
* Transform an input chunk into output chunk(s).
|
|
73
|
+
* Use `this.enqueue(controller, chunk)` instead of `controller.enqueue()`.
|
|
74
|
+
* May return a Promise for asynchronous processing.
|
|
75
|
+
*
|
|
76
|
+
* @param chunk - The input chunk to transform
|
|
77
|
+
* @param controller - The stream controller (use `this.enqueue()` for output)
|
|
78
|
+
*/
|
|
79
|
+
protected abstract onTransform(chunk: I, controller: TransformStreamDefaultController<O>): void | Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Called when the stream is ending. Override to flush remaining state.
|
|
82
|
+
* May return a Promise for asynchronous processing.
|
|
83
|
+
* Default implementation does nothing.
|
|
84
|
+
*
|
|
85
|
+
* @param controller
|
|
86
|
+
*/
|
|
87
|
+
protected onFlush(controller: TransformStreamDefaultController<O>): void | Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=safeTransformer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeTransformer.d.ts","sourceRoot":"","sources":["../src/safeTransformer.ts"],"names":[],"mappings":"AAeA;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,OAAO,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,8BAAsB,eAAe,CAAC,CAAC,EAAE,CAAC,CAAE,YAAW,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IACtE;;;OAGG;IAEH,SAAS,CAAC,UAAU,UAAS;IAE7B;;;;OAIG;IACG,SAAS,CACb,KAAK,EAAE,CAAC,EACR,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,GAC9C,OAAO,CAAC,IAAI,CAAC;IAchB;;;;;OAKG;IACG,KAAK,CAAC,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAY3E;;;OAGG;IACH,OAAO,CAAC,WAAW;IAQnB;;;OAGG;IACH,SAAS,CAAC,SAAS,CAAC,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,GAAG,OAAO;IAK7E;;;OAGG;IACH,SAAS,CAAC,OAAO,CACf,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,EAC/C,KAAK,EAAE,CAAC,GACP,OAAO;IAIV;;;;;;;OAOG;IACH,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAGhE;;;;;;;OAOG;IACH,SAAS,CAAC,QAAQ,CAAC,WAAW,CAC5B,KAAK,EAAE,CAAC,EACR,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,GAC9C,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAEvB;;;;;;OAMG;IACH,SAAS,CAAC,OAAO,CACf,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,GAC9C,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAGxB"}
|