🚀. Socket Launch Week Day 2:Introducing Manifest Alerts.Learn more
Sign In

@ahoo-wang/fetcher-eventstream

Package Overview
Dependencies
Maintainers
1
Versions
340
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

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

Comparing version
3.16.5
to
3.16.6
+52
dist/streamController.d.ts
/**
* Union type representing a stream controller that supports safe operations.
*
* Both ReadableStreamDefaultController and TransformStreamDefaultController
* share the `enqueue()` and `error()` methods. TransformStreamDefaultController
* additionally provides `terminate()`. This union type allows the safe
* utility functions to work with either controller kind.
*
* @template T - The type of chunks the controller handles
*/
export type StreamController<T> = ReadableStreamDefaultController<T> | TransformStreamDefaultController<T>;
/**
* Safely terminates a TransformStream controller, ignoring the TypeError
* that occurs if the stream has already been terminated.
*
* After controller.terminate() is called, upstream may still push chunks
* before the backpressure signal propagates, causing transform() to be
* invoked again. A second call to controller.terminate() throws TypeError,
* which this function suppresses.
*
* @param controller - The TransformStream controller to terminate
* @returns true if termination succeeded, false if the stream was already closed
*/
export declare function safeTerminate<T>(controller: TransformStreamDefaultController<T>): boolean;
/**
* Safely enqueues a chunk to a stream controller, ignoring the TypeError
* that occurs if the stream has already been closed or errored.
*
* After a stream is terminated or errored, upstream may still push chunks
* before the signal propagates. Calling controller.enqueue() on a closed
* stream throws TypeError, which this function suppresses.
*
* @param controller - The stream controller to enqueue to
* @param chunk - The chunk to enqueue
* @returns true if the chunk was enqueued, false if the stream was already closed
*/
export declare function safeEnqueue<T>(controller: StreamController<T>, chunk: T): boolean;
/**
* Safely errors a stream controller, ignoring the TypeError
* that occurs if the stream has already been closed or errored.
*
* After a stream is terminated or errored, subsequent error signals may
* arrive before the backpressure propagates. Calling controller.error()
* on an already-closed stream throws TypeError, which this function
* suppresses. Non-TypeError exceptions are re-thrown.
*
* @param controller - The stream controller to error
* @param reason - The error reason to pass to the controller
* @returns true if the error was set, false if the stream was already closed
*/
export declare function safeError<T>(controller: StreamController<T>, reason: any): boolean;
//# sourceMappingURL=streamController.d.ts.map
{"version":3,"file":"streamController.d.ts","sourceRoot":"","sources":["../src/streamController.ts"],"names":[],"mappings":"AAaA;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAC1B,+BAA+B,CAAC,CAAC,CAAC,GAClC,gCAAgC,CAAC,CAAC,CAAC,CAAC;AAkCxC;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,GAC9C,OAAO,CAET;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC3B,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC/B,KAAK,EAAE,CAAC,GACP,OAAO,CAET;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,CAAC,EACzB,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC/B,MAAM,EAAE,GAAG,GACV,OAAO,CAET"}
+1
-0

