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

@ahoo-wang/fetcher-eventstream

Package Overview
Dependencies
Maintainers
1
Versions
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.6
to
3.16.8
+89
dist/safeTransformer.d.ts
/**
* Identifies the lifecycle phase where an error occurred.
*/
export type TransformerPhase = 'transform' | 'flush';
/**
* Abstract base class for TransformStream transformers with built-in error safety
* and termination guard.
*
* Provides three guarantees that every concrete transformer inherits:
*
* 1. **Termination guard** — Once `terminated` is set (via `terminate()` or an
* unhandled error), all subsequent chunks are silently dropped in `transform()`.
*
* 2. **Safe controller operations** — `enqueue()` delegates to
* `safeEnqueue` which suppresses TypeError from already-closed streams.
* `terminate()` delegates to `safeTerminate`.
*
* 3. **Error boundary** — Unhandled errors in `onTransform()` / `onFlush()`
* are caught, the transformer is terminated, and the error is forwarded
* via `safeError()`.
*
* Subclasses implement `onTransform()` and optionally `onFlush()` instead of
* the raw `transform()` / `flush()` methods.
*
* @template I - The type of input chunks
* @template O - The type of output chunks
*/
export declare abstract class SafeTransformer<I, O> implements Transformer<I, O> {
/**
* Guard flag indicating the stream has been terminated or errored.
* Once set, subsequent chunks are silently dropped in `transform()`.
*/
protected terminated: boolean;
/**
* Transforms an input chunk. Drops immediately if terminated.
* Delegates to `onTransform()` with error protection.
* Supports both synchronous and asynchronous `onTransform()` implementations.
*/
transform(chunk: I, controller: TransformStreamDefaultController<O>): Promise<void>;
/**
* Called when the stream ends. Always invokes `onFlush()` for cleanup,
* even if already terminated. Errors in `onFlush()` are caught and
* forwarded via `safeError()`.
* Supports both synchronous and asynchronous `onFlush()` implementations.
*/
flush(controller: TransformStreamDefaultController<O>): Promise<void>;
/**
* Safely invokes `onError()`, catching any exception to prevent
* cleanup logic from breaking the error boundary guarantee.
*/
private safeOnError;
/**
* Marks the transformer as terminated and safely terminates the controller.
* After calling this, all subsequent chunks are silently dropped.
*/
protected terminate(controller: TransformStreamDefaultController<O>): boolean;
/**
* Safely enqueues a chunk to the controller.
* Suppresses TypeError if the stream is already closed.
*/
protected enqueue(controller: TransformStreamDefaultController<O>, chunk: O): boolean;
/**
* Called when an error occurs during `onTransform()` or `onFlush()`.
* Subclasses can override to clean up internal state (e.g. reset buffers).
* The stream is already marked as terminated when this is called.
*
* @param error - The error that was caught
* @param phase - The lifecycle phase where the error occurred
*/
protected onError(error: unknown, phase: TransformerPhase): void;
/**
* Transform an input chunk into output chunk(s).
* Use `this.enqueue(controller, chunk)` instead of `controller.enqueue()`.
* May return a Promise for asynchronous processing.
*
* @param chunk - The input chunk to transform
* @param controller - The stream controller (use `this.enqueue()` for output)
*/
protected abstract onTransform(chunk: I, controller: TransformStreamDefaultController<O>): void | Promise<void>;
/**
* Called when the stream is ending. Override to flush remaining state.
* May return a Promise for asynchronous processing.
* Default implementation does nothing.
*
* @param controller
*/
protected onFlush(controller: TransformStreamDefaultController<O>): void | Promise<void>;
}
//# sourceMappingURL=safeTransformer.d.ts.map
{"version":3,"file":"safeTransformer.d.ts","sourceRoot":"","sources":["../src/safeTransformer.ts"],"names":[],"mappings":"AAeA;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,OAAO,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,8BAAsB,eAAe,CAAC,CAAC,EAAE,CAAC,CAAE,YAAW,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IACtE;;;OAGG;IAEH,SAAS,CAAC,UAAU,UAAS;IAE7B;;;;OAIG;IACG,SAAS,CACb,KAAK,EAAE,CAAC,EACR,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,GAC9C,OAAO,CAAC,IAAI,CAAC;IAchB;;;;;OAKG;IACG,KAAK,CAAC,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAY3E;;;OAGG;IACH,OAAO,CAAC,WAAW;IAQnB;;;OAGG;IACH,SAAS,CAAC,SAAS,CAAC,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,GAAG,OAAO;IAK7E;;;OAGG;IACH,SAAS,CAAC,OAAO,CACf,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,EAC/C,KAAK,EAAE,CAAC,GACP,OAAO;IAIV;;;;;;;OAOG;IACH,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAGhE;;;;;;;OAOG;IACH,SAAS,CAAC,QAAQ,CAAC,WAAW,CAC5B,KAAK,EAAE,CAAC,EACR,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,GAC9C,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAEvB;;;;;;OAMG;IACH,SAAS,CAAC,OAAO,CACf,UAAU,EAAE,gCAAgC,CAAC,CAAC,CAAC,GAC9C,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAGxB"}
+1
-0

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

export * from './readableStreams';
export * from './safeTransformer';
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;AAClC,cAAc,oBAAoB,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,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}

@@ -21,29 +21,54 @@ import { CONTENT_TYPE_HEADER as e, ContentTypeValues as t, FetcherError as n } from "@ahoo-wang/fetcher";