@@ -57,2 +57,3 @@ /**

export * from './readableStreams';
export * from './streamController';
//# sourceMappingURL=index.d.ts.map
+1
-1

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

{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,cAAc,wBAAwB,CAAC;AACvC,cAAc,sCAAsC,CAAC;AACrD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,aAAa,CAAC;AAC5B,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mBAAmB,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,cAAc,wBAAwB,CAAC;AACvC,cAAc,sCAAsC,CAAC;AACrD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,aAAa,CAAC;AAC5B,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
import { CONTENT_TYPE_HEADER as e, ContentTypeValues as t, FetcherError as n } from "@ahoo-wang/fetcher";
//#region src/streamController.ts
function r(e) {
try {
return e(), !0;
} catch (e) {
if (e instanceof TypeError || Object.prototype.toString.call(e) === "[object TypeError]") return !1;
throw e;
}
}
function i(e) {
return r(() => e.terminate());
}
function a(e, t) {
return r(() => e.enqueue(t));
}
function o(e, t) {
return r(() => e.error(t));
}
//#endregion
//#region src/textLineTransformStream.ts
var r = class {
var s = class {
constructor() {

@@ -12,5 +31,5 @@ this.buffer = "";

this.buffer = n.pop() || "";
for (let e of n) t.enqueue(e);
for (let e of n) a(t, e);
} catch (e) {
t.error(e);
o(t, e);
}

@@ -20,12 +39,12 @@ }

try {
this.buffer && e.enqueue(this.buffer);
this.buffer && a(e, this.buffer);
} catch (t) {
e.error(t);
o(e, t);
}
}
}, i = class extends TransformStream {
}, c = class extends TransformStream {
constructor() {
super(new r());
super(new s());
}
}, a = class {
}, l = class {
static {

@@ -44,14 +63,14 @@ this.ID = "id";

};
function o(e, t, n) {
function u(e, t, n) {
switch (e) {
case a.EVENT:
case l.EVENT:
n.event = t;
break;
case a.DATA:
case l.DATA:
n.data.push(t);
break;
case a.ID:
case l.ID:
n.id = t;
break;
case a.RETRY: {
case l.RETRY: {
let e = parseInt(t, 10);

@@ -64,6 +83,6 @@ isNaN(e) || (n.retry = e);

}
var s = "message", c = class {
var d = "message", f = class {
constructor() {
this.currentEventState = {
event: s,
event: d,
id: void 0,

@@ -75,3 +94,3 @@ retry: void 0,

resetEventState() {
this.currentEventState.event = s, this.currentEventState.id = void 0, this.currentEventState.retry = void 0, this.currentEventState.data = [];
this.currentEventState.event = d, this.currentEventState.id = void 0, this.currentEventState.retry = void 0, this.currentEventState.data = [];
}

@@ -82,16 +101,15 @@ transform(e, t) {

if (e.trim() === "") {
n.data.length > 0 && (t.enqueue({
event: n.event || s,
n.data.length > 0 && (a(t, {
event: n.event || d,
data: n.data.join("\n"),
id: n.id || "",
retry: n.retry
}), n.event = s, n.data = []);
}), n.event = d, n.data = []);
return;
}
if (e.startsWith(":")) return;
let r = e.indexOf(":"), i, a;
r === -1 ? (i = e.toLowerCase(), a = "") : (i = e.substring(0, r).toLowerCase(), a = e.substring(r + 1), a.startsWith(" ") && (a = a.substring(1))), i = i.trim(), a = a.trim(), o(i, a, n);
let r = e.indexOf(":"), i, o;
r === -1 ? (i = e.toLowerCase(), o = "") : (i = e.substring(0, r).toLowerCase(), o = e.substring(r + 1), o.startsWith(" ") && (o = o.substring(1))), i = i.trim(), o = o.trim(), u(i, o, n);
} catch (n) {
let r = /* @__PURE__ */ Error(`Failed to process chunk: "${e}". ${n instanceof Error ? n.message : String(n)}`);
t.error(r), this.resetEventState();
o(t, /* @__PURE__ */ Error(`Failed to process chunk: "${e}". ${n instanceof Error ? n.message : String(n)}`)), this.resetEventState();
}

@@ -102,4 +120,4 @@ }

try {
t.data.length > 0 && e.enqueue({
event: t.event || s,
t.data.length > 0 && a(e, {
event: t.event || d,
data: t.data.join("\n"),

@@ -110,4 +128,3 @@ id: t.id || "",

} catch (t) {
let n = /* @__PURE__ */ Error(`Failed to flush remaining data. ${t instanceof Error ? t.message : String(t)}`);
e.error(n);
o(e, /* @__PURE__ */ Error(`Failed to flush remaining data. ${t instanceof Error ? t.message : String(t)}`));
} finally {

@@ -117,7 +134,7 @@ this.resetEventState();

}
}, l = class extends TransformStream {
}, p = class extends TransformStream {
constructor() {
super(new c());
super(new f());
}
}, u = class e extends n {
}, m = class e extends n {
constructor(t, n, r) {

@@ -127,21 +144,20 @@ super(n, r), this.response = t, this.name = "EventStreamConvertError", Object.setPrototypeOf(this, e.prototype);

};
function d(e) {
if (!e.body) throw new u(e, "Response body is null");
return e.body.pipeThrough(new TextDecoderStream("utf-8")).pipeThrough(new i()).pipeThrough(new l());
function h(e) {
if (!e.body) throw new m(e, "Response body is null");
return e.body.pipeThrough(new TextDecoderStream("utf-8")).pipeThrough(new c()).pipeThrough(new p());
}
//#endregion
//#region src/jsonServerSentEventTransformStream.ts
var f = class {
var g = class {
constructor(e) {
this.terminateDetector = e;
this.terminateDetector = e, this.terminated = !1;
}
transform(e, t) {
try {
if (!this.terminated) try {
if (this.terminateDetector?.(e)) {
t.terminate();
this.terminated = !0, i(t);
return;
}
let n = JSON.parse(e.data);
t.enqueue({
data: n,
a(t, {
data: JSON.parse(e.data),
event: e.event,

@@ -152,17 +168,16 @@ id: e.id,

} catch (e) {
t.error(e);
return;
this.terminated = !0, o(t, e);
}
}
}, p = class extends TransformStream {
}, _ = class extends TransformStream {
constructor(e) {
super(new f(e));
super(new g(e));
}
};
function m(e, t) {
return e.pipeThrough(new p(t));
function v(e, t) {
return e.pipeThrough(new _(t));
}
//#endregion
//#region src/eventStreamResultExtractor.ts
var h = (e) => e.requiredResponse.requiredEventStream(), g = (e) => e.requiredResponse.requiredJsonEventStream();
var y = (e) => e.requiredResponse.requiredEventStream(), b = (e) => e.requiredResponse.requiredJsonEventStream();
//#endregion

@@ -186,13 +201,13 @@ //#region src/responses.ts

}), Object.prototype.hasOwnProperty.call(Response.prototype, "eventStream") || (Response.prototype.eventStream = function() {
return this.isEventStream ? d(this) : null;
return this.isEventStream ? h(this) : null;
}), Object.prototype.hasOwnProperty.call(Response.prototype, "requiredEventStream") || (Response.prototype.requiredEventStream = function() {
let e = this.eventStream();
if (!e) throw new u(this, `Event stream is not available. Response content-type: [${this.contentType}]`);
if (!e) throw new m(this, `Event stream is not available. Response content-type: [${this.contentType}]`);
return e;
}), Object.prototype.hasOwnProperty.call(Response.prototype, "jsonEventStream") || (Response.prototype.jsonEventStream = function(e) {
let t = this.eventStream();
return t ? m(t, e) : null;
return t ? v(t, e) : null;
}), Object.prototype.hasOwnProperty.call(Response.prototype, "requiredJsonEventStream") || (Response.prototype.requiredJsonEventStream = function(e) {
let t = this.jsonEventStream(e);
if (!t) throw new u(this, `Event stream is not available. Response content-type: [${this.contentType}]`);
if (!t) throw new m(this, `Event stream is not available. Response content-type: [${this.contentType}]`);
return t;

@@ -203,3 +218,3 @@ });

//#region src/readableStreamAsyncIterable.ts
var _ = class {
var x = class {
constructor(e) {

@@ -256,9 +271,9 @@ this.stream = e, this._locked = !0, this.reader = e.getReader();

}
}, v = typeof ReadableStream < "u" && typeof ReadableStream.prototype[Symbol.asyncIterator] == "function";
v || (ReadableStream.prototype[Symbol.asyncIterator] = function() {
return new _(this);
}, S = typeof ReadableStream < "u" && typeof ReadableStream.prototype[Symbol.asyncIterator] == "function";
S || (ReadableStream.prototype[Symbol.asyncIterator] = function() {
return new x(this);
});
//#endregion
export { u as EventStreamConvertError, h as EventStreamResultExtractor, g as JsonEventStreamResultExtractor, f as JsonServerSentEventTransform, p as JsonServerSentEventTransformStream, _ as ReadableStreamAsyncIterable, a as ServerSentEventFields, l as ServerSentEventTransformStream, c as ServerSentEventTransformer, i as TextLineTransformStream, r as TextLineTransformer, v as isReadableStreamAsyncIterableSupported, m as toJsonServerSentEventStream, d as toServerSentEventStream };
export { m as EventStreamConvertError, y as EventStreamResultExtractor, b as JsonEventStreamResultExtractor, g as JsonServerSentEventTransform, _ as JsonServerSentEventTransformStream, x as ReadableStreamAsyncIterable, l as ServerSentEventFields, p as ServerSentEventTransformStream, f as ServerSentEventTransformer, c as TextLineTransformStream, s as TextLineTransformer, S as isReadableStreamAsyncIterableSupported, a as safeEnqueue, o as safeError, i as safeTerminate, v as toJsonServerSentEventStream, h as toServerSentEventStream };
//# sourceMappingURL=index.es.js.map

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

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

(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`@ahoo-wang/fetcher`)):typeof define==`function`&&define.amd?define([`exports`,`@ahoo-wang/fetcher`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.FetcherEventStream={},e.Fetcher))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=class{constructor(){this.buffer=``}transform(e,t){try{this.buffer+=e;let n=this.buffer.split(`
`);this.buffer=n.pop()||``;for(let e of n)t.enqueue(e)}catch(e){t.error(e)}}flush(e){try{this.buffer&&e.enqueue(this.buffer)}catch(t){e.error(t)}}},r=class extends TransformStream{constructor(){super(new n)}},i=class{static{this.ID=`id`}static{this.RETRY=`retry`}static{this.EVENT=`event`}static{this.DATA=`data`}};function a(e,t,n){switch(e){case i.EVENT:n.event=t;break;case i.DATA:n.data.push(t);break;case i.ID:n.id=t;break;case i.RETRY:{let e=parseInt(t,10);isNaN(e)||(n.retry=e);break}default:break}}var o=`message`,s=class{constructor(){this.currentEventState={event:o,id:void 0,retry:void 0,data:[]}}resetEventState(){this.currentEventState.event=o,this.currentEventState.id=void 0,this.currentEventState.retry=void 0,this.currentEventState.data=[]}transform(e,t){let n=this.currentEventState;try{if(e.trim()===``){n.data.length>0&&(t.enqueue({event:n.event||o,data:n.data.join(`
`),id:n.id||``,retry:n.retry}),n.event=o,n.data=[]);return}if(e.startsWith(`:`))return;let r=e.indexOf(`:`),i,s;r===-1?(i=e.toLowerCase(),s=``):(i=e.substring(0,r).toLowerCase(),s=e.substring(r+1),s.startsWith(` `)&&(s=s.substring(1))),i=i.trim(),s=s.trim(),a(i,s,n)}catch(n){let r=Error(`Failed to process chunk: "${e}". ${n instanceof Error?n.message:String(n)}`);t.error(r),this.resetEventState()}}flush(e){let t=this.currentEventState;try{t.data.length>0&&e.enqueue({event:t.event||o,data:t.data.join(`
`),id:t.id||``,retry:t.retry})}catch(t){let n=Error(`Failed to flush remaining data. ${t instanceof Error?t.message:String(t)}`);e.error(n)}finally{this.resetEventState()}}},c=class extends TransformStream{constructor(){super(new s)}},l=class e extends t.FetcherError{constructor(t,n,r){super(n,r),this.response=t,this.name=`EventStreamConvertError`,Object.setPrototypeOf(this,e.prototype)}};function u(e){if(!e.body)throw new l(e,`Response body is null`);return e.body.pipeThrough(new TextDecoderStream(`utf-8`)).pipeThrough(new r).pipeThrough(new c)}var d=class{constructor(e){this.terminateDetector=e}transform(e,t){try{if(this.terminateDetector?.(e)){t.terminate();return}let n=JSON.parse(e.data);t.enqueue({data:n,event:e.event,id:e.id,retry:e.retry})}catch(e){t.error(e);return}}},f=class extends TransformStream{constructor(e){super(new d(e))}};function p(e,t){return e.pipeThrough(new f(t))}var m=e=>e.requiredResponse.requiredEventStream(),h=e=>e.requiredResponse.requiredJsonEventStream();if(typeof Response<`u`){let e=`contentType`;Object.prototype.hasOwnProperty.call(Response.prototype,e)||Object.defineProperty(Response.prototype,e,{get(){return this.headers.get(t.CONTENT_TYPE_HEADER)},configurable:!0});let n=`isEventStream`;Object.prototype.hasOwnProperty.call(Response.prototype,n)||Object.defineProperty(Response.prototype,n,{get(){let e=this.contentType;return e?e.includes(t.ContentTypeValues.TEXT_EVENT_STREAM):!1},configurable:!0}),Object.prototype.hasOwnProperty.call(Response.prototype,`eventStream`)||(Response.prototype.eventStream=function(){return this.isEventStream?u(this):null}),Object.prototype.hasOwnProperty.call(Response.prototype,`requiredEventStream`)||(Response.prototype.requiredEventStream=function(){let e=this.eventStream();if(!e)throw new l(this,`Event stream is not available. Response content-type: [${this.contentType}]`);return e}),Object.prototype.hasOwnProperty.call(Response.prototype,`jsonEventStream`)||(Response.prototype.jsonEventStream=function(e){let t=this.eventStream();return t?p(t,e):null}),Object.prototype.hasOwnProperty.call(Response.prototype,`requiredJsonEventStream`)||(Response.prototype.requiredJsonEventStream=function(e){let t=this.jsonEventStream(e);if(!t)throw new l(this,`Event stream is not available. Response content-type: [${this.contentType}]`);return t})}var g=class{constructor(e){this.stream=e,this._locked=!0,this.reader=e.getReader()}get locked(){return this._locked}releaseLock(){if(!this._locked)return!1;this._locked=!1;try{return this.reader.releaseLock(),!0}catch(e){return console.debug(`Failed to release reader lock:`,e),!1}}[Symbol.asyncIterator](){return this}async next(){try{let{done:e,value:t}=await this.reader.read();return e?(this.releaseLock(),{done:!0,value:void 0}):{done:!1,value:t}}catch(e){throw this.releaseLock(),e}}async return(){try{await this.reader.cancel()}catch(e){console.debug(`Failed to cancel stream reader:`,e)}finally{this.releaseLock()}return{done:!0,value:void 0}}async throw(e){return console.debug(`Throwing error:`,e),this.releaseLock(),{done:!0,value:void 0}}},_=typeof ReadableStream<`u`&&typeof ReadableStream.prototype[Symbol.asyncIterator]==`function`;_||(ReadableStream.prototype[Symbol.asyncIterator]=function(){return new g(this)}),e.EventStreamConvertError=l,e.EventStreamResultExtractor=m,e.JsonEventStreamResultExtractor=h,e.JsonServerSentEventTransform=d,e.JsonServerSentEventTransformStream=f,e.ReadableStreamAsyncIterable=g,e.ServerSentEventFields=i,e.ServerSentEventTransformStream=c,e.ServerSentEventTransformer=s,e.TextLineTransformStream=r,e.TextLineTransformer=n,e.isReadableStreamAsyncIterableSupported=_,e.toJsonServerSentEventStream=p,e.toServerSentEventStream=u});
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`@ahoo-wang/fetcher`)):typeof define==`function`&&define.amd?define([`exports`,`@ahoo-wang/fetcher`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.FetcherEventStream={},e.Fetcher))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function n(e){try{return e(),!0}catch(e){if(e instanceof TypeError||Object.prototype.toString.call(e)===`[object TypeError]`)return!1;throw e}}function r(e){return n(()=>e.terminate())}function i(e,t){return n(()=>e.enqueue(t))}function a(e,t){return n(()=>e.error(t))}var o=class{constructor(){this.buffer=``}transform(e,t){try{this.buffer+=e;let n=this.buffer.split(`
`);this.buffer=n.pop()||``;for(let e of n)i(t,e)}catch(e){a(t,e)}}flush(e){try{this.buffer&&i(e,this.buffer)}catch(t){a(e,t)}}},s=class extends TransformStream{constructor(){super(new o)}},c=class{static{this.ID=`id`}static{this.RETRY=`retry`}static{this.EVENT=`event`}static{this.DATA=`data`}};function l(e,t,n){switch(e){case c.EVENT:n.event=t;break;case c.DATA:n.data.push(t);break;case c.ID:n.id=t;break;case c.RETRY:{let e=parseInt(t,10);isNaN(e)||(n.retry=e);break}default:break}}var u=`message`,d=class{constructor(){this.currentEventState={event:u,id:void 0,retry:void 0,data:[]}}resetEventState(){this.currentEventState.event=u,this.currentEventState.id=void 0,this.currentEventState.retry=void 0,this.currentEventState.data=[]}transform(e,t){let n=this.currentEventState;try{if(e.trim()===``){n.data.length>0&&(i(t,{event:n.event||u,data:n.data.join(`
`),id:n.id||``,retry:n.retry}),n.event=u,n.data=[]);return}if(e.startsWith(`:`))return;let r=e.indexOf(`:`),a,o;r===-1?(a=e.toLowerCase(),o=``):(a=e.substring(0,r).toLowerCase(),o=e.substring(r+1),o.startsWith(` `)&&(o=o.substring(1))),a=a.trim(),o=o.trim(),l(a,o,n)}catch(n){a(t,Error(`Failed to process chunk: "${e}". ${n instanceof Error?n.message:String(n)}`)),this.resetEventState()}}flush(e){let t=this.currentEventState;try{t.data.length>0&&i(e,{event:t.event||u,data:t.data.join(`
`),id:t.id||``,retry:t.retry})}catch(t){a(e,Error(`Failed to flush remaining data. ${t instanceof Error?t.message:String(t)}`))}finally{this.resetEventState()}}},f=class extends TransformStream{constructor(){super(new d)}},p=class e extends t.FetcherError{constructor(t,n,r){super(n,r),this.response=t,this.name=`EventStreamConvertError`,Object.setPrototypeOf(this,e.prototype)}};function m(e){if(!e.body)throw new p(e,`Response body is null`);return e.body.pipeThrough(new TextDecoderStream(`utf-8`)).pipeThrough(new s).pipeThrough(new f)}var h=class{constructor(e){this.terminateDetector=e,this.terminated=!1}transform(e,t){if(!this.terminated)try{if(this.terminateDetector?.(e)){this.terminated=!0,r(t);return}i(t,{data:JSON.parse(e.data),event:e.event,id:e.id,retry:e.retry})}catch(e){this.terminated=!0,a(t,e)}}},g=class extends TransformStream{constructor(e){super(new h(e))}};function _(e,t){return e.pipeThrough(new g(t))}var v=e=>e.requiredResponse.requiredEventStream(),y=e=>e.requiredResponse.requiredJsonEventStream();if(typeof Response<`u`){let e=`contentType`;Object.prototype.hasOwnProperty.call(Response.prototype,e)||Object.defineProperty(Response.prototype,e,{get(){return this.headers.get(t.CONTENT_TYPE_HEADER)},configurable:!0});let n=`isEventStream`;Object.prototype.hasOwnProperty.call(Response.prototype,n)||Object.defineProperty(Response.prototype,n,{get(){let e=this.contentType;return e?e.includes(t.ContentTypeValues.TEXT_EVENT_STREAM):!1},configurable:!0}),Object.prototype.hasOwnProperty.call(Response.prototype,`eventStream`)||(Response.prototype.eventStream=function(){return this.isEventStream?m(this):null}),Object.prototype.hasOwnProperty.call(Response.prototype,`requiredEventStream`)||(Response.prototype.requiredEventStream=function(){let e=this.eventStream();if(!e)throw new p(this,`Event stream is not available. Response content-type: [${this.contentType}]`);return e}),Object.prototype.hasOwnProperty.call(Response.prototype,`jsonEventStream`)||(Response.prototype.jsonEventStream=function(e){let t=this.eventStream();return t?_(t,e):null}),Object.prototype.hasOwnProperty.call(Response.prototype,`requiredJsonEventStream`)||(Response.prototype.requiredJsonEventStream=function(e){let t=this.jsonEventStream(e);if(!t)throw new p(this,`Event stream is not available. Response content-type: [${this.contentType}]`);return t})}var b=class{constructor(e){this.stream=e,this._locked=!0,this.reader=e.getReader()}get locked(){return this._locked}releaseLock(){if(!this._locked)return!1;this._locked=!1;try{return this.reader.releaseLock(),!0}catch(e){return console.debug(`Failed to release reader lock:`,e),!1}}[Symbol.asyncIterator](){return this}async next(){try{let{done:e,value:t}=await this.reader.read();return e?(this.releaseLock(),{done:!0,value:void 0}):{done:!1,value:t}}catch(e){throw this.releaseLock(),e}}async return(){try{await this.reader.cancel()}catch(e){console.debug(`Failed to cancel stream reader:`,e)}finally{this.releaseLock()}return{done:!0,value:void 0}}async throw(e){return console.debug(`Throwing error:`,e),this.releaseLock(),{done:!0,value:void 0}}},x=typeof ReadableStream<`u`&&typeof ReadableStream.prototype[Symbol.asyncIterator]==`function`;x||(ReadableStream.prototype[Symbol.asyncIterator]=function(){return new b(this)}),e.EventStreamConvertError=p,e.EventStreamResultExtractor=v,e.JsonEventStreamResultExtractor=y,e.JsonServerSentEventTransform=h,e.JsonServerSentEventTransformStream=g,e.ReadableStreamAsyncIterable=b,e.ServerSentEventFields=c,e.ServerSentEventTransformStream=f,e.ServerSentEventTransformer=d,e.TextLineTransformStream=s,e.TextLineTransformer=o,e.isReadableStreamAsyncIterableSupported=x,e.safeEnqueue=i,e.safeError=a,e.safeTerminate=r,e.toJsonServerSentEventStream=_,e.toServerSentEventStream=m});
//# sourceMappingURL=index.umd.js.map

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

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

@@ -45,2 +45,7 @@ import { ServerSentEvent } from './serverSentEventTransformStream';

/**
* Guard flag to prevent any controller operations after the stream has been
* terminated or errored. Once set, all subsequent chunks are silently dropped.
*/
private terminated;
/**
* Creates a new JsonServerSentEventTransform instance.

@@ -56,19 +61,16 @@ *

*
* This method first checks if the event should terminate the stream using the terminateDetector.
* If termination is required, the controller is terminated. Otherwise, the event data is parsed
* This method first checks if the stream has already been terminated. If so,
* the chunk is silently dropped. Otherwise, it checks if the event should
* terminate the stream using the terminateDetector. If termination is required,
* the controller is safely terminated. Otherwise, the event data is parsed
* as JSON and enqueued as a JsonServerSentEvent.
*
* If the terminateDetector throws an exception, the stream is terminated with an error to prevent
* corrupted state.
* All controller operations use safe wrappers (safeTerminate, safeEnqueue,
* safeError) that suppress TypeError from already-closed streams as normal
* control flow. Any error thrown during detection or parsing (including
* TypeError from detector/parse logic) is caught, the stream is errored
* via safeError, and subsequent chunks are dropped.
*
* @param chunk - The ServerSentEvent to transform
* @param controller - The TransformStream controller for managing the stream
* @throws {SyntaxError} If the event data is not valid JSON
* @throws {Error} If the terminateDetector throws an exception
*
* @example
* ```typescript
* const transformer = new JsonServerSentEventTransform<MyData>();
* // This will be called automatically by the TransformStream
* ```
*/

@@ -75,0 +77,0 @@ transform(chunk: ServerSentEvent, controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>): void;

@@ -1,1 +0,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;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB,CAAC,IAAI,CAAE,SAAQ,IAAI,CACrD,eAAe,EACf,MAAM,CACP;IACC,0CAA0C;IAC1C,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;;;;;;GAQG;AACH,qBAAa,4BAA4B,CAAC,IAAI,CAAE,YAAW,WAAW,CACpE,eAAe,EACf,mBAAmB,CAAC,IAAI,CAAC,CAC1B;IAQa,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IAP/C;;;;;;OAMG;gBAC0B,iBAAiB,CAAC,EAAE,iBAAiB,YAAA;IAElE;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,CACP,KAAK,EAAE,eAAe,EACtB,UAAU,EAAE,gCAAgC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;CAsB1E;AAED;;;;;;;;GAQG;AACH,qBAAa,kCAAkC,CAAC,IAAI,CAAE,SAAQ,eAAe,CAC3E,eAAe,EACf,mBAAmB,CAAC,IAAI,CAAC,CAC1B;IACC;;;;;;;;;;;;;;;;OAgBG;gBACS,iBAAiB,CAAC,EAAE,iBAAiB;CAGlD;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,yBAAyB,CAAC,IAAI,IAAI,cAAc,CAC1D,mBAAmB,CAAC,IAAI,CAAC,CAC1B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAC9C,qBAAqB,EAAE,qBAAqB,EAC5C,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,yBAAyB,CAAC,IAAI,CAAC,CAIjC"}
{"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;AAGpE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB,CAAC,IAAI,CAAE,SAAQ,IAAI,CACrD,eAAe,EACf,MAAM,CACP;IACC,0CAA0C;IAC1C,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;;;;;;GAQG;AACH,qBAAa,4BAA4B,CAAC,IAAI,CAAE,YAAW,WAAW,CACpE,eAAe,EACf,mBAAmB,CAAC,IAAI,CAAC,CAC1B;IAca,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IAb/C;;;OAGG;IACH,OAAO,CAAC,UAAU,CAAS;IAE3B;;;;;;OAMG;gBAC0B,iBAAiB,CAAC,EAAE,iBAAiB,YAAA;IAElE;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CACP,KAAK,EAAE,eAAe,EACtB,UAAU,EAAE,gCAAgC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;CA0B1E;AAED;;;;;;;;GAQG;AACH,qBAAa,kCAAkC,CAAC,IAAI,CAAE,SAAQ,eAAe,CAC3E,eAAe,EACf,mBAAmB,CAAC,IAAI,CAAC,CAC1B;IACC;;;;;;;;;;;;;;;;OAgBG;gBACS,iBAAiB,CAAC,EAAE,iBAAiB;CAGlD;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,yBAAyB,CAAC,IAAI,IAAI,cAAc,CAC1D,mBAAmB,CAAC,IAAI,CAAC,CAC1B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAC9C,qBAAqB,EAAE,qBAAqB,EAC5C,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,yBAAyB,CAAC,IAAI,CAAC,CAIjC"}

@@ -20,9 +20,2 @@ /**

}
/**
* Constants for Server-Sent Event field names.
*
* This class provides string constants for the standard SSE field names as defined
* in the W3C Server-Sent Events specification. These constants help ensure
* consistent field name usage throughout the parsing logic.
*/
export declare class ServerSentEventFields {

@@ -29,0 +22,0 @@ /** The field name for event ID */

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

{"version":3,"file":"serverSentEventTransformStream.d.ts","sourceRoot":"","sources":["../src/serverSentEventTransformStream.ts"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,qBAAa,qBAAqB;IAChC,kCAAkC;IAClC,MAAM,CAAC,QAAQ,CAAC,EAAE,QAAQ;IAC1B,wCAAwC;IACxC,MAAM,CAAC,QAAQ,CAAC,KAAK,WAAW;IAChC,oCAAoC;IACpC,MAAM,CAAC,QAAQ,CAAC,KAAK,WAAW;IAChC,oCAAoC;IACpC,MAAM,CAAC,QAAQ,CAAC,IAAI,UAAU;CAC/B;AAmED;;;;;;GAMG;AACH,qBAAa,0BAA2B,YAAW,WAAW,CAC5D,MAAM,EACN,eAAe,CAChB;IAEC,OAAO,CAAC,iBAAiB,CAKvB;IAEF;;;OAGG;IACH,OAAO,CAAC,eAAe;IAOvB;;;;;;;;;;;OAWG;IACH,SAAS,CACP,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,gCAAgC,CAAC,eAAe,CAAC;IA+D/D;;;;OAIG;IACH,KAAK,CAAC,UAAU,EAAE,gCAAgC,CAAC,eAAe,CAAC;CAsBpE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,8BAA+B,SAAQ,eAAe,CACjE,MAAM,EACN,eAAe,CAChB;IACC;;;;;OAKG;;CAIJ"}
{"version":3,"file":"serverSentEventTransformStream.d.ts","sourceRoot":"","sources":["../src/serverSentEventTransformStream.ts"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAWD,qBAAa,qBAAqB;IAChC,kCAAkC;IAClC,MAAM,CAAC,QAAQ,CAAC,EAAE,QAAQ;IAC1B,wCAAwC;IACxC,MAAM,CAAC,QAAQ,CAAC,KAAK,WAAW;IAChC,oCAAoC;IACpC,MAAM,CAAC,QAAQ,CAAC,KAAK,WAAW;IAChC,oCAAoC;IACpC,MAAM,CAAC,QAAQ,CAAC,IAAI,UAAU;CAC/B;AAmED;;;;;;GAMG;AACH,qBAAa,0BAA2B,YAAW,WAAW,CAC5D,MAAM,EACN,eAAe,CAChB;IAEC,OAAO,CAAC,iBAAiB,CAKvB;IAEF;;;OAGG;IACH,OAAO,CAAC,eAAe;IAOvB;;;;;;;;;;;OAWG;IACH,SAAS,CACP,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,gCAAgC,CAAC,eAAe,CAAC;IA+D/D;;;;OAIG;IACH,KAAK,CAAC,UAAU,EAAE,gCAAgC,CAAC,eAAe,CAAC;CAsBpE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,8BAA+B,SAAQ,eAAe,CACjE,MAAM,EACN,eAAe,CAChB;IACC;;;;;OAKG;;CAIJ"}

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

/**
* Transformer that splits text into lines.
*
* This transformer accumulates chunks of text and splits them by newline characters ('\n'),
* emitting each complete line as a separate chunk. It handles partial lines that span multiple
* input chunks by maintaining an internal buffer. Lines are emitted without the newline character.
*
* The transformer handles various edge cases:
* - Lines that span multiple input chunks
* - Empty lines (emitted as empty strings)
* - Text without trailing newlines (buffered until stream ends)
* - Mixed line endings (only '\n' is recognized as line separator)
*
* @implements {Transformer<string, string>}
*
* @example
* ```typescript
* const transformer = new TextLineTransformer();
* // Input: "line1\nline2\npartial"
* // Output: ["line1", "line2"]
* // Buffer: "partial"
*
* // Later input: "line\n"
* // Output: ["partialline"]
* // Buffer: ""
* ```
*/
export declare class TextLineTransformer implements Transformer<string, string> {

@@ -29,0 +2,0 @@ private buffer;

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

{"version":3,"file":"textLineTransformStream.d.ts","sourceRoot":"","sources":["../src/textLineTransformStream.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,mBAAoB,YAAW,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;IACrE,OAAO,CAAC,MAAM,CAAM;IAEpB;;;;;OAKG;IACH,SAAS,CACP,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,gCAAgC,CAAC,MAAM,CAAC;IAetD;;;;OAIG;IACH,KAAK,CAAC,UAAU,EAAE,gCAAgC,CAAC,MAAM,CAAC;CAU3D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,uBAAwB,SAAQ,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC;IAC1E;;;;OAIG;;CAIJ"}
{"version":3,"file":"textLineTransformStream.d.ts","sourceRoot":"","sources":["../src/textLineTransformStream.ts"],"names":[],"mappings":"AA0CA,qBAAa,mBAAoB,YAAW,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;IACrE,OAAO,CAAC,MAAM,CAAM;IAEpB;;;;;OAKG;IACH,SAAS,CACP,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,gCAAgC,CAAC,MAAM,CAAC;IAetD;;;;OAIG;IACH,KAAK,CAAC,UAAU,EAAE,gCAAgC,CAAC,MAAM,CAAC;CAU3D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,uBAAwB,SAAQ,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC;IAC1E;;;;OAIG;;CAIJ"}
{
"name": "@ahoo-wang/fetcher-eventstream",
"version": "3.16.5",
"version": "3.16.6",
"description": "Server-Sent Events (SSE) support for Fetcher HTTP client with native LLM streaming API support. Enables real-time data streaming and token-by-token LLM response handling.",

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