//#endregion
//#region src/textLineTransformStream.ts
//#region src/safeTransformer.ts
var s = class {
constructor() {
this.buffer = "";
this.terminated = !1;
}
transform(e, t) {
try {
this.buffer += e;
let n = this.buffer.split("\n");
this.buffer = n.pop() || "";
for (let e of n) a(t, e);
async transform(e, t) {
if (!this.terminated) try {
await this.onTransform(e, t);
} catch (e) {
o(t, e);
this.terminated = !0, this.safeOnError(e, "transform"), o(t, e);
}
}
flush(e) {
async flush(e) {
try {
this.buffer && a(e, this.buffer);
await this.onFlush(e);
} catch (t) {
o(e, t);
this.terminated = !0, this.safeOnError(t, "flush"), o(e, t);
} finally {
this.terminated = !0;
}
}
}, c = class extends TransformStream {
safeOnError(e, t) {
try {
this.onError(e, t);
} catch {}
}
terminate(e) {
return this.terminated = !0, i(e);
}
enqueue(e, t) {
return a(e, t);
}
onError(e, t) {}
onFlush(e) {}
}, c = class extends s {
constructor(...e) {
super(...e), this.buffer = "";
}
onTransform(e, t) {
this.buffer += e;
let n = this.buffer.split("\n");
this.buffer = n.pop() || "";
for (let e of n) this.enqueue(t, e);
}
onFlush(e) {
this.buffer && this.enqueue(e, this.buffer);
}
}, l = class extends TransformStream {
constructor() {
super(new s());
super(new c());
}
}, l = class {
}, u = class {
static {

@@ -62,25 +87,23 @@ this.ID = "id";

};
function u(e, t, n) {
function d(e, t, n) {
switch (e) {
case l.EVENT:
case u.EVENT:
n.event = t;
break;
case l.DATA:
case u.DATA:
n.data.push(t);
break;
case l.ID:
n.id = t;
case u.ID:
t.includes("\0") || (n.id = t);
break;
case l.RETRY: {
let e = parseInt(t, 10);
isNaN(e) || (n.retry = e);
case u.RETRY:
/^\d+$/.test(t) && (n.retry = parseInt(t, 10));
break;
}
default: break;
}
}
var d = "message", f = class {
constructor() {
this.currentEventState = {
event: d,
var f = "message", p = class extends s {
constructor(...e) {
super(...e), this.currentEventState = {
event: f,
id: void 0,

@@ -92,28 +115,27 @@ retry: void 0,

resetEventState() {
this.currentEventState.event = d, this.currentEventState.id = void 0, this.currentEventState.retry = void 0, this.currentEventState.data = [];
this.currentEventState.event = f, this.currentEventState.id = void 0, this.currentEventState.retry = void 0, this.currentEventState.data = [];
}
transform(e, t) {
onError(e, t) {
this.resetEventState();
}
onTransform(e, t) {
let n = this.currentEventState;
try {
if (e.trim() === "") {
n.data.length > 0 && (a(t, {
event: n.event || d,
data: n.data.join("\n"),
id: n.id || "",
retry: n.retry
}), n.event = d, n.data = []);
return;
}
if (e.startsWith(":")) return;
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) {
o(t, /* @__PURE__ */ Error(`Failed to process chunk: "${e}". ${n instanceof Error ? n.message : String(n)}`)), this.resetEventState();
if (e.trim() === "") {
n.data.length > 0 && (this.enqueue(t, {
event: n.event || f,
data: n.data.join("\n"),
id: n.id || "",
retry: n.retry
}), n.event = f, 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))), d(i, a, n);
}
flush(e) {
onFlush(e) {
let t = this.currentEventState;
try {
t.data.length > 0 && a(e, {
event: t.event || d,
t.data.length > 0 && this.enqueue(e, {
event: t.event || f,
data: t.data.join("\n"),

@@ -123,4 +145,2 @@ id: t.id || "",

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

@@ -130,7 +150,7 @@ this.resetEventState();

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

@@ -140,39 +160,36 @@ super(n, r), this.response = t, this.name = "EventStreamConvertError", Object.setPrototypeOf(this, e.prototype);

};
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());
function g(e) {
if (!e.body) throw new h(e, "Response body is null");
return e.body.pipeThrough(new TextDecoderStream("utf-8")).pipeThrough(new l()).pipeThrough(new m());
}
//#endregion
//#region src/jsonServerSentEventTransformStream.ts
var g = class {
var _ = class extends s {
constructor(e) {
this.terminateDetector = e, this.terminated = !1;
super(), this.terminateDetector = e;
}
transform(e, t) {
if (!this.terminated) try {
if (this.terminateDetector?.(e)) {
this.terminated = !0, i(t);
return;
}
a(t, {
data: JSON.parse(e.data),
event: e.event,
id: e.id,
retry: e.retry
});
} catch (e) {
this.terminated = !0, o(t, e);
onTransform(e, t) {
if (this.terminateDetector?.(e)) {
this.terminate(t);
return;
}
let n = JSON.parse(e.data);
this.enqueue(t, {
data: n,
event: e.event,
id: e.id,
retry: e.retry
});
}
}, _ = class extends TransformStream {
}, v = class extends TransformStream {
constructor(e) {
super(new g(e));
super(new _(e));
}
};
function v(e, t) {
return e.pipeThrough(new _(t));
function y(e, t) {
return e.pipeThrough(new v(t));
}
//#endregion
//#region src/eventStreamResultExtractor.ts
var y = (e) => e.requiredResponse.requiredEventStream(), b = (e) => e.requiredResponse.requiredJsonEventStream();
var b = (e) => e.requiredResponse.requiredEventStream(), x = (e) => e.requiredResponse.requiredJsonEventStream();
//#endregion

@@ -196,13 +213,13 @@ //#region src/responses.ts

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

@@ -213,3 +230,3 @@ });

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

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

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

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

{"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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"}
{"version":3,"file":"index.es.js","names":[],"sources":["../src/streamController.ts","../src/safeTransformer.ts","../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/eventStreamResultExtractor.ts","../src/responses.ts","../src/readableStreamAsyncIterable.ts","../src/readableStreams.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Union type representing a stream controller that supports safe operations.\n *\n * Both ReadableStreamDefaultController and TransformStreamDefaultController\n * share the `enqueue()` and `error()` methods. TransformStreamDefaultController\n * additionally provides `terminate()`. This union type allows the safe\n * utility functions to work with either controller kind.\n *\n * @template T - The type of chunks the controller handles\n */\nexport type StreamController<T> =\n | ReadableStreamDefaultController<T>\n | TransformStreamDefaultController<T>;\n\n/**\n * Executes an action and suppresses TypeError, returning a boolean indicating success.\n *\n * This is the shared implementation for all safe* controller functions.\n * Stream controller methods (terminate, enqueue, error) throw TypeError when\n * the stream is already closed or errored. This helper catches that TypeError\n * and returns false, while re-throwing any non-TypeError exceptions.\n *\n * Uses both `instanceof TypeError` and `Object.prototype.toString` checks\n * to handle cross-realm TypeErrors (e.g. from iframes or worker threads)\n * where `instanceof` alone would fail. The toString check matches values\n * with the internal TypeError class tag, which covers cross-realm TypeErrors\n * while rejecting plain objects that merely have a `name` property.\n *\n * @param action - The controller operation to attempt\n * @returns true if the action succeeded, false if a TypeError was caught\n */\nfunction suppressTypeError(action: () => void): boolean {\n try {\n action();\n return true;\n } catch (error) {\n if (\n error instanceof TypeError ||\n Object.prototype.toString.call(error) === '[object TypeError]'\n ) {\n return false;\n }\n throw error;\n }\n}\n\n/**\n * Safely terminates a TransformStream controller, ignoring the TypeError\n * that occurs if the stream has already been terminated.\n *\n * After controller.terminate() is called, upstream may still push chunks\n * before the backpressure signal propagates, causing transform() to be\n * invoked again. A second call to controller.terminate() throws TypeError,\n * which this function suppresses.\n *\n * @param controller - The TransformStream controller to terminate\n * @returns true if termination succeeded, false if the stream was already closed\n */\nexport function safeTerminate<T>(\n controller: TransformStreamDefaultController<T>,\n): boolean {\n return suppressTypeError(() => controller.terminate());\n}\n\n/**\n * Safely enqueues a chunk to a stream controller, ignoring the TypeError\n * that occurs if the stream has already been closed or errored.\n *\n * After a stream is terminated or errored, upstream may still push chunks\n * before the signal propagates. Calling controller.enqueue() on a closed\n * stream throws TypeError, which this function suppresses.\n *\n * @param controller - The stream controller to enqueue to\n * @param chunk - The chunk to enqueue\n * @returns true if the chunk was enqueued, false if the stream was already closed\n */\nexport function safeEnqueue<T>(\n controller: StreamController<T>,\n chunk: T,\n): boolean {\n return suppressTypeError(() => controller.enqueue(chunk));\n}\n\n/**\n * Safely errors a stream controller, ignoring the TypeError\n * that occurs if the stream has already been closed or errored.\n *\n * After a stream is terminated or errored, subsequent error signals may\n * arrive before the backpressure propagates. Calling controller.error()\n * on an already-closed stream throws TypeError, which this function\n * suppresses. Non-TypeError exceptions are re-thrown.\n *\n * @param controller - The stream controller to error\n * @param reason - The error reason to pass to the controller\n * @returns true if the error was set, false if the stream was already closed\n */\nexport function safeError<T>(\n controller: StreamController<T>,\n reason: any,\n): boolean {\n return suppressTypeError(() => controller.error(reason));\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { safeEnqueue, safeError, safeTerminate } from './streamController';\n\n/**\n * Identifies the lifecycle phase where an error occurred.\n */\nexport type TransformerPhase = 'transform' | 'flush';\n\n/**\n * Abstract base class for TransformStream transformers with built-in error safety\n * and termination guard.\n *\n * Provides three guarantees that every concrete transformer inherits:\n *\n * 1. **Termination guard** — Once `terminated` is set (via `terminate()` or an\n * unhandled error), all subsequent chunks are silently dropped in `transform()`.\n *\n * 2. **Safe controller operations** — `enqueue()` delegates to\n * `safeEnqueue` which suppresses TypeError from already-closed streams.\n * `terminate()` delegates to `safeTerminate`.\n *\n * 3. **Error boundary** — Unhandled errors in `onTransform()` / `onFlush()`\n * are caught, the transformer is terminated, and the error is forwarded\n * via `safeError()`.\n *\n * Subclasses implement `onTransform()` and optionally `onFlush()` instead of\n * the raw `transform()` / `flush()` methods.\n *\n * @template I - The type of input chunks\n * @template O - The type of output chunks\n */\nexport abstract class SafeTransformer<I, O> implements Transformer<I, O> {\n /**\n * Guard flag indicating the stream has been terminated or errored.\n * Once set, subsequent chunks are silently dropped in `transform()`.\n */\n\n protected terminated = false;\n\n /**\n * Transforms an input chunk. Drops immediately if terminated.\n * Delegates to `onTransform()` with error protection.\n * Supports both synchronous and asynchronous `onTransform()` implementations.\n */\n async transform(\n chunk: I,\n controller: TransformStreamDefaultController<O>,\n ): Promise<void> {\n if (this.terminated) {\n return;\n }\n\n try {\n await this.onTransform(chunk, controller);\n } catch (error) {\n this.terminated = true;\n this.safeOnError(error, 'transform');\n safeError(controller, error);\n }\n }\n\n /**\n * Called when the stream ends. Always invokes `onFlush()` for cleanup,\n * even if already terminated. Errors in `onFlush()` are caught and\n * forwarded via `safeError()`.\n * Supports both synchronous and asynchronous `onFlush()` implementations.\n */\n async flush(controller: TransformStreamDefaultController<O>): Promise<void> {\n try {\n await this.onFlush(controller);\n } catch (error) {\n this.terminated = true;\n this.safeOnError(error, 'flush');\n safeError(controller, error);\n } finally {\n this.terminated = true;\n }\n }\n\n /**\n * Safely invokes `onError()`, catching any exception to prevent\n * cleanup logic from breaking the error boundary guarantee.\n */\n private safeOnError(error: unknown, phase: TransformerPhase): void {\n try {\n this.onError(error, phase);\n } catch {\n // onError must not break the error boundary\n }\n }\n\n /**\n * Marks the transformer as terminated and safely terminates the controller.\n * After calling this, all subsequent chunks are silently dropped.\n */\n protected terminate(controller: TransformStreamDefaultController<O>): boolean {\n this.terminated = true;\n return safeTerminate(controller);\n }\n\n /**\n * Safely enqueues a chunk to the controller.\n * Suppresses TypeError if the stream is already closed.\n */\n protected enqueue(\n controller: TransformStreamDefaultController<O>,\n chunk: O,\n ): boolean {\n return safeEnqueue(controller, chunk);\n }\n\n /**\n * Called when an error occurs during `onTransform()` or `onFlush()`.\n * Subclasses can override to clean up internal state (e.g. reset buffers).\n * The stream is already marked as terminated when this is called.\n *\n * @param error - The error that was caught\n * @param phase - The lifecycle phase where the error occurred\n */\n protected onError(error: unknown, phase: TransformerPhase): void {\n }\n\n /**\n * Transform an input chunk into output chunk(s).\n * Use `this.enqueue(controller, chunk)` instead of `controller.enqueue()`.\n * May return a Promise for asynchronous processing.\n *\n * @param chunk - The input chunk to transform\n * @param controller - The stream controller (use `this.enqueue()` for output)\n */\n protected abstract onTransform(\n chunk: I,\n controller: TransformStreamDefaultController<O>,\n ): void | Promise<void>;\n\n /**\n * Called when the stream is ending. Override to flush remaining state.\n * May return a Promise for asynchronous processing.\n * Default implementation does nothing.\n *\n * @param controller\n */\n protected onFlush(\n controller: TransformStreamDefaultController<O>,\n ): void | Promise<void> {\n // Default: nothing to flush\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SafeTransformer } from './safeTransformer';\n\n/**\n * Transformer that splits text into lines.\n *\n * Accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. Handles partial lines\n * that span multiple input chunks by maintaining an internal buffer.\n */\nexport class TextLineTransformer extends SafeTransformer<string, string> {\n private buffer = '';\n\n protected onTransform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ): void {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n this.enqueue(controller, line);\n }\n }\n\n protected onFlush(\n controller: TransformStreamDefaultController<string>,\n ): void {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n this.enqueue(controller, this.buffer);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * @example\n * ```typescript\n * const lineStream = new TextLineTransformStream();\n * const lines = textStream.pipeThrough(lineStream);\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SafeTransformer, type TransformerPhase } from './safeTransformer';\n\n/**\n * Represents a message sent in an event stream.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\n/**\n * Constants for Server-Sent Event field names.\n */\nexport class ServerSentEventFields {\n static readonly ID = 'id';\n static readonly RETRY = 'retry';\n static readonly EVENT = 'event';\n static readonly DATA = 'data';\n}\n\nfunction processFieldInternal(\n field: string,\n value: string,\n currentEvent: EventState,\n) {\n switch (field) {\n case ServerSentEventFields.EVENT:\n currentEvent.event = value;\n break;\n case ServerSentEventFields.DATA:\n currentEvent.data.push(value);\n break;\n case ServerSentEventFields.ID:\n // Per W3C SSE spec: \"If the field value does not contain U+0000 NULL,\n // then set the last event ID buffer to the field value.\n // Otherwise, ignore the field.\"\n if (!value.includes('\\0')) {\n currentEvent.id = value;\n }\n break;\n case ServerSentEventFields.RETRY: {\n // Per W3C SSE spec: \"If the field value consists of only ASCII digits,\n // then interpret the field value as an integer in base ten.\"\n if (/^\\d+$/.test(value)) {\n currentEvent.retry = parseInt(value, 10);\n }\n break;\n }\n default:\n break;\n }\n}\n\ninterface EventState {\n event?: string;\n id?: string;\n retry?: number;\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a\n * ServerSentEvent object stream, following the W3C SSE specification.\n */\nexport class ServerSentEventTransformer extends SafeTransformer<\n string,\n ServerSentEvent\n> {\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n private resetEventState() {\n this.currentEventState.event = DEFAULT_EVENT_TYPE;\n this.currentEventState.id = undefined;\n this.currentEventState.retry = undefined;\n this.currentEventState.data = [];\n }\n\n protected override onError(_error: unknown, _phase: TransformerPhase): void {\n this.resetEventState();\n }\n\n protected onTransform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ): void {\n const currentEvent = this.currentEventState;\n\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n if (currentEvent.data.length > 0) {\n this.enqueue(controller, {\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n\n currentEvent.event = DEFAULT_EVENT_TYPE;\n currentEvent.data = [];\n }\n return;\n }\n\n // Ignore comment lines (starting with colon)\n if (chunk.startsWith(':')) {\n return;\n }\n\n // Parse fields\n const colonIndex = chunk.indexOf(':');\n let field: string;\n let value: string;\n\n if (colonIndex === -1) {\n field = chunk.toLowerCase();\n value = '';\n } else {\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n\n processFieldInternal(field, value, currentEvent);\n }\n\n protected onFlush(\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ): void {\n const currentEvent = this.currentEventState;\n try {\n if (currentEvent.data.length > 0) {\n this.enqueue(controller, {\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n }\n } finally {\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of\n * ServerSentEvent objects.\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n type ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n *\n * This type represents a stream that yields Server-Sent Event objects as they are parsed\n * from a raw event stream. Each chunk in the stream contains a complete SSE event with\n * its metadata (event type, ID, retry interval) and data.\n *\n * @see {@link ServerSentEvent} for the structure of individual events\n * @see {@link toServerSentEventStream} for converting HTTP responses to this type\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n *\n * This error is thrown when there are issues converting an HTTP Response to a ServerSentEventStream.\n * It extends FetcherError to provide additional context about the failed conversion, including\n * the original Response object and any underlying cause.\n *\n * @extends {FetcherError}\n *\n * @example\n * ```typescript\n * try {\n * const eventStream = toServerSentEventStream(response);\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Failed to convert response to event stream:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n *\n * @param response - The Response object associated with the error, providing context\n * about the failed conversion (status, headers, etc.)\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this conversion error\n */\n constructor(\n public readonly response: Response,\n errorMsg?: string,\n cause?: Error | any,\n ) {\n super(errorMsg, cause);\n this.name = 'EventStreamConvertError';\n // Restore prototype chain for proper inheritance\n Object.setPrototypeOf(this, EventStreamConvertError.prototype);\n }\n}\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * This function takes an HTTP Response object and converts its body into a stream of\n * Server-Sent Event objects. The conversion process involves several transformation steps:\n *\n * 1. **TextDecoderStream**: Decodes the raw Uint8Array response body to UTF-8 strings\n * 2. **TextLineTransformStream**: Splits the text stream into individual lines\n * 3. **ServerSentEventTransformStream**: Parses the line-based SSE format into structured events\n *\n * The resulting stream can be consumed using async iteration or other stream methods.\n *\n * @param response - The HTTP Response object to convert. Must have a readable body stream.\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {EventStreamConvertError} If the response body is null or cannot be processed\n *\n * @example\n * ```typescript\n * // Convert an SSE response to an event stream\n * const response = await fetch('/api/events');\n * const eventStream = toServerSentEventStream(response);\n *\n * // Consume events asynchronously\n * for await (const event of eventStream) {\n * console.log(`Event: ${event.event}, Data: ${event.data}`);\n *\n * // Handle different event types\n * switch (event.event) {\n * case 'message':\n * handleMessage(event.data);\n * break;\n * case 'error':\n * handleError(event.data);\n * break;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Handle conversion errors\n * try {\n * const eventStream = toServerSentEventStream(response);\n * // Use the stream...\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Event stream conversion failed:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new EventStreamConvertError(response, 'Response body is null');\n }\n\n return response.body\n .pipeThrough(new TextDecoderStream('utf-8'))\n .pipeThrough(new TextLineTransformStream())\n .pipeThrough(new ServerSentEventTransformStream());\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type ServerSentEvent } from './serverSentEventTransformStream';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport { SafeTransformer } from './safeTransformer';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\n *\n * @template DATA - The expected type of the parsed JSON data\n */\nexport interface JsonServerSentEvent<DATA> extends Omit<\n ServerSentEvent,\n 'data'\n> {\n /** The parsed JSON data from the event */\n data: DATA;\n}\n\n/**\n * A TransformStream transformer that converts ServerSentEvent to\n * JsonServerSentEvent with optional termination detection.\n *\n * Inherits termination guard and safe controller operations from SafeTransformer.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransform<DATA> extends SafeTransformer<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n constructor(private readonly terminateDetector?: TerminateDetector) {\n super();\n }\n\n protected onTransform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ): void {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n this.terminate(controller);\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n this.enqueue(controller, {\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to\n * JsonServerSentEvent streams with optional termination detection.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform<DATA>(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\n/**\n * Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform\n * @param terminateDetector - Optional function to detect when the stream should be terminated\n * @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data\n */\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n terminateDetector?: TerminateDetector,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(terminateDetector),\n );\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { FetchExchange, ResultExtractor } from '@ahoo-wang/fetcher';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport type { JsonServerSentEventStream } from './jsonServerSentEventTransformStream';\n\n/**\n * ServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a ServerSentEventStream from an HTTP response that contains Server-Sent Events.\n * The extractor validates that the response supports event streaming and converts the\n * response body into a properly typed event stream.\n *\n * This extractor should be used when you want to consume raw Server-Sent Events\n * without JSON parsing, maintaining the original string data format.\n *\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {ExchangeError} When the server response does not support ServerSentEventStream\n * (e.g., wrong content type, no response body)\n *\n *\n * @see {@link ServerSentEventStream} for the stream type\n * @see {@link JsonEventStreamResultExtractor} for JSON-parsed event streams\n */\nexport const EventStreamResultExtractor: ResultExtractor<\n ServerSentEventStream\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredEventStream();\n};\n\n/**\n * JsonServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a JsonServerSentEventStream from an HTTP response that contains Server-Sent Events\n * with JSON data. The extractor validates that the response supports event streaming and converts\n * the response body into a properly typed event stream with automatic JSON parsing.\n *\n * This extractor should be used when you want to consume Server-Sent Events where the event\n * data is JSON-formatted, providing type-safe access to parsed JSON objects instead of raw strings.\n *\n * @template DATA - The expected type of the JSON data in the server-sent events\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects with parsed JSON data as they are received\n * @throws {ExchangeError} When the server response does not support JsonServerSentEventStream\n * (e.g., wrong content type, no response body, invalid JSON)\n *\n *\n * @see {@link JsonServerSentEventStream} for the stream type with JSON data\n * @see {@link EventStreamResultExtractor} for raw string event streams\n */\nexport const JsonEventStreamResultExtractor: ResultExtractor<\n JsonServerSentEventStream<any>\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredJsonEventStream();\n};\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EventStreamConvertError,\n type ServerSentEventStream,\n toServerSentEventStream,\n} from './eventStreamConverter';\nimport {\n type JsonServerSentEventStream,\n type TerminateDetector,\n toJsonServerSentEventStream,\n} from './jsonServerSentEventTransformStream';\nimport { CONTENT_TYPE_HEADER, ContentTypeValues } from '@ahoo-wang/fetcher';\n\ndeclare global {\n interface Response {\n /**\n * Gets the content type of the response.\n *\n * This property provides access to the Content-Type header of the response,\n * which indicates the media type of the resource transmitted in the response.\n *\n * @returns The content type header value as a string, or null if the header is not set\n */\n get contentType(): string | null;\n\n /**\n * Checks if the response is an event stream.\n *\n * This property examines the Content-Type header to determine if the response\n * contains server-sent events data (text/event-stream).\n *\n * @returns true if the response is an event stream, false otherwise\n */\n get isEventStream(): boolean;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects, or null if not an event stream\n */\n eventStream(): ServerSentEventStream | null;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is similar to eventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws {Error} if the event stream is not available\n */\n requiredEventStream(): ServerSentEventStream;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data, or null if not an event stream\n */\n jsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA> | null;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is similar to jsonEventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream with JSON data.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data\n * @throws {Error} if the event stream is not available\n */\n requiredJsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA>;\n }\n}\n\nif (typeof Response !== 'undefined') {\n const CONTENT_TYPE_PROPERTY_NAME = 'contentType';\n /**\n * Defines the contentType property on Response prototype.\n * This property provides a convenient way to access the Content-Type header value.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n CONTENT_TYPE_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, CONTENT_TYPE_PROPERTY_NAME, {\n get() {\n return this.headers.get(CONTENT_TYPE_HEADER);\n },\n configurable: true,\n });\n }\n\n const IS_EVENT_STREAM_PROPERTY_NAME = 'isEventStream';\n /**\n * Defines the isEventStream property on Response prototype.\n * This property checks if the response has a Content-Type header indicating it's an event stream.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n IS_EVENT_STREAM_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, IS_EVENT_STREAM_PROPERTY_NAME, {\n get() {\n const contentType = this.contentType;\n if (!contentType) {\n return false;\n }\n return contentType.includes(ContentTypeValues.TEXT_EVENT_STREAM);\n },\n configurable: true,\n });\n }\n\n /**\n * Implementation of the eventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream.\n *\n * @returns A ServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'eventStream')\n ) {\n Response.prototype.eventStream = function () {\n if (!this.isEventStream) {\n return null;\n }\n return toServerSentEventStream(this);\n };\n }\n\n /**\n * Implementation of the requiredEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @returns A ServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredEventStream',\n )\n ) {\n Response.prototype.requiredEventStream = function () {\n const eventStream = this.eventStream();\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n\n /**\n * Implementation of the jsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'jsonEventStream')\n ) {\n Response.prototype.jsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.eventStream();\n if (!eventStream) {\n return null;\n }\n return toJsonServerSentEventStream<DATA>(eventStream, terminateDetector);\n };\n }\n\n /**\n * Implementation of the requiredJsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredJsonEventStream',\n )\n ) {\n Response.prototype.requiredJsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.jsonEventStream<DATA>(terminateDetector);\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper class that converts a ReadableStream into an AsyncIterable.\n *\n * This class enables the use of ReadableStream objects with async iteration syntax\n * (for-await...of loops), providing a more ergonomic way to consume streaming data.\n * It implements the AsyncIterable interface and manages the underlying stream reader,\n * handling proper resource cleanup and error propagation.\n *\n * The wrapper automatically handles stream locking, ensuring that only one consumer\n * can read from the stream at a time, and provides safe cleanup when iteration ends\n * or errors occur.\n *\n * @template T - The type of data yielded by the stream\n *\n * @example\n * ```typescript\n * // Direct usage\n * const response = await fetch('/api/stream');\n * const stream = response.body;\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * console.log('Received:', chunk);\n * }\n * // Stream is automatically cleaned up after iteration\n * ```\n *\n * @example\n * ```typescript\n * // With early termination\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * if (someCondition) {\n * asyncIterable.releaseLock(); // Manually release if needed\n * break;\n * }\n * }\n * ```\n */\nexport class ReadableStreamAsyncIterable<T> implements AsyncIterable<T> {\n private readonly reader: ReadableStreamDefaultReader<T>;\n private _locked: boolean = true;\n\n /**\n * Creates a new ReadableStreamAsyncIterable instance.\n * @param stream - The ReadableStream to wrap.\n */\n constructor(private readonly stream: ReadableStream<T>) {\n this.reader = stream.getReader();\n }\n\n /**\n * Gets the lock status of the reader.\n * @returns True if the reader is currently locked, false otherwise.\n */\n get locked(): boolean {\n return this._locked;\n }\n\n /**\n * Releases the reader lock if currently locked.\n * This method safely releases the reader lock by catching any potential errors.\n */\n releaseLock() {\n if (!this._locked) return false;\n this._locked = false;\n try {\n this.reader.releaseLock();\n return true;\n } catch (error) {\n console.debug('Failed to release reader lock:', error);\n return false;\n }\n }\n\n /**\n * Implements the AsyncIterable interface by returning this iterator.\n * @returns The async iterator for this instance.\n */\n [Symbol.asyncIterator]() {\n return this;\n }\n\n /**\n * Gets the next value from the stream.\n * Reads the next chunk from the stream and returns it as an IteratorResult.\n * If the stream is done, releases the lock and returns a done result.\n * @returns A promise that resolves to an IteratorResult containing the next value or done status.\n * @throws If an error occurs while reading from the stream.\n */\n async next(): Promise<IteratorResult<T>> {\n try {\n const { done, value } = await this.reader.read();\n if (done) {\n this.releaseLock();\n return { done: true, value: undefined };\n }\n\n return { done: false, value };\n } catch (error) {\n this.releaseLock();\n throw error;\n }\n }\n\n /**\n * Implements the return method of the async iterator.\n * Cancels the stream reader and releases the lock.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async return(): Promise<IteratorResult<T>> {\n try {\n await this.reader.cancel();\n } catch (error) {\n console.debug('Failed to cancel stream reader:', error);\n } finally {\n this.releaseLock();\n }\n return { done: true, value: undefined };\n }\n\n /**\n * Implements the throw method of the async iterator.\n * Releases the lock and returns a done result.\n * @param error - The error to be thrown.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async throw(error: any): Promise<IteratorResult<T>> {\n // Ensure the reader lock is released before throwing\n console.debug('Throwing error:', error);\n this.releaseLock();\n return { done: true, value: undefined };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReadableStreamAsyncIterable } from './readableStreamAsyncIterable';\n\n/**\n * Checks if the current environment natively supports async iteration on ReadableStream.\n *\n * This constant determines whether the browser or runtime already provides\n * built-in support for using ReadableStream with for-await...of loops.\n * If not supported, this library will polyfill the functionality by adding\n * the [Symbol.asyncIterator] method to ReadableStream.prototype.\n *\n * @returns true if native async iteration is supported, false if polyfill is needed\n *\n * @example\n * ```typescript\n * import { isReadableStreamAsyncIterableSupported } from '@ahoo-wang/fetcher-eventstream';\n *\n * if (isReadableStreamAsyncIterableSupported) {\n * console.log('Native support available');\n * } else {\n * console.log('Using polyfill');\n * }\n * ```\n */\nexport const isReadableStreamAsyncIterableSupported =\n typeof ReadableStream !== 'undefined' &&\n typeof ReadableStream.prototype[Symbol.asyncIterator] === 'function';\n\n// Add [Symbol.asyncIterator] to ReadableStream if not already implemented\nif (!isReadableStreamAsyncIterableSupported) {\n ReadableStream.prototype[Symbol.asyncIterator] = function <R = any>() {\n return new ReadableStreamAsyncIterable<R>(this as ReadableStream<R>);\n };\n}\n"],"mappings":"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"}

@@ -1,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`});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});
(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.terminated=!1}async transform(e,t){if(!this.terminated)try{await this.onTransform(e,t)}catch(e){this.terminated=!0,this.safeOnError(e,`transform`),a(t,e)}}async flush(e){try{await this.onFlush(e)}catch(t){this.terminated=!0,this.safeOnError(t,`flush`),a(e,t)}finally{this.terminated=!0}}safeOnError(e,t){try{this.onError(e,t)}catch{}}terminate(e){return this.terminated=!0,r(e)}enqueue(e,t){return i(e,t)}onError(e,t){}onFlush(e){}},s=class extends o{constructor(...e){super(...e),this.buffer=``}onTransform(e,t){this.buffer+=e;let n=this.buffer.split(`
`);this.buffer=n.pop()||``;for(let e of n)this.enqueue(t,e)}onFlush(e){this.buffer&&this.enqueue(e,this.buffer)}},c=class extends TransformStream{constructor(){super(new s)}},l=class{static{this.ID=`id`}static{this.RETRY=`retry`}static{this.EVENT=`event`}static{this.DATA=`data`}};function u(e,t,n){switch(e){case l.EVENT:n.event=t;break;case l.DATA:n.data.push(t);break;case l.ID:t.includes(`\0`)||(n.id=t);break;case l.RETRY:/^\d+$/.test(t)&&(n.retry=parseInt(t,10));break;default:break}}var d=`message`,f=class extends o{constructor(...e){super(...e),this.currentEventState={event:d,id:void 0,retry:void 0,data:[]}}resetEventState(){this.currentEventState.event=d,this.currentEventState.id=void 0,this.currentEventState.retry=void 0,this.currentEventState.data=[]}onError(e,t){this.resetEventState()}onTransform(e,t){let n=this.currentEventState;if(e.trim()===``){n.data.length>0&&(this.enqueue(t,{event:n.event||d,data:n.data.join(`
`),id:n.id||``,retry:n.retry}),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))),u(i,a,n)}onFlush(e){let t=this.currentEventState;try{t.data.length>0&&this.enqueue(e,{event:t.event||d,data:t.data.join(`
`),id:t.id||``,retry:t.retry})}finally{this.resetEventState()}}},p=class extends TransformStream{constructor(){super(new f)}},m=class e extends t.FetcherError{constructor(t,n,r){super(n,r),this.response=t,this.name=`EventStreamConvertError`,Object.setPrototypeOf(this,e.prototype)}};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)}var g=class extends o{constructor(e){super(),this.terminateDetector=e}onTransform(e,t){if(this.terminateDetector?.(e)){this.terminate(t);return}let n=JSON.parse(e.data);this.enqueue(t,{data:n,event:e.event,id:e.id,retry:e.retry})}},_=class extends TransformStream{constructor(e){super(new g(e))}};function v(e,t){return e.pipeThrough(new _(t))}var y=e=>e.requiredResponse.requiredEventStream(),b=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?h(this):null}),Object.prototype.hasOwnProperty.call(Response.prototype,`requiredEventStream`)||(Response.prototype.requiredEventStream=function(){let e=this.eventStream();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?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 m(this,`Event stream is not available. Response content-type: [${this.contentType}]`);return t})}var x=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}}},S=typeof ReadableStream<`u`&&typeof ReadableStream.prototype[Symbol.asyncIterator]==`function`;S||(ReadableStream.prototype[Symbol.asyncIterator]=function(){return new x(this)}),e.EventStreamConvertError=m,e.EventStreamResultExtractor=y,e.JsonEventStreamResultExtractor=b,e.JsonServerSentEventTransform=g,e.JsonServerSentEventTransformStream=_,e.ReadableStreamAsyncIterable=x,e.SafeTransformer=o,e.ServerSentEventFields=l,e.ServerSentEventTransformStream=p,e.ServerSentEventTransformer=f,e.TextLineTransformStream=c,e.TextLineTransformer=s,e.isReadableStreamAsyncIterableSupported=S,e.safeEnqueue=i,e.safeError=a,e.safeTerminate=r,e.toJsonServerSentEventStream=v,e.toServerSentEventStream=h});
//# sourceMappingURL=index.umd.js.map

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

{"version":3,"file":"index.umd.js","names":[],"sources":["../src/streamController.ts","../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/eventStreamResultExtractor.ts","../src/responses.ts","../src/readableStreamAsyncIterable.ts","../src/readableStreams.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Union type representing a stream controller that supports safe operations.\n *\n * Both ReadableStreamDefaultController and TransformStreamDefaultController\n * share the `enqueue()` and `error()` methods. TransformStreamDefaultController\n * additionally provides `terminate()`. This union type allows the safe\n * utility functions to work with either controller kind.\n *\n * @template T - The type of chunks the controller handles\n */\nexport type StreamController<T> =\n | ReadableStreamDefaultController<T>\n | TransformStreamDefaultController<T>;\n\n/**\n * Executes an action and suppresses TypeError, returning a boolean indicating success.\n *\n * This is the shared implementation for all safe* controller functions.\n * Stream controller methods (terminate, enqueue, error) throw TypeError when\n * the stream is already closed or errored. This helper catches that TypeError\n * and returns false, while re-throwing any non-TypeError exceptions.\n *\n * Uses both `instanceof TypeError` and `Object.prototype.toString` checks\n * to handle cross-realm TypeErrors (e.g. from iframes or worker threads)\n * where `instanceof` alone would fail. The toString check matches values\n * with the internal TypeError class tag, which covers cross-realm TypeErrors\n * while rejecting plain objects that merely have a `name` property.\n *\n * @param action - The controller operation to attempt\n * @returns true if the action succeeded, false if a TypeError was caught\n */\nfunction suppressTypeError(action: () => void): boolean {\n try {\n action();\n return true;\n } catch (error) {\n if (\n error instanceof TypeError ||\n Object.prototype.toString.call(error) === '[object TypeError]'\n ) {\n return false;\n }\n throw error;\n }\n}\n\n/**\n * Safely terminates a TransformStream controller, ignoring the TypeError\n * that occurs if the stream has already been terminated.\n *\n * After controller.terminate() is called, upstream may still push chunks\n * before the backpressure signal propagates, causing transform() to be\n * invoked again. A second call to controller.terminate() throws TypeError,\n * which this function suppresses.\n *\n * @param controller - The TransformStream controller to terminate\n * @returns true if termination succeeded, false if the stream was already closed\n */\nexport function safeTerminate<T>(\n controller: TransformStreamDefaultController<T>,\n): boolean {\n return suppressTypeError(() => controller.terminate());\n}\n\n/**\n * Safely enqueues a chunk to a stream controller, ignoring the TypeError\n * that occurs if the stream has already been closed or errored.\n *\n * After a stream is terminated or errored, upstream may still push chunks\n * before the signal propagates. Calling controller.enqueue() on a closed\n * stream throws TypeError, which this function suppresses.\n *\n * @param controller - The stream controller to enqueue to\n * @param chunk - The chunk to enqueue\n * @returns true if the chunk was enqueued, false if the stream was already closed\n */\nexport function safeEnqueue<T>(\n controller: StreamController<T>,\n chunk: T,\n): boolean {\n return suppressTypeError(() => controller.enqueue(chunk));\n}\n\n/**\n * Safely errors a stream controller, ignoring the TypeError\n * that occurs if the stream has already been closed or errored.\n *\n * After a stream is terminated or errored, subsequent error signals may\n * arrive before the backpressure propagates. Calling controller.error()\n * on an already-closed stream throws TypeError, which this function\n * suppresses. Non-TypeError exceptions are re-thrown.\n *\n * @param controller - The stream controller to error\n * @param reason - The error reason to pass to the controller\n * @returns true if the error was set, false if the stream was already closed\n */\nexport function safeError<T>(\n controller: StreamController<T>,\n reason: any,\n): boolean {\n return suppressTypeError(() => controller.error(reason));\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Transformer that splits text into lines.\n *\n * This transformer accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. It handles partial lines that span multiple\n * input chunks by maintaining an internal buffer. Lines are emitted without the newline character.\n *\n * The transformer handles various edge cases:\n * - Lines that span multiple input chunks\n * - Empty lines (emitted as empty strings)\n * - Text without trailing newlines (buffered until stream ends)\n * - Mixed line endings (only '\\n' is recognized as line separator)\n *\n * @implements {Transformer<string, string>}\n *\n * @example\n * ```typescript\n * const transformer = new TextLineTransformer();\n * // Input: \"line1\\nline2\\npartial\"\n * // Output: [\"line1\", \"line2\"]\n * // Buffer: \"partial\"\n *\n * // Later input: \"line\\n\"\n * // Output: [\"partialline\"]\n * // Buffer: \"\"\n * ```\n */\nimport { safeEnqueue, safeError } from './streamController';\n\nexport class TextLineTransformer implements Transformer<string, string> {\n private buffer = '';\n\n /**\n * Transform input string chunk by splitting it into lines.\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ) {\n try {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n safeEnqueue(controller, line);\n }\n } catch (error) {\n safeError(controller, error);\n }\n }\n\n /**\n * Flush remaining buffer when the stream ends.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<string>) {\n try {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n safeEnqueue(controller, this.buffer);\n }\n } catch (error) {\n safeError(controller, error);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * This class provides a convenient way to transform a stream of text chunks into a stream\n * of individual lines. It wraps the TextLineTransformer in a TransformStream for easy\n * integration with other stream processing pipelines.\n *\n * The stream processes text data and emits each line as a separate chunk, handling\n * lines that may span multiple input chunks automatically.\n *\n * @example\n * ```typescript\n * // Create a line-splitting stream\n * const lineStream = new TextLineTransformStream();\n *\n * // Pipe text through it\n * const lines = textStream.pipeThrough(lineStream);\n *\n * // Process each line\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Process SSE response line by line\n * const response = await fetch('/api/stream');\n * const lines = response.body!\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(new TextLineTransformStream());\n *\n * for await (const line of lines) {\n * if (line.startsWith('data: ')) {\n * console.log('SSE data:', line.substring(6));\n * }\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n /**\n * Creates a new TextLineTransformStream instance.\n *\n * Initializes the stream with a TextLineTransformer that handles the line splitting logic.\n */\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * This interface defines the structure of Server-Sent Events (SSE) as specified by the W3C.\n * Each event contains metadata and data that can be processed by clients to handle real-time\n * updates from the server.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\n/**\n * Constants for Server-Sent Event field names.\n *\n * This class provides string constants for the standard SSE field names as defined\n * in the W3C Server-Sent Events specification. These constants help ensure\n * consistent field name usage throughout the parsing logic.\n */\nimport { safeEnqueue, safeError } from './streamController';\n\nexport class ServerSentEventFields {\n /** The field name for event ID */\n static readonly ID = 'id';\n /** The field name for retry interval */\n static readonly RETRY = 'retry';\n /** The field name for event type */\n static readonly EVENT = 'event';\n /** The field name for event data */\n static readonly DATA = 'data';\n}\n\n/**\n * Processes a field-value pair and updates the current event state accordingly.\n *\n * This internal function handles the parsing of individual SSE fields according to the\n * Server-Sent Events specification. It updates the provided event state object with\n * the parsed field values.\n *\n * @param field - The field name (e.g., 'event', 'data', 'id', 'retry')\n * @param value - The field value as a string\n * @param currentEvent - The current event state object to update\n *\n * @example\n * ```typescript\n * const eventState: EventState = { event: 'message', data: [] };\n * processFieldInternal('event', 'custom', eventState);\n * // eventState.event is now 'custom'\n * ```\n */\nfunction processFieldInternal(\n field: string,\n value: string,\n currentEvent: EventState,\n) {\n switch (field) {\n case ServerSentEventFields.EVENT:\n currentEvent.event = value;\n break;\n case ServerSentEventFields.DATA:\n currentEvent.data.push(value);\n break;\n case ServerSentEventFields.ID:\n currentEvent.id = value;\n break;\n case ServerSentEventFields.RETRY: {\n const retryValue = parseInt(value, 10);\n if (!isNaN(retryValue)) {\n currentEvent.retry = retryValue;\n }\n break;\n }\n default:\n // Ignore unknown fields\n break;\n }\n}\n\n/**\n * Internal state representation during Server-Sent Event parsing.\n *\n * This interface tracks the current state of an event being parsed from the SSE stream.\n * It accumulates field values until a complete event is ready to be emitted.\n */\ninterface EventState {\n /** The event type (defaults to 'message') */\n event?: string;\n /** The event ID */\n id?: string;\n /** The retry interval in milliseconds */\n retry?: number;\n /** Array of data lines that will be joined with newlines */\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n * This transformer handles the parsing of Server-Sent Events (SSE) according to the W3C specification.\n * It processes incoming text chunks and converts them into structured ServerSentEvent objects.\n */\nexport class ServerSentEventTransformer implements Transformer<\n string,\n ServerSentEvent\n> {\n // Initialize currentEventState with default values in a closure\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Reset the current event state to default values.\n * This method is called after processing each complete event or when an error occurs.\n */\n private resetEventState() {\n this.currentEventState.event = DEFAULT_EVENT_TYPE;\n this.currentEventState.id = undefined;\n this.currentEventState.retry = undefined;\n this.currentEventState.data = [];\n }\n\n /**\n * Transform input string chunk into ServerSentEvent object.\n * This method processes individual chunks of text data, parsing them according to the SSE format.\n * It handles:\n * - Empty lines (used as event separators)\n * - Comment lines (starting with ':')\n * - Field lines (field: value format)\n * - Event completion and emission\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ) {\n const currentEvent = this.currentEventState;\n try {\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n // If there is accumulated event data, send event\n if (currentEvent.data.length > 0) {\n safeEnqueue(controller, {\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n\n // Reset current event (preserve id and retry for subsequent events)\n currentEvent.event = DEFAULT_EVENT_TYPE;\n // Preserve id and retry for subsequent events (no need to reassign to themselves)\n currentEvent.data = [];\n }\n return;\n }\n\n // Ignore comment lines (starting with colon)\n if (chunk.startsWith(':')) {\n return;\n }\n\n // Parse fields\n const colonIndex = chunk.indexOf(':');\n let field: string;\n let value: string;\n\n if (colonIndex === -1) {\n // No colon, entire line as field name, value is empty\n field = chunk.toLowerCase();\n value = '';\n } else {\n // Extract field name and value\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n\n // If value starts with space, remove leading space\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n // Remove trailing newlines from field and value\n field = field.trim();\n value = value.trim();\n\n processFieldInternal(field, value, currentEvent);\n } catch (error) {\n const enhancedError = new Error(\n `Failed to process chunk: \"${chunk}\". ${error instanceof Error ? error.message : String(error)}`,\n );\n safeError(controller, enhancedError);\n // Reset state\n this.resetEventState();\n }\n }\n\n /**\n * Called when the stream ends, used to process remaining data.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<ServerSentEvent>) {\n const currentEvent = this.currentEventState;\n try {\n // Send the last event (if any)\n if (currentEvent.data.length > 0) {\n safeEnqueue(controller, {\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n }\n } catch (error) {\n const enhancedError = new Error(\n `Failed to flush remaining data. ${error instanceof Error ? error.message : String(error)}`,\n );\n safeError(controller, enhancedError);\n } finally {\n // Reset state\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n *\n * This class provides a convenient way to transform raw text streams containing Server-Sent Events\n * into structured event objects. It wraps the ServerSentEventTransformer in a TransformStream\n * for easy integration with other stream processing pipelines.\n *\n * The stream processes SSE format text and emits ServerSentEvent objects as they are completed.\n * Events are separated by empty lines, and the stream handles partial events across multiple chunks.\n *\n * @example\n * ```typescript\n * // Create a transform stream\n * const sseStream = new ServerSentEventTransformStream();\n *\n * // Pipe a text stream through it\n * const eventStream = textStream.pipeThrough(sseStream);\n *\n * // Consume the events\n * for await (const event of eventStream) {\n * console.log('Event:', event.event, 'Data:', event.data);\n * }\n * ```\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n /**\n * Creates a new ServerSentEventTransformStream instance.\n *\n * Initializes the stream with a ServerSentEventTransformer that handles\n * the parsing of SSE format text into structured events.\n */\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n type ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n *\n * This type represents a stream that yields Server-Sent Event objects as they are parsed\n * from a raw event stream. Each chunk in the stream contains a complete SSE event with\n * its metadata (event type, ID, retry interval) and data.\n *\n * @see {@link ServerSentEvent} for the structure of individual events\n * @see {@link toServerSentEventStream} for converting HTTP responses to this type\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n *\n * This error is thrown when there are issues converting an HTTP Response to a ServerSentEventStream.\n * It extends FetcherError to provide additional context about the failed conversion, including\n * the original Response object and any underlying cause.\n *\n * @extends {FetcherError}\n *\n * @example\n * ```typescript\n * try {\n * const eventStream = toServerSentEventStream(response);\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Failed to convert response to event stream:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n *\n * @param response - The Response object associated with the error, providing context\n * about the failed conversion (status, headers, etc.)\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this conversion error\n */\n constructor(\n public readonly response: Response,\n errorMsg?: string,\n cause?: Error | any,\n ) {\n super(errorMsg, cause);\n this.name = 'EventStreamConvertError';\n // Restore prototype chain for proper inheritance\n Object.setPrototypeOf(this, EventStreamConvertError.prototype);\n }\n}\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * This function takes an HTTP Response object and converts its body into a stream of\n * Server-Sent Event objects. The conversion process involves several transformation steps:\n *\n * 1. **TextDecoderStream**: Decodes the raw Uint8Array response body to UTF-8 strings\n * 2. **TextLineTransformStream**: Splits the text stream into individual lines\n * 3. **ServerSentEventTransformStream**: Parses the line-based SSE format into structured events\n *\n * The resulting stream can be consumed using async iteration or other stream methods.\n *\n * @param response - The HTTP Response object to convert. Must have a readable body stream.\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {EventStreamConvertError} If the response body is null or cannot be processed\n *\n * @example\n * ```typescript\n * // Convert an SSE response to an event stream\n * const response = await fetch('/api/events');\n * const eventStream = toServerSentEventStream(response);\n *\n * // Consume events asynchronously\n * for await (const event of eventStream) {\n * console.log(`Event: ${event.event}, Data: ${event.data}`);\n *\n * // Handle different event types\n * switch (event.event) {\n * case 'message':\n * handleMessage(event.data);\n * break;\n * case 'error':\n * handleError(event.data);\n * break;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Handle conversion errors\n * try {\n * const eventStream = toServerSentEventStream(response);\n * // Use the stream...\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Event stream conversion failed:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new EventStreamConvertError(response, 'Response body is null');\n }\n\n return response.body\n .pipeThrough(new TextDecoderStream('utf-8'))\n .pipeThrough(new TextLineTransformStream())\n .pipeThrough(new ServerSentEventTransformStream());\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type ServerSentEvent } from './serverSentEventTransformStream';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport { safeEnqueue, safeError, safeTerminate } from './streamController';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * This detector function is called for each incoming ServerSentEvent. If it returns true,\n * the stream transformation will be terminated, preventing further events from being processed.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n *\n * @example\n * ```typescript\n * const terminateOnDone: TerminateDetector = (event) => {\n * return event.event === 'done' || event.data === '[DONE]';\n * };\n * ```\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\n *\n * This interface extends the base ServerSentEvent but replaces the string 'data' field\n * with a parsed JSON object of the specified generic type. This allows for type-safe\n * access to the event payload.\n *\n * @template DATA - The expected type of the parsed JSON data\n */\nexport interface JsonServerSentEvent<DATA> extends Omit<\n ServerSentEvent,\n 'data'\n> {\n /** The parsed JSON data from the event */\n data: DATA;\n}\n\n/**\n * A TransformStream transformer that converts ServerSentEvent to JsonServerSentEvent with optional termination detection.\n *\n * This transformer parses the JSON data from ServerSentEvent chunks and optionally terminates\n * the stream when a termination condition is met. It's designed to work within a TransformStream\n * to convert raw server-sent events into typed JSON events.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransform<DATA> implements Transformer<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Guard flag to prevent any controller operations after the stream has been\n * terminated or errored. Once set, all subsequent chunks are silently dropped.\n */\n private terminated = false;\n\n /**\n * Creates a new JsonServerSentEventTransform instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * If provided, this function is called for each event and can terminate\n * the stream by returning true.\n */\n constructor(private readonly terminateDetector?: TerminateDetector) {}\n\n /**\n * Transforms a ServerSentEvent chunk into a JsonServerSentEvent.\n *\n * This method first checks if the stream has already been terminated. If so,\n * the chunk is silently dropped. Otherwise, it checks if the event should\n * terminate the stream using the terminateDetector. If termination is required,\n * the controller is safely terminated. Otherwise, the event data is parsed\n * as JSON and enqueued as a JsonServerSentEvent.\n *\n * All controller operations use safe wrappers (safeTerminate, safeEnqueue,\n * safeError) that suppress TypeError from already-closed streams as normal\n * control flow. Any error thrown during detection or parsing (including\n * TypeError from detector/parse logic) is caught, the stream is errored\n * via safeError, and subsequent chunks are dropped.\n *\n * @param chunk - The ServerSentEvent to transform\n * @param controller - The TransformStream controller for managing the stream\n */\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ) {\n if (this.terminated) {\n return;\n }\n\n try {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n this.terminated = true;\n safeTerminate(controller);\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n safeEnqueue(controller, {\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n } catch (error) {\n this.terminated = true;\n safeError(controller, error);\n }\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to JsonServerSentEvent streams with optional termination detection.\n *\n * This class extends TransformStream to provide a convenient way to transform streams of ServerSentEvent\n * objects into streams of JsonServerSentEvent objects. It supports optional termination detection to\n * automatically end the stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransformStream instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * When provided, the stream will automatically terminate when this\n * function returns true for any event.\n *\n * @example\n * ```typescript\n * // Create a stream that terminates on 'done' events\n * const terminateOnDone: TerminateDetector = (event) => event.event === 'done';\n * const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);\n *\n * // Create a stream without termination detection\n * const basicStream = new JsonServerSentEventTransformStream<MyData>();\n * ```\n */\n constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\n *\n * This type represents a stream that yields parsed JSON server-sent events.\n * Each chunk in the stream contains the event metadata along with parsed JSON data.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\n/**\n * Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.\n *\n * This function takes a stream of raw server-sent events and transforms it into a stream of\n * parsed JSON events. It optionally accepts a termination detector to automatically end the\n * stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform\n * @param terminateDetector - Optional function to detect when the stream should be terminated\n * @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data\n * @throws {SyntaxError} If any event data is not valid JSON (thrown during stream consumption)\n *\n * @example\n * ```typescript\n * // Basic usage without termination detection\n * const jsonStream = toJsonServerSentEventStream<MyData>(serverSentEventStream);\n *\n * // With termination detection\n * const terminateOnDone: TerminateDetector = (event) => event.data === '[DONE]';\n * const terminatingStream = toJsonServerSentEventStream<MyData>(\n * serverSentEventStream,\n * terminateOnDone\n * );\n *\n * // Consume the stream\n * for await (const event of jsonStream) {\n * console.log('Received:', event.data);\n * console.log('Event type:', event.event);\n * }\n * ```\n */\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n terminateDetector?: TerminateDetector,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(terminateDetector),\n );\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { FetchExchange, ResultExtractor } from '@ahoo-wang/fetcher';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport type { JsonServerSentEventStream } from './jsonServerSentEventTransformStream';\n\n/**\n * ServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a ServerSentEventStream from an HTTP response that contains Server-Sent Events.\n * The extractor validates that the response supports event streaming and converts the\n * response body into a properly typed event stream.\n *\n * This extractor should be used when you want to consume raw Server-Sent Events\n * without JSON parsing, maintaining the original string data format.\n *\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {ExchangeError} When the server response does not support ServerSentEventStream\n * (e.g., wrong content type, no response body)\n *\n *\n * @see {@link ServerSentEventStream} for the stream type\n * @see {@link JsonEventStreamResultExtractor} for JSON-parsed event streams\n */\nexport const EventStreamResultExtractor: ResultExtractor<\n ServerSentEventStream\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredEventStream();\n};\n\n/**\n * JsonServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a JsonServerSentEventStream from an HTTP response that contains Server-Sent Events\n * with JSON data. The extractor validates that the response supports event streaming and converts\n * the response body into a properly typed event stream with automatic JSON parsing.\n *\n * This extractor should be used when you want to consume Server-Sent Events where the event\n * data is JSON-formatted, providing type-safe access to parsed JSON objects instead of raw strings.\n *\n * @template DATA - The expected type of the JSON data in the server-sent events\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects with parsed JSON data as they are received\n * @throws {ExchangeError} When the server response does not support JsonServerSentEventStream\n * (e.g., wrong content type, no response body, invalid JSON)\n *\n *\n * @see {@link JsonServerSentEventStream} for the stream type with JSON data\n * @see {@link EventStreamResultExtractor} for raw string event streams\n */\nexport const JsonEventStreamResultExtractor: ResultExtractor<\n JsonServerSentEventStream<any>\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredJsonEventStream();\n};\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EventStreamConvertError,\n type ServerSentEventStream,\n toServerSentEventStream,\n} from './eventStreamConverter';\nimport {\n type JsonServerSentEventStream,\n type TerminateDetector,\n toJsonServerSentEventStream,\n} from './jsonServerSentEventTransformStream';\nimport { CONTENT_TYPE_HEADER, ContentTypeValues } from '@ahoo-wang/fetcher';\n\ndeclare global {\n interface Response {\n /**\n * Gets the content type of the response.\n *\n * This property provides access to the Content-Type header of the response,\n * which indicates the media type of the resource transmitted in the response.\n *\n * @returns The content type header value as a string, or null if the header is not set\n */\n get contentType(): string | null;\n\n /**\n * Checks if the response is an event stream.\n *\n * This property examines the Content-Type header to determine if the response\n * contains server-sent events data (text/event-stream).\n *\n * @returns true if the response is an event stream, false otherwise\n */\n get isEventStream(): boolean;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects, or null if not an event stream\n */\n eventStream(): ServerSentEventStream | null;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is similar to eventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws {Error} if the event stream is not available\n */\n requiredEventStream(): ServerSentEventStream;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data, or null if not an event stream\n */\n jsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA> | null;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is similar to jsonEventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream with JSON data.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data\n * @throws {Error} if the event stream is not available\n */\n requiredJsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA>;\n }\n}\n\nif (typeof Response !== 'undefined') {\n const CONTENT_TYPE_PROPERTY_NAME = 'contentType';\n /**\n * Defines the contentType property on Response prototype.\n * This property provides a convenient way to access the Content-Type header value.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n CONTENT_TYPE_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, CONTENT_TYPE_PROPERTY_NAME, {\n get() {\n return this.headers.get(CONTENT_TYPE_HEADER);\n },\n configurable: true,\n });\n }\n\n const IS_EVENT_STREAM_PROPERTY_NAME = 'isEventStream';\n /**\n * Defines the isEventStream property on Response prototype.\n * This property checks if the response has a Content-Type header indicating it's an event stream.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n IS_EVENT_STREAM_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, IS_EVENT_STREAM_PROPERTY_NAME, {\n get() {\n const contentType = this.contentType;\n if (!contentType) {\n return false;\n }\n return contentType.includes(ContentTypeValues.TEXT_EVENT_STREAM);\n },\n configurable: true,\n });\n }\n\n /**\n * Implementation of the eventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream.\n *\n * @returns A ServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'eventStream')\n ) {\n Response.prototype.eventStream = function () {\n if (!this.isEventStream) {\n return null;\n }\n return toServerSentEventStream(this);\n };\n }\n\n /**\n * Implementation of the requiredEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @returns A ServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredEventStream',\n )\n ) {\n Response.prototype.requiredEventStream = function () {\n const eventStream = this.eventStream();\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n\n /**\n * Implementation of the jsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'jsonEventStream')\n ) {\n Response.prototype.jsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.eventStream();\n if (!eventStream) {\n return null;\n }\n return toJsonServerSentEventStream<DATA>(eventStream, terminateDetector);\n };\n }\n\n /**\n * Implementation of the requiredJsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredJsonEventStream',\n )\n ) {\n Response.prototype.requiredJsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.jsonEventStream<DATA>(terminateDetector);\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper class that converts a ReadableStream into an AsyncIterable.\n *\n * This class enables the use of ReadableStream objects with async iteration syntax\n * (for-await...of loops), providing a more ergonomic way to consume streaming data.\n * It implements the AsyncIterable interface and manages the underlying stream reader,\n * handling proper resource cleanup and error propagation.\n *\n * The wrapper automatically handles stream locking, ensuring that only one consumer\n * can read from the stream at a time, and provides safe cleanup when iteration ends\n * or errors occur.\n *\n * @template T - The type of data yielded by the stream\n *\n * @example\n * ```typescript\n * // Direct usage\n * const response = await fetch('/api/stream');\n * const stream = response.body;\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * console.log('Received:', chunk);\n * }\n * // Stream is automatically cleaned up after iteration\n * ```\n *\n * @example\n * ```typescript\n * // With early termination\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * if (someCondition) {\n * asyncIterable.releaseLock(); // Manually release if needed\n * break;\n * }\n * }\n * ```\n */\nexport class ReadableStreamAsyncIterable<T> implements AsyncIterable<T> {\n private readonly reader: ReadableStreamDefaultReader<T>;\n private _locked: boolean = true;\n\n /**\n * Creates a new ReadableStreamAsyncIterable instance.\n * @param stream - The ReadableStream to wrap.\n */\n constructor(private readonly stream: ReadableStream<T>) {\n this.reader = stream.getReader();\n }\n\n /**\n * Gets the lock status of the reader.\n * @returns True if the reader is currently locked, false otherwise.\n */\n get locked(): boolean {\n return this._locked;\n }\n\n /**\n * Releases the reader lock if currently locked.\n * This method safely releases the reader lock by catching any potential errors.\n */\n releaseLock() {\n if (!this._locked) return false;\n this._locked = false;\n try {\n this.reader.releaseLock();\n return true;\n } catch (error) {\n console.debug('Failed to release reader lock:', error);\n return false;\n }\n }\n\n /**\n * Implements the AsyncIterable interface by returning this iterator.\n * @returns The async iterator for this instance.\n */\n [Symbol.asyncIterator]() {\n return this;\n }\n\n /**\n * Gets the next value from the stream.\n * Reads the next chunk from the stream and returns it as an IteratorResult.\n * If the stream is done, releases the lock and returns a done result.\n * @returns A promise that resolves to an IteratorResult containing the next value or done status.\n * @throws If an error occurs while reading from the stream.\n */\n async next(): Promise<IteratorResult<T>> {\n try {\n const { done, value } = await this.reader.read();\n if (done) {\n this.releaseLock();\n return { done: true, value: undefined };\n }\n\n return { done: false, value };\n } catch (error) {\n this.releaseLock();\n throw error;\n }\n }\n\n /**\n * Implements the return method of the async iterator.\n * Cancels the stream reader and releases the lock.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async return(): Promise<IteratorResult<T>> {\n try {\n await this.reader.cancel();\n } catch (error) {\n console.debug('Failed to cancel stream reader:', error);\n } finally {\n this.releaseLock();\n }\n return { done: true, value: undefined };\n }\n\n /**\n * Implements the throw method of the async iterator.\n * Releases the lock and returns a done result.\n * @param error - The error to be thrown.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async throw(error: any): Promise<IteratorResult<T>> {\n // Ensure the reader lock is released before throwing\n console.debug('Throwing error:', error);\n this.releaseLock();\n return { done: true, value: undefined };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReadableStreamAsyncIterable } from './readableStreamAsyncIterable';\n\n/**\n * Checks if the current environment natively supports async iteration on ReadableStream.\n *\n * This constant determines whether the browser or runtime already provides\n * built-in support for using ReadableStream with for-await...of loops.\n * If not supported, this library will polyfill the functionality by adding\n * the [Symbol.asyncIterator] method to ReadableStream.prototype.\n *\n * @returns true if native async iteration is supported, false if polyfill is needed\n *\n * @example\n * ```typescript\n * import { isReadableStreamAsyncIterableSupported } from '@ahoo-wang/fetcher-eventstream';\n *\n * if (isReadableStreamAsyncIterableSupported) {\n * console.log('Native support available');\n * } else {\n * console.log('Using polyfill');\n * }\n * ```\n */\nexport const isReadableStreamAsyncIterableSupported =\n typeof ReadableStream !== 'undefined' &&\n typeof ReadableStream.prototype[Symbol.asyncIterator] === 'function';\n\n// Add [Symbol.asyncIterator] to ReadableStream if not already implemented\nif (!isReadableStreamAsyncIterableSupported) {\n ReadableStream.prototype[Symbol.asyncIterator] = function <R = any>() {\n return new ReadableStreamAsyncIterable<R>(this as ReadableStream<R>);\n };\n}\n"],"mappings":"yVA4CA,SAAS,EAAkB,EAA6B,CACtD,GAAI,CAEF,OADA,GAAQ,CACD,SACA,EAAO,CACd,GACE,aAAiB,WACjB,OAAO,UAAU,SAAS,KAAK,EAAM,GAAK,qBAE1C,MAAO,GAET,MAAM,GAgBV,SAAgB,EACd,EACS,CACT,OAAO,MAAwB,EAAW,WAAW,CAAC,CAexD,SAAgB,EACd,EACA,EACS,CACT,OAAO,MAAwB,EAAW,QAAQ,EAAM,CAAC,CAgB3D,SAAgB,EACd,EACA,EACS,CACT,OAAO,MAAwB,EAAW,MAAM,EAAO,CAAC,CCvE1D,IAAa,EAAb,KAAwE,2BACrD,GAQjB,UACE,EACA,EACA,CACA,GAAI,CACF,KAAK,QAAU,EACf,IAAM,EAAQ,KAAK,OAAO,MAAM;EAAK,CACrC,KAAK,OAAS,EAAM,KAAK,EAAI,GAE7B,IAAK,IAAM,KAAQ,EACjB,EAAY,EAAY,EAAK,OAExB,EAAO,CACd,EAAU,EAAY,EAAM,EAShC,MAAM,EAAsD,CAC1D,GAAI,CAEE,KAAK,QACP,EAAY,EAAY,KAAK,OAAO,OAE/B,EAAO,CACd,EAAU,EAAY,EAAM,IA4CrB,EAAb,cAA6C,eAAgC,CAM3E,aAAc,CACZ,MAAM,IAAI,EAAsB,GCzFvB,EAAb,KAAmC,gBAEZ,uBAEG,0BAEA,yBAED,SAqBzB,SAAS,EACP,EACA,EACA,EACA,CACA,OAAQ,EAAR,CACE,KAAK,EAAsB,MACzB,EAAa,MAAQ,EACrB,MACF,KAAK,EAAsB,KACzB,EAAa,KAAK,KAAK,EAAM,CAC7B,MACF,KAAK,EAAsB,GACzB,EAAa,GAAK,EAClB,MACF,KAAK,EAAsB,MAAO,CAChC,IAAM,EAAa,SAAS,EAAO,GAAG,CACjC,MAAM,EAAW,GACpB,EAAa,MAAQ,GAEvB,MAEF,QAEE,OAqBN,IAAM,EAAqB,UASd,EAAb,KAGE,sCAEwC,CACtC,MAAO,EACP,GAAI,IAAA,GACJ,MAAO,IAAA,GACP,KAAM,EAAE,CACT,CAMD,iBAA0B,CACxB,KAAK,kBAAkB,MAAQ,EAC/B,KAAK,kBAAkB,GAAK,IAAA,GAC5B,KAAK,kBAAkB,MAAQ,IAAA,GAC/B,KAAK,kBAAkB,KAAO,EAAE,CAelC,UACE,EACA,EACA,CACA,IAAM,EAAe,KAAK,kBAC1B,GAAI,CAEF,GAAI,EAAM,MAAM,GAAK,GAAI,CAEnB,EAAa,KAAK,OAAS,IAC7B,EAAY,EAAY,CACtB,MAAO,EAAa,OAAS,EAC7B,KAAM,EAAa,KAAK,KAAK;EAAK,CAClC,GAAI,EAAa,IAAM,GACvB,MAAO,EAAa,MACrB,CAAoB,CAGrB,EAAa,MAAQ,EAErB,EAAa,KAAO,EAAE,EAExB,OAIF,GAAI,EAAM,WAAW,IAAI,CACvB,OAIF,IAAM,EAAa,EAAM,QAAQ,IAAI,CACjC,EACA,EAEA,IAAe,IAEjB,EAAQ,EAAM,aAAa,CAC3B,EAAQ,KAGR,EAAQ,EAAM,UAAU,EAAG,EAAW,CAAC,aAAa,CACpD,EAAQ,EAAM,UAAU,EAAa,EAAE,CAGnC,EAAM,WAAW,IAAI,GACvB,EAAQ,EAAM,UAAU,EAAE,GAK9B,EAAQ,EAAM,MAAM,CACpB,EAAQ,EAAM,MAAM,CAEpB,EAAqB,EAAO,EAAO,EAAa,OACzC,EAAO,CAId,EAAU,EAHgB,MACxB,6BAA6B,EAAM,KAAK,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,GAC/F,CACmC,CAEpC,KAAK,iBAAiB,EAS1B,MAAM,EAA+D,CACnE,IAAM,EAAe,KAAK,kBAC1B,GAAI,CAEE,EAAa,KAAK,OAAS,GAC7B,EAAY,EAAY,CACtB,MAAO,EAAa,OAAS,EAC7B,KAAM,EAAa,KAAK,KAAK;EAAK,CAClC,GAAI,EAAa,IAAM,GACvB,MAAO,EAAa,MACrB,CAAoB,OAEhB,EAAO,CAId,EAAU,EAHgB,MACxB,mCAAmC,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,GAC1F,CACmC,QAC5B,CAER,KAAK,iBAAiB,IA6Bf,EAAb,cAAoD,eAGlD,CAOA,aAAc,CACZ,MAAM,IAAI,EAA6B,GC5O9B,EAAb,MAAa,UAAgC,EAAA,YAAa,CASxD,YACE,EACA,EACA,EACA,CACA,MAAM,EAAU,EAAM,CAJN,KAAA,SAAA,EAKhB,KAAK,KAAO,0BAEZ,OAAO,eAAe,KAAM,EAAwB,UAAU,GAwDlE,SAAgB,EACd,EACuB,CACvB,GAAI,CAAC,EAAS,KACZ,MAAM,IAAI,EAAwB,EAAU,wBAAwB,CAGtE,OAAO,EAAS,KACb,YAAY,IAAI,kBAAkB,QAAQ,CAAC,CAC3C,YAAY,IAAI,EAA0B,CAC1C,YAAY,IAAI,EAAiC,CC3EtD,IAAa,EAAb,KAGE,CAcA,YAAY,EAAwD,CAAvC,KAAA,kBAAA,kBATR,GA6BrB,UACE,EACA,EACA,CACI,SAAK,WAIT,GAAI,CAEF,GAAI,KAAK,oBAAoB,EAAM,CAAE,CACnC,KAAK,WAAa,GAClB,EAAc,EAAW,CACzB,OAIF,EAAY,EAAY,CACtB,KAFW,KAAK,MAAM,EAAM,KAAK,CAGjC,MAAO,EAAM,MACb,GAAI,EAAM,GACV,MAAO,EAAM,MACd,CAAC,OACK,EAAO,CACd,KAAK,WAAa,GAClB,EAAU,EAAY,EAAM,IAcrB,EAAb,cAA8D,eAG5D,CAkBA,YAAY,EAAuC,CACjD,MAAM,IAAI,EAA6B,EAAkB,CAAC,GAgD9D,SAAgB,EACd,EACA,EACiC,CACjC,OAAO,EAAsB,YAC3B,IAAI,EAAyC,EAAkB,CAChE,CChLH,IAAa,EAER,GACI,EAAS,iBAAiB,qBAAqB,CAwB3C,EAER,GACI,EAAS,iBAAiB,yBAAyB,CCkC5D,GAAI,OAAO,SAAa,IAAa,CACnC,IAAM,EAA6B,cAMhC,OAAO,UAAU,eAAe,KAC/B,SAAS,UACT,EACD,EAED,OAAO,eAAe,SAAS,UAAW,EAA4B,CACpE,KAAM,CACJ,OAAO,KAAK,QAAQ,IAAI,EAAA,oBAAoB,EAE9C,aAAc,GACf,CAAC,CAGJ,IAAM,EAAgC,gBAMnC,OAAO,UAAU,eAAe,KAC/B,SAAS,UACT,EACD,EAED,OAAO,eAAe,SAAS,UAAW,EAA+B,CACvE,KAAM,CACJ,IAAM,EAAc,KAAK,YAIzB,OAHK,EAGE,EAAY,SAAS,EAAA,kBAAkB,kBAAkB,CAFvD,IAIX,aAAc,GACf,CAAC,CAUD,OAAO,UAAU,eAAe,KAAK,SAAS,UAAW,cAAc,GAExE,SAAS,UAAU,YAAc,UAAY,CAI3C,OAHK,KAAK,cAGH,EAAwB,KAAK,CAF3B,OAeV,OAAO,UAAU,eAAe,KAC/B,SAAS,UACT,sBACD,GAED,SAAS,UAAU,oBAAsB,UAAY,CACnD,IAAM,EAAc,KAAK,aAAa,CACtC,GAAI,CAAC,EACH,MAAM,IAAI,EACR,KACA,0DAA0D,KAAK,YAAY,GAC5E,CAEH,OAAO,IAaR,OAAO,UAAU,eAAe,KAAK,SAAS,UAAW,kBAAkB,GAE5E,SAAS,UAAU,gBAAkB,SACnC,EACA,CACA,IAAM,EAAc,KAAK,aAAa,CAItC,OAHK,EAGE,EAAkC,EAAa,EAAkB,CAF/D,OAiBV,OAAO,UAAU,eAAe,KAC/B,SAAS,UACT,0BACD,GAED,SAAS,UAAU,wBAA0B,SAC3C,EACA,CACA,IAAM,EAAc,KAAK,gBAAsB,EAAkB,CACjE,GAAI,CAAC,EACH,MAAM,IAAI,EACR,KACA,0DAA0D,KAAK,YAAY,GAC5E,CAEH,OAAO,ICtLb,IAAa,EAAb,KAAwE,CAQtE,YAAY,EAA4C,CAA3B,KAAA,OAAA,eANF,GAOzB,KAAK,OAAS,EAAO,WAAW,CAOlC,IAAI,QAAkB,CACpB,OAAO,KAAK,QAOd,aAAc,CACZ,GAAI,CAAC,KAAK,QAAS,MAAO,GAC1B,KAAK,QAAU,GACf,GAAI,CAEF,OADA,KAAK,OAAO,aAAa,CAClB,SACA,EAAO,CAEd,OADA,QAAQ,MAAM,iCAAkC,EAAM,CAC/C,IAQX,CAAC,OAAO,gBAAiB,CACvB,OAAO,KAUT,MAAM,MAAmC,CACvC,GAAI,CACF,GAAM,CAAE,OAAM,SAAU,MAAM,KAAK,OAAO,MAAM,CAMhD,OALI,GACF,KAAK,aAAa,CACX,CAAE,KAAM,GAAM,MAAO,IAAA,GAAW,EAGlC,CAAE,KAAM,GAAO,QAAO,OACtB,EAAO,CAEd,MADA,KAAK,aAAa,CACZ,GASV,MAAM,QAAqC,CACzC,GAAI,CACF,MAAM,KAAK,OAAO,QAAQ,OACnB,EAAO,CACd,QAAQ,MAAM,kCAAmC,EAAM,QAC/C,CACR,KAAK,aAAa,CAEpB,MAAO,CAAE,KAAM,GAAM,MAAO,IAAA,GAAW,CASzC,MAAM,MAAM,EAAwC,CAIlD,OAFA,QAAQ,MAAM,kBAAmB,EAAM,CACvC,KAAK,aAAa,CACX,CAAE,KAAM,GAAM,MAAO,IAAA,GAAW,GC7G9B,EACX,OAAO,eAAmB,KAC1B,OAAO,eAAe,UAAU,OAAO,gBAAmB,WAGvD,IACH,eAAe,UAAU,OAAO,eAAiB,UAAqB,CACpE,OAAO,IAAI,EAA+B,KAA0B"}
{"version":3,"file":"index.umd.js","names":[],"sources":["../src/streamController.ts","../src/safeTransformer.ts","../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/eventStreamResultExtractor.ts","../src/responses.ts","../src/readableStreamAsyncIterable.ts","../src/readableStreams.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Union type representing a stream controller that supports safe operations.\n *\n * Both ReadableStreamDefaultController and TransformStreamDefaultController\n * share the `enqueue()` and `error()` methods. TransformStreamDefaultController\n * additionally provides `terminate()`. This union type allows the safe\n * utility functions to work with either controller kind.\n *\n * @template T - The type of chunks the controller handles\n */\nexport type StreamController<T> =\n | ReadableStreamDefaultController<T>\n | TransformStreamDefaultController<T>;\n\n/**\n * Executes an action and suppresses TypeError, returning a boolean indicating success.\n *\n * This is the shared implementation for all safe* controller functions.\n * Stream controller methods (terminate, enqueue, error) throw TypeError when\n * the stream is already closed or errored. This helper catches that TypeError\n * and returns false, while re-throwing any non-TypeError exceptions.\n *\n * Uses both `instanceof TypeError` and `Object.prototype.toString` checks\n * to handle cross-realm TypeErrors (e.g. from iframes or worker threads)\n * where `instanceof` alone would fail. The toString check matches values\n * with the internal TypeError class tag, which covers cross-realm TypeErrors\n * while rejecting plain objects that merely have a `name` property.\n *\n * @param action - The controller operation to attempt\n * @returns true if the action succeeded, false if a TypeError was caught\n */\nfunction suppressTypeError(action: () => void): boolean {\n try {\n action();\n return true;\n } catch (error) {\n if (\n error instanceof TypeError ||\n Object.prototype.toString.call(error) === '[object TypeError]'\n ) {\n return false;\n }\n throw error;\n }\n}\n\n/**\n * Safely terminates a TransformStream controller, ignoring the TypeError\n * that occurs if the stream has already been terminated.\n *\n * After controller.terminate() is called, upstream may still push chunks\n * before the backpressure signal propagates, causing transform() to be\n * invoked again. A second call to controller.terminate() throws TypeError,\n * which this function suppresses.\n *\n * @param controller - The TransformStream controller to terminate\n * @returns true if termination succeeded, false if the stream was already closed\n */\nexport function safeTerminate<T>(\n controller: TransformStreamDefaultController<T>,\n): boolean {\n return suppressTypeError(() => controller.terminate());\n}\n\n/**\n * Safely enqueues a chunk to a stream controller, ignoring the TypeError\n * that occurs if the stream has already been closed or errored.\n *\n * After a stream is terminated or errored, upstream may still push chunks\n * before the signal propagates. Calling controller.enqueue() on a closed\n * stream throws TypeError, which this function suppresses.\n *\n * @param controller - The stream controller to enqueue to\n * @param chunk - The chunk to enqueue\n * @returns true if the chunk was enqueued, false if the stream was already closed\n */\nexport function safeEnqueue<T>(\n controller: StreamController<T>,\n chunk: T,\n): boolean {\n return suppressTypeError(() => controller.enqueue(chunk));\n}\n\n/**\n * Safely errors a stream controller, ignoring the TypeError\n * that occurs if the stream has already been closed or errored.\n *\n * After a stream is terminated or errored, subsequent error signals may\n * arrive before the backpressure propagates. Calling controller.error()\n * on an already-closed stream throws TypeError, which this function\n * suppresses. Non-TypeError exceptions are re-thrown.\n *\n * @param controller - The stream controller to error\n * @param reason - The error reason to pass to the controller\n * @returns true if the error was set, false if the stream was already closed\n */\nexport function safeError<T>(\n controller: StreamController<T>,\n reason: any,\n): boolean {\n return suppressTypeError(() => controller.error(reason));\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { safeEnqueue, safeError, safeTerminate } from './streamController';\n\n/**\n * Identifies the lifecycle phase where an error occurred.\n */\nexport type TransformerPhase = 'transform' | 'flush';\n\n/**\n * Abstract base class for TransformStream transformers with built-in error safety\n * and termination guard.\n *\n * Provides three guarantees that every concrete transformer inherits:\n *\n * 1. **Termination guard** — Once `terminated` is set (via `terminate()` or an\n * unhandled error), all subsequent chunks are silently dropped in `transform()`.\n *\n * 2. **Safe controller operations** — `enqueue()` delegates to\n * `safeEnqueue` which suppresses TypeError from already-closed streams.\n * `terminate()` delegates to `safeTerminate`.\n *\n * 3. **Error boundary** — Unhandled errors in `onTransform()` / `onFlush()`\n * are caught, the transformer is terminated, and the error is forwarded\n * via `safeError()`.\n *\n * Subclasses implement `onTransform()` and optionally `onFlush()` instead of\n * the raw `transform()` / `flush()` methods.\n *\n * @template I - The type of input chunks\n * @template O - The type of output chunks\n */\nexport abstract class SafeTransformer<I, O> implements Transformer<I, O> {\n /**\n * Guard flag indicating the stream has been terminated or errored.\n * Once set, subsequent chunks are silently dropped in `transform()`.\n */\n\n protected terminated = false;\n\n /**\n * Transforms an input chunk. Drops immediately if terminated.\n * Delegates to `onTransform()` with error protection.\n * Supports both synchronous and asynchronous `onTransform()` implementations.\n */\n async transform(\n chunk: I,\n controller: TransformStreamDefaultController<O>,\n ): Promise<void> {\n if (this.terminated) {\n return;\n }\n\n try {\n await this.onTransform(chunk, controller);\n } catch (error) {\n this.terminated = true;\n this.safeOnError(error, 'transform');\n safeError(controller, error);\n }\n }\n\n /**\n * Called when the stream ends. Always invokes `onFlush()` for cleanup,\n * even if already terminated. Errors in `onFlush()` are caught and\n * forwarded via `safeError()`.\n * Supports both synchronous and asynchronous `onFlush()` implementations.\n */\n async flush(controller: TransformStreamDefaultController<O>): Promise<void> {\n try {\n await this.onFlush(controller);\n } catch (error) {\n this.terminated = true;\n this.safeOnError(error, 'flush');\n safeError(controller, error);\n } finally {\n this.terminated = true;\n }\n }\n\n /**\n * Safely invokes `onError()`, catching any exception to prevent\n * cleanup logic from breaking the error boundary guarantee.\n */\n private safeOnError(error: unknown, phase: TransformerPhase): void {\n try {\n this.onError(error, phase);\n } catch {\n // onError must not break the error boundary\n }\n }\n\n /**\n * Marks the transformer as terminated and safely terminates the controller.\n * After calling this, all subsequent chunks are silently dropped.\n */\n protected terminate(controller: TransformStreamDefaultController<O>): boolean {\n this.terminated = true;\n return safeTerminate(controller);\n }\n\n /**\n * Safely enqueues a chunk to the controller.\n * Suppresses TypeError if the stream is already closed.\n */\n protected enqueue(\n controller: TransformStreamDefaultController<O>,\n chunk: O,\n ): boolean {\n return safeEnqueue(controller, chunk);\n }\n\n /**\n * Called when an error occurs during `onTransform()` or `onFlush()`.\n * Subclasses can override to clean up internal state (e.g. reset buffers).\n * The stream is already marked as terminated when this is called.\n *\n * @param error - The error that was caught\n * @param phase - The lifecycle phase where the error occurred\n */\n protected onError(error: unknown, phase: TransformerPhase): void {\n }\n\n /**\n * Transform an input chunk into output chunk(s).\n * Use `this.enqueue(controller, chunk)` instead of `controller.enqueue()`.\n * May return a Promise for asynchronous processing.\n *\n * @param chunk - The input chunk to transform\n * @param controller - The stream controller (use `this.enqueue()` for output)\n */\n protected abstract onTransform(\n chunk: I,\n controller: TransformStreamDefaultController<O>,\n ): void | Promise<void>;\n\n /**\n * Called when the stream is ending. Override to flush remaining state.\n * May return a Promise for asynchronous processing.\n * Default implementation does nothing.\n *\n * @param controller\n */\n protected onFlush(\n controller: TransformStreamDefaultController<O>,\n ): void | Promise<void> {\n // Default: nothing to flush\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SafeTransformer } from './safeTransformer';\n\n/**\n * Transformer that splits text into lines.\n *\n * Accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. Handles partial lines\n * that span multiple input chunks by maintaining an internal buffer.\n */\nexport class TextLineTransformer extends SafeTransformer<string, string> {\n private buffer = '';\n\n protected onTransform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ): void {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n this.enqueue(controller, line);\n }\n }\n\n protected onFlush(\n controller: TransformStreamDefaultController<string>,\n ): void {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n this.enqueue(controller, this.buffer);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * @example\n * ```typescript\n * const lineStream = new TextLineTransformStream();\n * const lines = textStream.pipeThrough(lineStream);\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SafeTransformer, type TransformerPhase } from './safeTransformer';\n\n/**\n * Represents a message sent in an event stream.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\n/**\n * Constants for Server-Sent Event field names.\n */\nexport class ServerSentEventFields {\n static readonly ID = 'id';\n static readonly RETRY = 'retry';\n static readonly EVENT = 'event';\n static readonly DATA = 'data';\n}\n\nfunction processFieldInternal(\n field: string,\n value: string,\n currentEvent: EventState,\n) {\n switch (field) {\n case ServerSentEventFields.EVENT:\n currentEvent.event = value;\n break;\n case ServerSentEventFields.DATA:\n currentEvent.data.push(value);\n break;\n case ServerSentEventFields.ID:\n // Per W3C SSE spec: \"If the field value does not contain U+0000 NULL,\n // then set the last event ID buffer to the field value.\n // Otherwise, ignore the field.\"\n if (!value.includes('\\0')) {\n currentEvent.id = value;\n }\n break;\n case ServerSentEventFields.RETRY: {\n // Per W3C SSE spec: \"If the field value consists of only ASCII digits,\n // then interpret the field value as an integer in base ten.\"\n if (/^\\d+$/.test(value)) {\n currentEvent.retry = parseInt(value, 10);\n }\n break;\n }\n default:\n break;\n }\n}\n\ninterface EventState {\n event?: string;\n id?: string;\n retry?: number;\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a\n * ServerSentEvent object stream, following the W3C SSE specification.\n */\nexport class ServerSentEventTransformer extends SafeTransformer<\n string,\n ServerSentEvent\n> {\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n private resetEventState() {\n this.currentEventState.event = DEFAULT_EVENT_TYPE;\n this.currentEventState.id = undefined;\n this.currentEventState.retry = undefined;\n this.currentEventState.data = [];\n }\n\n protected override onError(_error: unknown, _phase: TransformerPhase): void {\n this.resetEventState();\n }\n\n protected onTransform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ): void {\n const currentEvent = this.currentEventState;\n\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n if (currentEvent.data.length > 0) {\n this.enqueue(controller, {\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n\n currentEvent.event = DEFAULT_EVENT_TYPE;\n currentEvent.data = [];\n }\n return;\n }\n\n // Ignore comment lines (starting with colon)\n if (chunk.startsWith(':')) {\n return;\n }\n\n // Parse fields\n const colonIndex = chunk.indexOf(':');\n let field: string;\n let value: string;\n\n if (colonIndex === -1) {\n field = chunk.toLowerCase();\n value = '';\n } else {\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n\n processFieldInternal(field, value, currentEvent);\n }\n\n protected onFlush(\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ): void {\n const currentEvent = this.currentEventState;\n try {\n if (currentEvent.data.length > 0) {\n this.enqueue(controller, {\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n }\n } finally {\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of\n * ServerSentEvent objects.\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n type ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n *\n * This type represents a stream that yields Server-Sent Event objects as they are parsed\n * from a raw event stream. Each chunk in the stream contains a complete SSE event with\n * its metadata (event type, ID, retry interval) and data.\n *\n * @see {@link ServerSentEvent} for the structure of individual events\n * @see {@link toServerSentEventStream} for converting HTTP responses to this type\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n *\n * This error is thrown when there are issues converting an HTTP Response to a ServerSentEventStream.\n * It extends FetcherError to provide additional context about the failed conversion, including\n * the original Response object and any underlying cause.\n *\n * @extends {FetcherError}\n *\n * @example\n * ```typescript\n * try {\n * const eventStream = toServerSentEventStream(response);\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Failed to convert response to event stream:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n *\n * @param response - The Response object associated with the error, providing context\n * about the failed conversion (status, headers, etc.)\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this conversion error\n */\n constructor(\n public readonly response: Response,\n errorMsg?: string,\n cause?: Error | any,\n ) {\n super(errorMsg, cause);\n this.name = 'EventStreamConvertError';\n // Restore prototype chain for proper inheritance\n Object.setPrototypeOf(this, EventStreamConvertError.prototype);\n }\n}\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * This function takes an HTTP Response object and converts its body into a stream of\n * Server-Sent Event objects. The conversion process involves several transformation steps:\n *\n * 1. **TextDecoderStream**: Decodes the raw Uint8Array response body to UTF-8 strings\n * 2. **TextLineTransformStream**: Splits the text stream into individual lines\n * 3. **ServerSentEventTransformStream**: Parses the line-based SSE format into structured events\n *\n * The resulting stream can be consumed using async iteration or other stream methods.\n *\n * @param response - The HTTP Response object to convert. Must have a readable body stream.\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {EventStreamConvertError} If the response body is null or cannot be processed\n *\n * @example\n * ```typescript\n * // Convert an SSE response to an event stream\n * const response = await fetch('/api/events');\n * const eventStream = toServerSentEventStream(response);\n *\n * // Consume events asynchronously\n * for await (const event of eventStream) {\n * console.log(`Event: ${event.event}, Data: ${event.data}`);\n *\n * // Handle different event types\n * switch (event.event) {\n * case 'message':\n * handleMessage(event.data);\n * break;\n * case 'error':\n * handleError(event.data);\n * break;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Handle conversion errors\n * try {\n * const eventStream = toServerSentEventStream(response);\n * // Use the stream...\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Event stream conversion failed:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new EventStreamConvertError(response, 'Response body is null');\n }\n\n return response.body\n .pipeThrough(new TextDecoderStream('utf-8'))\n .pipeThrough(new TextLineTransformStream())\n .pipeThrough(new ServerSentEventTransformStream());\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type ServerSentEvent } from './serverSentEventTransformStream';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport { SafeTransformer } from './safeTransformer';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\n *\n * @template DATA - The expected type of the parsed JSON data\n */\nexport interface JsonServerSentEvent<DATA> extends Omit<\n ServerSentEvent,\n 'data'\n> {\n /** The parsed JSON data from the event */\n data: DATA;\n}\n\n/**\n * A TransformStream transformer that converts ServerSentEvent to\n * JsonServerSentEvent with optional termination detection.\n *\n * Inherits termination guard and safe controller operations from SafeTransformer.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransform<DATA> extends SafeTransformer<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n constructor(private readonly terminateDetector?: TerminateDetector) {\n super();\n }\n\n protected onTransform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ): void {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n this.terminate(controller);\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n this.enqueue(controller, {\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to\n * JsonServerSentEvent streams with optional termination detection.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform<DATA>(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\n/**\n * Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform\n * @param terminateDetector - Optional function to detect when the stream should be terminated\n * @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data\n */\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n terminateDetector?: TerminateDetector,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(terminateDetector),\n );\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { FetchExchange, ResultExtractor } from '@ahoo-wang/fetcher';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport type { JsonServerSentEventStream } from './jsonServerSentEventTransformStream';\n\n/**\n * ServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a ServerSentEventStream from an HTTP response that contains Server-Sent Events.\n * The extractor validates that the response supports event streaming and converts the\n * response body into a properly typed event stream.\n *\n * This extractor should be used when you want to consume raw Server-Sent Events\n * without JSON parsing, maintaining the original string data format.\n *\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {ExchangeError} When the server response does not support ServerSentEventStream\n * (e.g., wrong content type, no response body)\n *\n *\n * @see {@link ServerSentEventStream} for the stream type\n * @see {@link JsonEventStreamResultExtractor} for JSON-parsed event streams\n */\nexport const EventStreamResultExtractor: ResultExtractor<\n ServerSentEventStream\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredEventStream();\n};\n\n/**\n * JsonServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a JsonServerSentEventStream from an HTTP response that contains Server-Sent Events\n * with JSON data. The extractor validates that the response supports event streaming and converts\n * the response body into a properly typed event stream with automatic JSON parsing.\n *\n * This extractor should be used when you want to consume Server-Sent Events where the event\n * data is JSON-formatted, providing type-safe access to parsed JSON objects instead of raw strings.\n *\n * @template DATA - The expected type of the JSON data in the server-sent events\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects with parsed JSON data as they are received\n * @throws {ExchangeError} When the server response does not support JsonServerSentEventStream\n * (e.g., wrong content type, no response body, invalid JSON)\n *\n *\n * @see {@link JsonServerSentEventStream} for the stream type with JSON data\n * @see {@link EventStreamResultExtractor} for raw string event streams\n */\nexport const JsonEventStreamResultExtractor: ResultExtractor<\n JsonServerSentEventStream<any>\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredJsonEventStream();\n};\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EventStreamConvertError,\n type ServerSentEventStream,\n toServerSentEventStream,\n} from './eventStreamConverter';\nimport {\n type JsonServerSentEventStream,\n type TerminateDetector,\n toJsonServerSentEventStream,\n} from './jsonServerSentEventTransformStream';\nimport { CONTENT_TYPE_HEADER, ContentTypeValues } from '@ahoo-wang/fetcher';\n\ndeclare global {\n interface Response {\n /**\n * Gets the content type of the response.\n *\n * This property provides access to the Content-Type header of the response,\n * which indicates the media type of the resource transmitted in the response.\n *\n * @returns The content type header value as a string, or null if the header is not set\n */\n get contentType(): string | null;\n\n /**\n * Checks if the response is an event stream.\n *\n * This property examines the Content-Type header to determine if the response\n * contains server-sent events data (text/event-stream).\n *\n * @returns true if the response is an event stream, false otherwise\n */\n get isEventStream(): boolean;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects, or null if not an event stream\n */\n eventStream(): ServerSentEventStream | null;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is similar to eventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws {Error} if the event stream is not available\n */\n requiredEventStream(): ServerSentEventStream;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data, or null if not an event stream\n */\n jsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA> | null;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is similar to jsonEventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream with JSON data.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data\n * @throws {Error} if the event stream is not available\n */\n requiredJsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA>;\n }\n}\n\nif (typeof Response !== 'undefined') {\n const CONTENT_TYPE_PROPERTY_NAME = 'contentType';\n /**\n * Defines the contentType property on Response prototype.\n * This property provides a convenient way to access the Content-Type header value.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n CONTENT_TYPE_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, CONTENT_TYPE_PROPERTY_NAME, {\n get() {\n return this.headers.get(CONTENT_TYPE_HEADER);\n },\n configurable: true,\n });\n }\n\n const IS_EVENT_STREAM_PROPERTY_NAME = 'isEventStream';\n /**\n * Defines the isEventStream property on Response prototype.\n * This property checks if the response has a Content-Type header indicating it's an event stream.\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n IS_EVENT_STREAM_PROPERTY_NAME,\n )\n ) {\n Object.defineProperty(Response.prototype, IS_EVENT_STREAM_PROPERTY_NAME, {\n get() {\n const contentType = this.contentType;\n if (!contentType) {\n return false;\n }\n return contentType.includes(ContentTypeValues.TEXT_EVENT_STREAM);\n },\n configurable: true,\n });\n }\n\n /**\n * Implementation of the eventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream.\n *\n * @returns A ServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'eventStream')\n ) {\n Response.prototype.eventStream = function () {\n if (!this.isEventStream) {\n return null;\n }\n return toServerSentEventStream(this);\n };\n }\n\n /**\n * Implementation of the requiredEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a ServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @returns A ServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredEventStream',\n )\n ) {\n Response.prototype.requiredEventStream = function () {\n const eventStream = this.eventStream();\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n\n /**\n * Implementation of the jsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream, null otherwise\n */\n if (\n !Object.prototype.hasOwnProperty.call(Response.prototype, 'jsonEventStream')\n ) {\n Response.prototype.jsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.eventStream();\n if (!eventStream) {\n return null;\n }\n return toJsonServerSentEventStream<DATA>(eventStream, terminateDetector);\n };\n }\n\n /**\n * Implementation of the requiredJsonEventStream method for Response objects.\n * Converts a Response with text/event-stream content type to a JsonServerSentEventStream,\n * throwing an error if the response is not an event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A JsonServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\n if (\n !Object.prototype.hasOwnProperty.call(\n Response.prototype,\n 'requiredJsonEventStream',\n )\n ) {\n Response.prototype.requiredJsonEventStream = function <DATA>(\n terminateDetector?: TerminateDetector,\n ) {\n const eventStream = this.jsonEventStream<DATA>(terminateDetector);\n if (!eventStream) {\n throw new EventStreamConvertError(\n this,\n `Event stream is not available. Response content-type: [${this.contentType}]`,\n );\n }\n return eventStream;\n };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper class that converts a ReadableStream into an AsyncIterable.\n *\n * This class enables the use of ReadableStream objects with async iteration syntax\n * (for-await...of loops), providing a more ergonomic way to consume streaming data.\n * It implements the AsyncIterable interface and manages the underlying stream reader,\n * handling proper resource cleanup and error propagation.\n *\n * The wrapper automatically handles stream locking, ensuring that only one consumer\n * can read from the stream at a time, and provides safe cleanup when iteration ends\n * or errors occur.\n *\n * @template T - The type of data yielded by the stream\n *\n * @example\n * ```typescript\n * // Direct usage\n * const response = await fetch('/api/stream');\n * const stream = response.body;\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * console.log('Received:', chunk);\n * }\n * // Stream is automatically cleaned up after iteration\n * ```\n *\n * @example\n * ```typescript\n * // With early termination\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * if (someCondition) {\n * asyncIterable.releaseLock(); // Manually release if needed\n * break;\n * }\n * }\n * ```\n */\nexport class ReadableStreamAsyncIterable<T> implements AsyncIterable<T> {\n private readonly reader: ReadableStreamDefaultReader<T>;\n private _locked: boolean = true;\n\n /**\n * Creates a new ReadableStreamAsyncIterable instance.\n * @param stream - The ReadableStream to wrap.\n */\n constructor(private readonly stream: ReadableStream<T>) {\n this.reader = stream.getReader();\n }\n\n /**\n * Gets the lock status of the reader.\n * @returns True if the reader is currently locked, false otherwise.\n */\n get locked(): boolean {\n return this._locked;\n }\n\n /**\n * Releases the reader lock if currently locked.\n * This method safely releases the reader lock by catching any potential errors.\n */\n releaseLock() {\n if (!this._locked) return false;\n this._locked = false;\n try {\n this.reader.releaseLock();\n return true;\n } catch (error) {\n console.debug('Failed to release reader lock:', error);\n return false;\n }\n }\n\n /**\n * Implements the AsyncIterable interface by returning this iterator.\n * @returns The async iterator for this instance.\n */\n [Symbol.asyncIterator]() {\n return this;\n }\n\n /**\n * Gets the next value from the stream.\n * Reads the next chunk from the stream and returns it as an IteratorResult.\n * If the stream is done, releases the lock and returns a done result.\n * @returns A promise that resolves to an IteratorResult containing the next value or done status.\n * @throws If an error occurs while reading from the stream.\n */\n async next(): Promise<IteratorResult<T>> {\n try {\n const { done, value } = await this.reader.read();\n if (done) {\n this.releaseLock();\n return { done: true, value: undefined };\n }\n\n return { done: false, value };\n } catch (error) {\n this.releaseLock();\n throw error;\n }\n }\n\n /**\n * Implements the return method of the async iterator.\n * Cancels the stream reader and releases the lock.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async return(): Promise<IteratorResult<T>> {\n try {\n await this.reader.cancel();\n } catch (error) {\n console.debug('Failed to cancel stream reader:', error);\n } finally {\n this.releaseLock();\n }\n return { done: true, value: undefined };\n }\n\n /**\n * Implements the throw method of the async iterator.\n * Releases the lock and returns a done result.\n * @param error - The error to be thrown.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async throw(error: any): Promise<IteratorResult<T>> {\n // Ensure the reader lock is released before throwing\n console.debug('Throwing error:', error);\n this.releaseLock();\n return { done: true, value: undefined };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReadableStreamAsyncIterable } from './readableStreamAsyncIterable';\n\n/**\n * Checks if the current environment natively supports async iteration on ReadableStream.\n *\n * This constant determines whether the browser or runtime already provides\n * built-in support for using ReadableStream with for-await...of loops.\n * If not supported, this library will polyfill the functionality by adding\n * the [Symbol.asyncIterator] method to ReadableStream.prototype.\n *\n * @returns true if native async iteration is supported, false if polyfill is needed\n *\n * @example\n * ```typescript\n * import { isReadableStreamAsyncIterableSupported } from '@ahoo-wang/fetcher-eventstream';\n *\n * if (isReadableStreamAsyncIterableSupported) {\n * console.log('Native support available');\n * } else {\n * console.log('Using polyfill');\n * }\n * ```\n */\nexport const isReadableStreamAsyncIterableSupported =\n typeof ReadableStream !== 'undefined' &&\n typeof ReadableStream.prototype[Symbol.asyncIterator] === 'function';\n\n// Add [Symbol.asyncIterator] to ReadableStream if not already implemented\nif (!isReadableStreamAsyncIterableSupported) {\n ReadableStream.prototype[Symbol.asyncIterator] = function <R = any>() {\n return new ReadableStreamAsyncIterable<R>(this as ReadableStream<R>);\n };\n}\n"],"mappings":"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"}
import { ServerSentEvent } from './serverSentEventTransformStream';
import { ServerSentEventStream } from './eventStreamConverter';
import { SafeTransformer } from './safeTransformer';
/**
* A function type that determines whether a Server-Sent Event should terminate the stream.
*
* This detector function is called for each incoming ServerSentEvent. If it returns true,
* the stream transformation will be terminated, preventing further events from being processed.
*
* @param event - The ServerSentEvent to evaluate for termination
* @returns true if the stream should be terminated, false otherwise
*
* @example
* ```typescript
* const terminateOnDone: TerminateDetector = (event) => {
* return event.event === 'done' || event.data === '[DONE]';
* };
* ```
*/

@@ -23,6 +14,2 @@ export type TerminateDetector = (event: ServerSentEvent) => boolean;

*
* This interface extends the base ServerSentEvent but replaces the string 'data' field
* with a parsed JSON object of the specified generic type. This allows for type-safe
* access to the event payload.
*
* @template DATA - The expected type of the parsed JSON data

@@ -35,72 +22,21 @@ */

/**
* A TransformStream transformer that converts ServerSentEvent to JsonServerSentEvent with optional termination detection.
* A TransformStream transformer that converts ServerSentEvent to
* JsonServerSentEvent with optional termination detection.
*
* This transformer parses the JSON data from ServerSentEvent chunks and optionally terminates
* the stream when a termination condition is met. It's designed to work within a TransformStream
* to convert raw server-sent events into typed JSON events.
* Inherits termination guard and safe controller operations from SafeTransformer.
*
* @template DATA - The expected type of the parsed JSON data in each event
*/
export declare class JsonServerSentEventTransform<DATA> implements Transformer<ServerSentEvent, JsonServerSentEvent<DATA>> {
export declare class JsonServerSentEventTransform<DATA> extends SafeTransformer<ServerSentEvent, JsonServerSentEvent<DATA>> {
private readonly terminateDetector?;
/**
* 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.
*
* @param terminateDetector - Optional function to detect when the stream should be terminated.
* If provided, this function is called for each event and can terminate
* the stream by returning true.
*/
constructor(terminateDetector?: TerminateDetector | undefined);
/**
* Transforms a ServerSentEvent chunk into a JsonServerSentEvent.
*
* 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.
*
* 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
*/
transform(chunk: ServerSentEvent, controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>): void;
protected onTransform(chunk: ServerSentEvent, controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>): void;
}
/**
* A TransformStream that converts ServerSentEvent streams to JsonServerSentEvent streams with optional termination detection.
* A TransformStream that converts ServerSentEvent streams to
* JsonServerSentEvent streams with optional termination detection.
*
* This class extends TransformStream to provide a convenient way to transform streams of ServerSentEvent
* objects into streams of JsonServerSentEvent objects. It supports optional termination detection to
* automatically end the stream when certain conditions are met.
*
* @template DATA - The expected type of the parsed JSON data in each event
*/
export declare class JsonServerSentEventTransformStream<DATA> extends TransformStream<ServerSentEvent, JsonServerSentEvent<DATA>> {
/**
* Creates a new JsonServerSentEventTransformStream instance.
*
* @param terminateDetector - Optional function to detect when the stream should be terminated.
* When provided, the stream will automatically terminate when this
* function returns true for any event.
*
* @example
* ```typescript
* // Create a stream that terminates on 'done' events
* const terminateOnDone: TerminateDetector = (event) => event.event === 'done';
* const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);
*
* // Create a stream without termination detection
* const basicStream = new JsonServerSentEventTransformStream<MyData>();
* ```
*/
constructor(terminateDetector?: TerminateDetector);

@@ -111,5 +47,2 @@ }

*
* This type represents a stream that yields parsed JSON server-sent events.
* Each chunk in the stream contains the event metadata along with parsed JSON data.
*
* @template DATA - The expected type of the parsed JSON data in each event

@@ -121,6 +54,2 @@ */

*
* This function takes a stream of raw server-sent events and transforms it into a stream of
* parsed JSON events. It optionally accepts a termination detector to automatically end the
* stream when certain conditions are met.
*
* @template DATA - The expected type of the parsed JSON data in each event

@@ -130,24 +59,4 @@ * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform

* @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data
* @throws {SyntaxError} If any event data is not valid JSON (thrown during stream consumption)
*
* @example
* ```typescript
* // Basic usage without termination detection
* const jsonStream = toJsonServerSentEventStream<MyData>(serverSentEventStream);
*
* // With termination detection
* const terminateOnDone: TerminateDetector = (event) => event.data === '[DONE]';
* const terminatingStream = toJsonServerSentEventStream<MyData>(
* serverSentEventStream,
* terminateOnDone
* );
*
* // Consume the stream
* for await (const event of jsonStream) {
* console.log('Received:', event.data);
* console.log('Event type:', event.event);
* }
* ```
*/
export declare function toJsonServerSentEventStream<DATA>(serverSentEventStream: ServerSentEventStream, terminateDetector?: TerminateDetector): JsonServerSentEventStream<DATA>;
//# sourceMappingURL=jsonServerSentEventTransformStream.d.ts.map

@@ -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;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"}
{"version":3,"file":"jsonServerSentEventTransformStream.d.ts","sourceRoot":"","sources":["../src/jsonServerSentEventTransformStream.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC;AAEpE;;;;GAIG;AACH,MAAM,WAAW,mBAAmB,CAAC,IAAI,CAAE,SAAQ,IAAI,CACrD,eAAe,EACf,MAAM,CACP;IACC,0CAA0C;IAC1C,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;;;;;GAOG;AACH,qBAAa,4BAA4B,CAAC,IAAI,CAAE,SAAQ,eAAe,CACrE,eAAe,EACf,mBAAmB,CAAC,IAAI,CAAC,CAC1B;IACa,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;gBAAlB,iBAAiB,CAAC,EAAE,iBAAiB,YAAA;IAIlE,SAAS,CAAC,WAAW,CACnB,KAAK,EAAE,eAAe,EACtB,UAAU,EAAE,gCAAgC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,GACtE,IAAI;CAeR;AAED;;;;;GAKG;AACH,qBAAa,kCAAkC,CAAC,IAAI,CAAE,SAAQ,eAAe,CAC3E,eAAe,EACf,mBAAmB,CAAC,IAAI,CAAC,CAC1B;gBACa,iBAAiB,CAAC,EAAE,iBAAiB;CAGlD;AAED;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,CAAC,IAAI,IAAI,cAAc,CAC1D,mBAAmB,CAAC,IAAI,CAAC,CAC1B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAC9C,qBAAqB,EAAE,qBAAqB,EAC5C,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,yBAAyB,CAAC,IAAI,CAAC,CAIjC"}

@@ -0,8 +1,5 @@

import { SafeTransformer, TransformerPhase } from './safeTransformer';
/**
* Represents a message sent in an event stream.
*
* This interface defines the structure of Server-Sent Events (SSE) as specified by the W3C.
* Each event contains metadata and data that can be processed by clients to handle real-time
* updates from the server.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}

@@ -20,79 +17,29 @@ */

}
/**
* Constants for Server-Sent Event field names.
*/
export declare class ServerSentEventFields {
/** The field name for event ID */
static readonly ID = "id";
/** The field name for retry interval */
static readonly RETRY = "retry";
/** The field name for event type */
static readonly EVENT = "event";
/** The field name for event data */
static readonly DATA = "data";
}
/**
* Transformer responsible for converting a string stream into a ServerSentEvent object stream.
*
* Implements the Transformer interface for processing data transformation in TransformStream.
* This transformer handles the parsing of Server-Sent Events (SSE) according to the W3C specification.
* It processes incoming text chunks and converts them into structured ServerSentEvent objects.
* Transformer responsible for converting a string stream into a
* ServerSentEvent object stream, following the W3C SSE specification.
*/
export declare class ServerSentEventTransformer implements Transformer<string, ServerSentEvent> {
export declare class ServerSentEventTransformer extends SafeTransformer<string, ServerSentEvent> {
private currentEventState;
/**
* Reset the current event state to default values.
* This method is called after processing each complete event or when an error occurs.
*/
private resetEventState;
/**
* Transform input string chunk into ServerSentEvent object.
* This method processes individual chunks of text data, parsing them according to the SSE format.
* It handles:
* - Empty lines (used as event separators)
* - Comment lines (starting with ':')
* - Field lines (field: value format)
* - Event completion and emission
*
* @param chunk Input string chunk
* @param controller Controller for controlling the transform stream
*/
transform(chunk: string, controller: TransformStreamDefaultController<ServerSentEvent>): void;
/**
* Called when the stream ends, used to process remaining data.
*
* @param controller Controller for controlling the transform stream
*/
flush(controller: TransformStreamDefaultController<ServerSentEvent>): void;
protected onError(_error: unknown, _phase: TransformerPhase): void;
protected onTransform(chunk: string, controller: TransformStreamDefaultController<ServerSentEvent>): void;
protected onFlush(controller: TransformStreamDefaultController<ServerSentEvent>): void;
}
/**
* A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.
*
* This class provides a convenient way to transform raw text streams containing Server-Sent Events
* into structured event objects. It wraps the ServerSentEventTransformer in a TransformStream
* for easy integration with other stream processing pipelines.
*
* The stream processes SSE format text and emits ServerSentEvent objects as they are completed.
* Events are separated by empty lines, and the stream handles partial events across multiple chunks.
*
* @example
* ```typescript
* // Create a transform stream
* const sseStream = new ServerSentEventTransformStream();
*
* // Pipe a text stream through it
* const eventStream = textStream.pipeThrough(sseStream);
*
* // Consume the events
* for await (const event of eventStream) {
* console.log('Event:', event.event, 'Data:', event.data);
* }
* ```
* A TransformStream that converts a stream of strings into a stream of
* ServerSentEvent objects.
*/
export declare class ServerSentEventTransformStream extends TransformStream<string, ServerSentEvent> {
/**
* Creates a new ServerSentEventTransformStream instance.
*
* Initializes the stream with a ServerSentEventTransformer that handles
* the parsing of SSE format text into structured events.
*/
constructor();
}
//# sourceMappingURL=serverSentEventTransformStream.d.ts.map

@@ -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;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"}
{"version":3,"file":"serverSentEventTransformStream.d.ts","sourceRoot":"","sources":["../src/serverSentEventTransformStream.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE3E;;;;GAIG;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;;GAEG;AACH,qBAAa,qBAAqB;IAChC,MAAM,CAAC,QAAQ,CAAC,EAAE,QAAQ;IAC1B,MAAM,CAAC,QAAQ,CAAC,KAAK,WAAW;IAChC,MAAM,CAAC,QAAQ,CAAC,KAAK,WAAW;IAChC,MAAM,CAAC,QAAQ,CAAC,IAAI,UAAU;CAC/B;AA4CD;;;GAGG;AACH,qBAAa,0BAA2B,SAAQ,eAAe,CAC7D,MAAM,EACN,eAAe,CAChB;IACC,OAAO,CAAC,iBAAiB,CAKvB;IAEF,OAAO,CAAC,eAAe;cAOJ,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAI3E,SAAS,CAAC,WAAW,CACnB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,gCAAgC,CAAC,eAAe,CAAC,GAC5D,IAAI;IA4CP,SAAS,CAAC,OAAO,CACf,UAAU,EAAE,gCAAgC,CAAC,eAAe,CAAC,GAC5D,IAAI;CAeR;AAED;;;GAGG;AACH,qBAAa,8BAA+B,SAAQ,eAAe,CACjE,MAAM,EACN,eAAe,CAChB;;CAIA"}

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

export declare class TextLineTransformer implements Transformer<string, string> {
import { SafeTransformer } from './safeTransformer';
/**
* Transformer that splits text into lines.
*
* Accumulates chunks of text and splits them by newline characters ('\n'),
* emitting each complete line as a separate chunk. Handles partial lines
* that span multiple input chunks by maintaining an internal buffer.
*/
export declare class TextLineTransformer extends SafeTransformer<string, string> {
private buffer;
/**
* Transform input string chunk by splitting it into lines.
*
* @param chunk Input string chunk
* @param controller Controller for controlling the transform stream
*/
transform(chunk: string, controller: TransformStreamDefaultController<string>): void;
/**
* Flush remaining buffer when the stream ends.
*
* @param controller Controller for controlling the transform stream
*/
flush(controller: TransformStreamDefaultController<string>): void;
protected onTransform(chunk: string, controller: TransformStreamDefaultController<string>): void;
protected onFlush(controller: TransformStreamDefaultController<string>): void;
}

@@ -20,18 +17,6 @@ /**

*
* This class provides a convenient way to transform a stream of text chunks into a stream
* of individual lines. It wraps the TextLineTransformer in a TransformStream for easy
* integration with other stream processing pipelines.
*
* The stream processes text data and emits each line as a separate chunk, handling
* lines that may span multiple input chunks automatically.
*
* @example
* ```typescript
* // Create a line-splitting stream
* const lineStream = new TextLineTransformStream();
*
* // Pipe text through it
* const lines = textStream.pipeThrough(lineStream);
*
* // Process each line
* for await (const line of lines) {

@@ -41,26 +26,6 @@ * console.log('Line:', line);

* ```
*
* @example
* ```typescript
* // Process SSE response line by line
* const response = await fetch('/api/stream');
* const lines = response.body!
* .pipeThrough(new TextDecoderStream())
* .pipeThrough(new TextLineTransformStream());
*
* for await (const line of lines) {
* if (line.startsWith('data: ')) {
* console.log('SSE data:', line.substring(6));
* }
* }
* ```
*/
export declare class TextLineTransformStream extends TransformStream<string, string> {
/**
* Creates a new TextLineTransformStream instance.
*
* Initializes the stream with a TextLineTransformer that handles the line splitting logic.
*/
constructor();
}
//# sourceMappingURL=textLineTransformStream.d.ts.map

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

{"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"}
{"version":3,"file":"textLineTransformStream.d.ts","sourceRoot":"","sources":["../src/textLineTransformStream.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC;IACtE,OAAO,CAAC,MAAM,CAAM;IAEpB,SAAS,CAAC,WAAW,CACnB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,gCAAgC,CAAC,MAAM,CAAC,GACnD,IAAI;IAUP,SAAS,CAAC,OAAO,CACf,UAAU,EAAE,gCAAgC,CAAC,MAAM,CAAC,GACnD,IAAI;CAMR;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,uBAAwB,SAAQ,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC;;CAI3E"}
{
"name": "@ahoo-wang/fetcher-eventstream",
"version": "3.16.6",
"version": "3.16.8",
"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": [