@ahoo-wang/fetcher-eventstream 3.13.15 → 3.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventStreamResultExtractor.d.ts","sourceRoot":"","sources":["../src/eventStreamResultExtractor.ts"],"names":[],"mappings":"AAaA,OAAO,EAAiB,eAAe,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"eventStreamResultExtractor.d.ts","sourceRoot":"","sources":["../src/eventStreamResultExtractor.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAiB,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAEtF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,0BAA0B,EAAE,eAAe,CACtD,qBAAqB,CAGtB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,8BAA8B,EAAE,eAAe,CAC1D,yBAAyB,CAAC,GAAG,CAAC,CAG/B,CAAC"}
|
package/dist/index.es.js
CHANGED
|
@@ -1,402 +1,245 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* Initializes the stream with a TextLineTransformer that handles the line splitting logic.
|
|
42
|
-
*/
|
|
43
|
-
constructor() {
|
|
44
|
-
super(new m());
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const c = class c {
|
|
1
|
+
import { CONTENT_TYPE_HEADER as e, ContentTypeValues as t, FetcherError as n } from "@ahoo-wang/fetcher";
|
|
2
|
+
//#region src/textLineTransformStream.ts
|
|
3
|
+
var r = class {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.buffer = "";
|
|
6
|
+
}
|
|
7
|
+
transform(e, t) {
|
|
8
|
+
try {
|
|
9
|
+
this.buffer += e;
|
|
10
|
+
let n = this.buffer.split("\n");
|
|
11
|
+
this.buffer = n.pop() || "";
|
|
12
|
+
for (let e of n) t.enqueue(e);
|
|
13
|
+
} catch (e) {
|
|
14
|
+
t.error(e);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
flush(e) {
|
|
18
|
+
try {
|
|
19
|
+
this.buffer && e.enqueue(this.buffer);
|
|
20
|
+
} catch (t) {
|
|
21
|
+
e.error(t);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}, i = class extends TransformStream {
|
|
25
|
+
constructor() {
|
|
26
|
+
super(new r());
|
|
27
|
+
}
|
|
28
|
+
}, a = class {
|
|
29
|
+
static {
|
|
30
|
+
this.ID = "id";
|
|
31
|
+
}
|
|
32
|
+
static {
|
|
33
|
+
this.RETRY = "retry";
|
|
34
|
+
}
|
|
35
|
+
static {
|
|
36
|
+
this.EVENT = "event";
|
|
37
|
+
}
|
|
38
|
+
static {
|
|
39
|
+
this.DATA = "data";
|
|
40
|
+
}
|
|
48
41
|
};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const u = "message";
|
|
70
|
-
class S {
|
|
71
|
-
constructor() {
|
|
72
|
-
this.currentEventState = {
|
|
73
|
-
event: u,
|
|
74
|
-
id: void 0,
|
|
75
|
-
retry: void 0,
|
|
76
|
-
data: []
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Reset the current event state to default values.
|
|
81
|
-
* This method is called after processing each complete event or when an error occurs.
|
|
82
|
-
*/
|
|
83
|
-
resetEventState() {
|
|
84
|
-
this.currentEventState.event = u, this.currentEventState.id = void 0, this.currentEventState.retry = void 0, this.currentEventState.data = [];
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Transform input string chunk into ServerSentEvent object.
|
|
88
|
-
* This method processes individual chunks of text data, parsing them according to the SSE format.
|
|
89
|
-
* It handles:
|
|
90
|
-
* - Empty lines (used as event separators)
|
|
91
|
-
* - Comment lines (starting with ':')
|
|
92
|
-
* - Field lines (field: value format)
|
|
93
|
-
* - Event completion and emission
|
|
94
|
-
*
|
|
95
|
-
* @param chunk Input string chunk
|
|
96
|
-
* @param controller Controller for controlling the transform stream
|
|
97
|
-
*/
|
|
98
|
-
transform(e, r) {
|
|
99
|
-
const n = this.currentEventState;
|
|
100
|
-
try {
|
|
101
|
-
if (e.trim() === "") {
|
|
102
|
-
n.data.length > 0 && (r.enqueue({
|
|
103
|
-
event: n.event || u,
|
|
104
|
-
data: n.data.join(`
|
|
105
|
-
`),
|
|
106
|
-
id: n.id || "",
|
|
107
|
-
retry: n.retry
|
|
108
|
-
}), n.event = u, n.data = []);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
if (e.startsWith(":"))
|
|
112
|
-
return;
|
|
113
|
-
const s = e.indexOf(":");
|
|
114
|
-
let a, o;
|
|
115
|
-
s === -1 ? (a = e.toLowerCase(), o = "") : (a = e.substring(0, s).toLowerCase(), o = e.substring(s + 1), o.startsWith(" ") && (o = o.substring(1))), a = a.trim(), o = o.trim(), v(a, o, n);
|
|
116
|
-
} catch (s) {
|
|
117
|
-
const a = new Error(
|
|
118
|
-
`Failed to process chunk: "${e}". ${s instanceof Error ? s.message : String(s)}`
|
|
119
|
-
);
|
|
120
|
-
r.error(a), this.resetEventState();
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Called when the stream ends, used to process remaining data.
|
|
125
|
-
*
|
|
126
|
-
* @param controller Controller for controlling the transform stream
|
|
127
|
-
*/
|
|
128
|
-
flush(e) {
|
|
129
|
-
const r = this.currentEventState;
|
|
130
|
-
try {
|
|
131
|
-
r.data.length > 0 && e.enqueue({
|
|
132
|
-
event: r.event || u,
|
|
133
|
-
data: r.data.join(`
|
|
134
|
-
`),
|
|
135
|
-
id: r.id || "",
|
|
136
|
-
retry: r.retry
|
|
137
|
-
});
|
|
138
|
-
} catch (n) {
|
|
139
|
-
const s = new Error(
|
|
140
|
-
`Failed to flush remaining data. ${n instanceof Error ? n.message : String(n)}`
|
|
141
|
-
);
|
|
142
|
-
e.error(s);
|
|
143
|
-
} finally {
|
|
144
|
-
this.resetEventState();
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
class T extends TransformStream {
|
|
149
|
-
/**
|
|
150
|
-
* Creates a new ServerSentEventTransformStream instance.
|
|
151
|
-
*
|
|
152
|
-
* Initializes the stream with a ServerSentEventTransformer that handles
|
|
153
|
-
* the parsing of SSE format text into structured events.
|
|
154
|
-
*/
|
|
155
|
-
constructor() {
|
|
156
|
-
super(new S());
|
|
157
|
-
}
|
|
42
|
+
function o(e, t, n) {
|
|
43
|
+
switch (e) {
|
|
44
|
+
case a.EVENT:
|
|
45
|
+
n.event = t;
|
|
46
|
+
break;
|
|
47
|
+
case a.DATA:
|
|
48
|
+
n.data.push(t);
|
|
49
|
+
break;
|
|
50
|
+
case a.ID:
|
|
51
|
+
n.id = t;
|
|
52
|
+
break;
|
|
53
|
+
case a.RETRY: {
|
|
54
|
+
let e = parseInt(t, 10);
|
|
55
|
+
isNaN(e) || (n.retry = e);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
default: break;
|
|
59
|
+
}
|
|
158
60
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
id: e.id,
|
|
220
|
-
retry: e.retry
|
|
221
|
-
});
|
|
222
|
-
} catch (n) {
|
|
223
|
-
r.error(n);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
class w extends TransformStream {
|
|
229
|
-
/**
|
|
230
|
-
* Creates a new JsonServerSentEventTransformStream instance.
|
|
231
|
-
*
|
|
232
|
-
* @param terminateDetector - Optional function to detect when the stream should be terminated.
|
|
233
|
-
* When provided, the stream will automatically terminate when this
|
|
234
|
-
* function returns true for any event.
|
|
235
|
-
*
|
|
236
|
-
* @example
|
|
237
|
-
* ```typescript
|
|
238
|
-
* // Create a stream that terminates on 'done' events
|
|
239
|
-
* const terminateOnDone: TerminateDetector = (event) => event.event === 'done';
|
|
240
|
-
* const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);
|
|
241
|
-
*
|
|
242
|
-
* // Create a stream without termination detection
|
|
243
|
-
* const basicStream = new JsonServerSentEventTransformStream<MyData>();
|
|
244
|
-
* ```
|
|
245
|
-
*/
|
|
246
|
-
constructor(e) {
|
|
247
|
-
super(new R(e));
|
|
248
|
-
}
|
|
61
|
+
var s = "message", c = class {
|
|
62
|
+
constructor() {
|
|
63
|
+
this.currentEventState = {
|
|
64
|
+
event: s,
|
|
65
|
+
id: void 0,
|
|
66
|
+
retry: void 0,
|
|
67
|
+
data: []
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
resetEventState() {
|
|
71
|
+
this.currentEventState.event = s, this.currentEventState.id = void 0, this.currentEventState.retry = void 0, this.currentEventState.data = [];
|
|
72
|
+
}
|
|
73
|
+
transform(e, t) {
|
|
74
|
+
let n = this.currentEventState;
|
|
75
|
+
try {
|
|
76
|
+
if (e.trim() === "") {
|
|
77
|
+
n.data.length > 0 && (t.enqueue({
|
|
78
|
+
event: n.event || s,
|
|
79
|
+
data: n.data.join("\n"),
|
|
80
|
+
id: n.id || "",
|
|
81
|
+
retry: n.retry
|
|
82
|
+
}), n.event = s, n.data = []);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (e.startsWith(":")) return;
|
|
86
|
+
let r = e.indexOf(":"), i, a;
|
|
87
|
+
r === -1 ? (i = e.toLowerCase(), a = "") : (i = e.substring(0, r).toLowerCase(), a = e.substring(r + 1), a.startsWith(" ") && (a = a.substring(1))), i = i.trim(), a = a.trim(), o(i, a, n);
|
|
88
|
+
} catch (n) {
|
|
89
|
+
let r = /* @__PURE__ */ Error(`Failed to process chunk: "${e}". ${n instanceof Error ? n.message : String(n)}`);
|
|
90
|
+
t.error(r), this.resetEventState();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
flush(e) {
|
|
94
|
+
let t = this.currentEventState;
|
|
95
|
+
try {
|
|
96
|
+
t.data.length > 0 && e.enqueue({
|
|
97
|
+
event: t.event || s,
|
|
98
|
+
data: t.data.join("\n"),
|
|
99
|
+
id: t.id || "",
|
|
100
|
+
retry: t.retry
|
|
101
|
+
});
|
|
102
|
+
} catch (t) {
|
|
103
|
+
let n = /* @__PURE__ */ Error(`Failed to flush remaining data. ${t instanceof Error ? t.message : String(t)}`);
|
|
104
|
+
e.error(n);
|
|
105
|
+
} finally {
|
|
106
|
+
this.resetEventState();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}, l = class extends TransformStream {
|
|
110
|
+
constructor() {
|
|
111
|
+
super(new c());
|
|
112
|
+
}
|
|
113
|
+
}, u = class e extends n {
|
|
114
|
+
constructor(t, n, r) {
|
|
115
|
+
super(n, r), this.response = t, this.name = "EventStreamConvertError", Object.setPrototypeOf(this, e.prototype);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
function d(e) {
|
|
119
|
+
if (!e.body) throw new u(e, "Response body is null");
|
|
120
|
+
return e.body.pipeThrough(new TextDecoderStream("utf-8")).pipeThrough(new i()).pipeThrough(new l());
|
|
249
121
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/jsonServerSentEventTransformStream.ts
|
|
124
|
+
var f = class {
|
|
125
|
+
constructor(e) {
|
|
126
|
+
this.terminateDetector = e;
|
|
127
|
+
}
|
|
128
|
+
transform(e, t) {
|
|
129
|
+
try {
|
|
130
|
+
if (this.terminateDetector?.(e)) {
|
|
131
|
+
t.terminate();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
let n = JSON.parse(e.data);
|
|
135
|
+
t.enqueue({
|
|
136
|
+
data: n,
|
|
137
|
+
event: e.event,
|
|
138
|
+
id: e.id,
|
|
139
|
+
retry: e.retry
|
|
140
|
+
});
|
|
141
|
+
} catch (e) {
|
|
142
|
+
t.error(e);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}, p = class extends TransformStream {
|
|
147
|
+
constructor(e) {
|
|
148
|
+
super(new f(e));
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
function m(e, t) {
|
|
152
|
+
return e.pipeThrough(new p(t));
|
|
254
153
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
configurable: !0
|
|
264
|
-
});
|
|
265
|
-
const h = "isEventStream";
|
|
266
|
-
Object.prototype.hasOwnProperty.call(
|
|
267
|
-
Response.prototype,
|
|
268
|
-
h
|
|
269
|
-
) || Object.defineProperty(Response.prototype, h, {
|
|
270
|
-
get() {
|
|
271
|
-
const t = this.contentType;
|
|
272
|
-
return t ? t.includes(y.TEXT_EVENT_STREAM) : !1;
|
|
273
|
-
},
|
|
274
|
-
configurable: !0
|
|
275
|
-
});
|
|
276
|
-
Object.prototype.hasOwnProperty.call(Response.prototype, "eventStream") || (Response.prototype.eventStream = function() {
|
|
277
|
-
return this.isEventStream ? b(this) : null;
|
|
278
|
-
});
|
|
279
|
-
Object.prototype.hasOwnProperty.call(
|
|
280
|
-
Response.prototype,
|
|
281
|
-
"requiredEventStream"
|
|
282
|
-
) || (Response.prototype.requiredEventStream = function() {
|
|
283
|
-
const t = this.eventStream();
|
|
284
|
-
if (!t)
|
|
285
|
-
throw new p(
|
|
286
|
-
this,
|
|
287
|
-
`Event stream is not available. Response content-type: [${this.contentType}]`
|
|
288
|
-
);
|
|
289
|
-
return t;
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/eventStreamResultExtractor.ts
|
|
156
|
+
var h = (e) => e.requiredResponse.requiredEventStream(), g = (e) => e.requiredResponse.requiredJsonEventStream(), _ = "contentType";
|
|
157
|
+
Object.prototype.hasOwnProperty.call(Response.prototype, _) || Object.defineProperty(Response.prototype, _, {
|
|
158
|
+
get() {
|
|
159
|
+
return this.headers.get(e);
|
|
160
|
+
},
|
|
161
|
+
configurable: !0
|
|
290
162
|
});
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
163
|
+
var v = "isEventStream";
|
|
164
|
+
Object.prototype.hasOwnProperty.call(Response.prototype, v) || Object.defineProperty(Response.prototype, v, {
|
|
165
|
+
get() {
|
|
166
|
+
let e = this.contentType;
|
|
167
|
+
return e ? e.includes(t.TEXT_EVENT_STREAM) : !1;
|
|
168
|
+
},
|
|
169
|
+
configurable: !0
|
|
170
|
+
}), Object.prototype.hasOwnProperty.call(Response.prototype, "eventStream") || (Response.prototype.eventStream = function() {
|
|
171
|
+
return this.isEventStream ? d(this) : null;
|
|
172
|
+
}), Object.prototype.hasOwnProperty.call(Response.prototype, "requiredEventStream") || (Response.prototype.requiredEventStream = function() {
|
|
173
|
+
let e = this.eventStream();
|
|
174
|
+
if (!e) throw new u(this, `Event stream is not available. Response content-type: [${this.contentType}]`);
|
|
175
|
+
return e;
|
|
176
|
+
}), Object.prototype.hasOwnProperty.call(Response.prototype, "jsonEventStream") || (Response.prototype.jsonEventStream = function(e) {
|
|
177
|
+
let t = this.eventStream();
|
|
178
|
+
return t ? m(t, e) : null;
|
|
179
|
+
}), Object.prototype.hasOwnProperty.call(Response.prototype, "requiredJsonEventStream") || (Response.prototype.requiredJsonEventStream = function(e) {
|
|
180
|
+
let t = this.jsonEventStream(e);
|
|
181
|
+
if (!t) throw new u(this, `Event stream is not available. Response content-type: [${this.contentType}]`);
|
|
182
|
+
return t;
|
|
294
183
|
});
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/readableStreamAsyncIterable.ts
|
|
186
|
+
var y = class {
|
|
187
|
+
constructor(e) {
|
|
188
|
+
this.stream = e, this._locked = !0, this.reader = e.getReader();
|
|
189
|
+
}
|
|
190
|
+
get locked() {
|
|
191
|
+
return this._locked;
|
|
192
|
+
}
|
|
193
|
+
releaseLock() {
|
|
194
|
+
if (!this._locked) return !1;
|
|
195
|
+
this._locked = !1;
|
|
196
|
+
try {
|
|
197
|
+
return this.reader.releaseLock(), !0;
|
|
198
|
+
} catch (e) {
|
|
199
|
+
return console.debug("Failed to release reader lock:", e), !1;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
[Symbol.asyncIterator]() {
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
async next() {
|
|
206
|
+
try {
|
|
207
|
+
let { done: e, value: t } = await this.reader.read();
|
|
208
|
+
return e ? (this.releaseLock(), {
|
|
209
|
+
done: !0,
|
|
210
|
+
value: void 0
|
|
211
|
+
}) : {
|
|
212
|
+
done: !1,
|
|
213
|
+
value: t
|
|
214
|
+
};
|
|
215
|
+
} catch (e) {
|
|
216
|
+
throw this.releaseLock(), e;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async return() {
|
|
220
|
+
try {
|
|
221
|
+
await this.reader.cancel();
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.debug("Failed to cancel stream reader:", e);
|
|
224
|
+
} finally {
|
|
225
|
+
this.releaseLock();
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
done: !0,
|
|
229
|
+
value: void 0
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
async throw(e) {
|
|
233
|
+
return console.debug("Throwing error:", e), this.releaseLock(), {
|
|
234
|
+
done: !0,
|
|
235
|
+
value: void 0
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}, b = typeof ReadableStream.prototype[Symbol.asyncIterator] == "function";
|
|
239
|
+
b || (ReadableStream.prototype[Symbol.asyncIterator] = function() {
|
|
240
|
+
return new y(this);
|
|
306
241
|
});
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
*/
|
|
312
|
-
constructor(e) {
|
|
313
|
-
this.stream = e, this._locked = !0, this.reader = e.getReader();
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Gets the lock status of the reader.
|
|
317
|
-
* @returns True if the reader is currently locked, false otherwise.
|
|
318
|
-
*/
|
|
319
|
-
get locked() {
|
|
320
|
-
return this._locked;
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Releases the reader lock if currently locked.
|
|
324
|
-
* This method safely releases the reader lock by catching any potential errors.
|
|
325
|
-
*/
|
|
326
|
-
releaseLock() {
|
|
327
|
-
if (!this._locked) return !1;
|
|
328
|
-
this._locked = !1;
|
|
329
|
-
try {
|
|
330
|
-
return this.reader.releaseLock(), !0;
|
|
331
|
-
} catch (e) {
|
|
332
|
-
return console.debug("Failed to release reader lock:", e), !1;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Implements the AsyncIterable interface by returning this iterator.
|
|
337
|
-
* @returns The async iterator for this instance.
|
|
338
|
-
*/
|
|
339
|
-
[Symbol.asyncIterator]() {
|
|
340
|
-
return this;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Gets the next value from the stream.
|
|
344
|
-
* Reads the next chunk from the stream and returns it as an IteratorResult.
|
|
345
|
-
* If the stream is done, releases the lock and returns a done result.
|
|
346
|
-
* @returns A promise that resolves to an IteratorResult containing the next value or done status.
|
|
347
|
-
* @throws If an error occurs while reading from the stream.
|
|
348
|
-
*/
|
|
349
|
-
async next() {
|
|
350
|
-
try {
|
|
351
|
-
const { done: e, value: r } = await this.reader.read();
|
|
352
|
-
return e ? (this.releaseLock(), { done: !0, value: void 0 }) : { done: !1, value: r };
|
|
353
|
-
} catch (e) {
|
|
354
|
-
throw this.releaseLock(), e;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Implements the return method of the async iterator.
|
|
359
|
-
* Cancels the stream reader and releases the lock.
|
|
360
|
-
* @returns A promise that resolves to a done IteratorResult.
|
|
361
|
-
*/
|
|
362
|
-
async return() {
|
|
363
|
-
try {
|
|
364
|
-
await this.reader.cancel();
|
|
365
|
-
} catch (e) {
|
|
366
|
-
console.debug("Failed to cancel stream reader:", e);
|
|
367
|
-
} finally {
|
|
368
|
-
this.releaseLock();
|
|
369
|
-
}
|
|
370
|
-
return { done: !0, value: void 0 };
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Implements the throw method of the async iterator.
|
|
374
|
-
* Releases the lock and returns a done result.
|
|
375
|
-
* @param error - The error to be thrown.
|
|
376
|
-
* @returns A promise that resolves to a done IteratorResult.
|
|
377
|
-
*/
|
|
378
|
-
async throw(e) {
|
|
379
|
-
return console.debug("Throwing error:", e), this.releaseLock(), { done: !0, value: void 0 };
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
const P = typeof ReadableStream.prototype[Symbol.asyncIterator] == "function";
|
|
383
|
-
P || (ReadableStream.prototype[Symbol.asyncIterator] = function() {
|
|
384
|
-
return new O(this);
|
|
385
|
-
});
|
|
386
|
-
export {
|
|
387
|
-
p as EventStreamConvertError,
|
|
388
|
-
N as EventStreamResultExtractor,
|
|
389
|
-
q as JsonEventStreamResultExtractor,
|
|
390
|
-
R as JsonServerSentEventTransform,
|
|
391
|
-
w as JsonServerSentEventTransformStream,
|
|
392
|
-
O as ReadableStreamAsyncIterable,
|
|
393
|
-
i as ServerSentEventFields,
|
|
394
|
-
T as ServerSentEventTransformStream,
|
|
395
|
-
S as ServerSentEventTransformer,
|
|
396
|
-
E as TextLineTransformStream,
|
|
397
|
-
m as TextLineTransformer,
|
|
398
|
-
P as isReadableStreamAsyncIterableSupported,
|
|
399
|
-
g as toJsonServerSentEventStream,
|
|
400
|
-
b as toServerSentEventStream
|
|
401
|
-
};
|
|
402
|
-
//# sourceMappingURL=index.es.js.map
|
|
242
|
+
//#endregion
|
|
243
|
+
export { u as EventStreamConvertError, h as EventStreamResultExtractor, g as JsonEventStreamResultExtractor, f as JsonServerSentEventTransform, p as JsonServerSentEventTransformStream, y as ReadableStreamAsyncIterable, a as ServerSentEventFields, l as ServerSentEventTransformStream, c as ServerSentEventTransformer, i as TextLineTransformStream, r as TextLineTransformer, b as isReadableStreamAsyncIterableSupported, m as toJsonServerSentEventStream, d as toServerSentEventStream };
|
|
244
|
+
|
|
245
|
+
//# sourceMappingURL=index.es.js.map
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":["../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/eventStreamResultExtractor.ts","../src/responses.ts","../src/readableStreamAsyncIterable.ts","../src/readableStreams.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Transformer that splits text into lines.\n *\n * This transformer accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. It handles partial lines that span multiple\n * input chunks by maintaining an internal buffer. Lines are emitted without the newline character.\n *\n * The transformer handles various edge cases:\n * - Lines that span multiple input chunks\n * - Empty lines (emitted as empty strings)\n * - Text without trailing newlines (buffered until stream ends)\n * - Mixed line endings (only '\\n' is recognized as line separator)\n *\n * @implements {Transformer<string, string>}\n *\n * @example\n * ```typescript\n * const transformer = new TextLineTransformer();\n * // Input: \"line1\\nline2\\npartial\"\n * // Output: [\"line1\", \"line2\"]\n * // Buffer: \"partial\"\n *\n * // Later input: \"line\\n\"\n * // Output: [\"partialline\"]\n * // Buffer: \"\"\n * ```\n */\nexport class TextLineTransformer implements Transformer<string, string> {\n private buffer = '';\n\n /**\n * Transform input string chunk by splitting it into lines.\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ) {\n try {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n controller.enqueue(line);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n\n /**\n * Flush remaining buffer when the stream ends.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<string>) {\n try {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n controller.enqueue(this.buffer);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * This class provides a convenient way to transform a stream of text chunks into a stream\n * of individual lines. It wraps the TextLineTransformer in a TransformStream for easy\n * integration with other stream processing pipelines.\n *\n * The stream processes text data and emits each line as a separate chunk, handling\n * lines that may span multiple input chunks automatically.\n *\n * @example\n * ```typescript\n * // Create a line-splitting stream\n * const lineStream = new TextLineTransformStream();\n *\n * // Pipe text through it\n * const lines = textStream.pipeThrough(lineStream);\n *\n * // Process each line\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Process SSE response line by line\n * const response = await fetch('/api/stream');\n * const lines = response.body!\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(new TextLineTransformStream());\n *\n * for await (const line of lines) {\n * if (line.startsWith('data: ')) {\n * console.log('SSE data:', line.substring(6));\n * }\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n /**\n * Creates a new TextLineTransformStream instance.\n *\n * Initializes the stream with a TextLineTransformer that handles the line splitting logic.\n */\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * This interface defines the structure of Server-Sent Events (SSE) as specified by the W3C.\n * Each event contains metadata and data that can be processed by clients to handle real-time\n * updates from the server.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\n/**\n * Constants for Server-Sent Event field names.\n *\n * This class provides string constants for the standard SSE field names as defined\n * in the W3C Server-Sent Events specification. These constants help ensure\n * consistent field name usage throughout the parsing logic.\n */\nexport class ServerSentEventFields {\n /** The field name for event ID */\n static readonly ID = 'id';\n /** The field name for retry interval */\n static readonly RETRY = 'retry';\n /** The field name for event type */\n static readonly EVENT = 'event';\n /** The field name for event data */\n static readonly DATA = 'data';\n}\n\n/**\n * Processes a field-value pair and updates the current event state accordingly.\n *\n * This internal function handles the parsing of individual SSE fields according to the\n * Server-Sent Events specification. It updates the provided event state object with\n * the parsed field values.\n *\n * @param field - The field name (e.g., 'event', 'data', 'id', 'retry')\n * @param value - The field value as a string\n * @param currentEvent - The current event state object to update\n *\n * @example\n * ```typescript\n * const eventState: EventState = { event: 'message', data: [] };\n * processFieldInternal('event', 'custom', eventState);\n * // eventState.event is now 'custom'\n * ```\n */\nfunction processFieldInternal(\n field: string,\n value: string,\n currentEvent: EventState,\n) {\n switch (field) {\n case ServerSentEventFields.EVENT:\n currentEvent.event = value;\n break;\n case ServerSentEventFields.DATA:\n currentEvent.data.push(value);\n break;\n case ServerSentEventFields.ID:\n currentEvent.id = value;\n break;\n case ServerSentEventFields.RETRY: {\n const retryValue = parseInt(value, 10);\n if (!isNaN(retryValue)) {\n currentEvent.retry = retryValue;\n }\n break;\n }\n default:\n // Ignore unknown fields\n break;\n }\n}\n\n/**\n * Internal state representation during Server-Sent Event parsing.\n *\n * This interface tracks the current state of an event being parsed from the SSE stream.\n * It accumulates field values until a complete event is ready to be emitted.\n */\ninterface EventState {\n /** The event type (defaults to 'message') */\n event?: string;\n /** The event ID */\n id?: string;\n /** The retry interval in milliseconds */\n retry?: number;\n /** Array of data lines that will be joined with newlines */\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n * This transformer handles the parsing of Server-Sent Events (SSE) according to the W3C specification.\n * It processes incoming text chunks and converts them into structured ServerSentEvent objects.\n */\nexport class ServerSentEventTransformer\n implements Transformer<string, ServerSentEvent>\n{\n // Initialize currentEventState with default values in a closure\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Reset the current event state to default values.\n * This method is called after processing each complete event or when an error occurs.\n */\n private resetEventState() {\n this.currentEventState.event = DEFAULT_EVENT_TYPE;\n this.currentEventState.id = undefined;\n this.currentEventState.retry = undefined;\n this.currentEventState.data = [];\n }\n\n /**\n * Transform input string chunk into ServerSentEvent object.\n * This method processes individual chunks of text data, parsing them according to the SSE format.\n * It handles:\n * - Empty lines (used as event separators)\n * - Comment lines (starting with ':')\n * - Field lines (field: value format)\n * - Event completion and emission\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ) {\n const currentEvent = this.currentEventState;\n try {\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n // If there is accumulated event data, send event\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n\n // Reset current event (preserve id and retry for subsequent events)\n currentEvent.event = DEFAULT_EVENT_TYPE;\n // Preserve id and retry for subsequent events (no need to reassign to themselves)\n currentEvent.data = [];\n }\n return;\n }\n\n // Ignore comment lines (starting with colon)\n if (chunk.startsWith(':')) {\n return;\n }\n\n // Parse fields\n const colonIndex = chunk.indexOf(':');\n let field: string;\n let value: string;\n\n if (colonIndex === -1) {\n // No colon, entire line as field name, value is empty\n field = chunk.toLowerCase();\n value = '';\n } else {\n // Extract field name and value\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n\n // If value starts with space, remove leading space\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n // Remove trailing newlines from field and value\n field = field.trim();\n value = value.trim();\n\n processFieldInternal(field, value, currentEvent);\n } catch (error) {\n const enhancedError = new Error(\n `Failed to process chunk: \"${chunk}\". ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n // Reset state\n this.resetEventState();\n }\n }\n\n /**\n * Called when the stream ends, used to process remaining data.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<ServerSentEvent>) {\n const currentEvent = this.currentEventState;\n try {\n // Send the last event (if any)\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n }\n } catch (error) {\n const enhancedError = new Error(\n `Failed to flush remaining data. ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n } finally {\n // Reset state\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n *\n * This class provides a convenient way to transform raw text streams containing Server-Sent Events\n * into structured event objects. It wraps the ServerSentEventTransformer in a TransformStream\n * for easy integration with other stream processing pipelines.\n *\n * The stream processes SSE format text and emits ServerSentEvent objects as they are completed.\n * Events are separated by empty lines, and the stream handles partial events across multiple chunks.\n *\n * @example\n * ```typescript\n * // Create a transform stream\n * const sseStream = new ServerSentEventTransformStream();\n *\n * // Pipe a text stream through it\n * const eventStream = textStream.pipeThrough(sseStream);\n *\n * // Consume the events\n * for await (const event of eventStream) {\n * console.log('Event:', event.event, 'Data:', event.data);\n * }\n * ```\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n /**\n * Creates a new ServerSentEventTransformStream instance.\n *\n * Initializes the stream with a ServerSentEventTransformer that handles\n * the parsing of SSE format text into structured events.\n */\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n type ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n *\n * This type represents a stream that yields Server-Sent Event objects as they are parsed\n * from a raw event stream. Each chunk in the stream contains a complete SSE event with\n * its metadata (event type, ID, retry interval) and data.\n *\n * @see {@link ServerSentEvent} for the structure of individual events\n * @see {@link toServerSentEventStream} for converting HTTP responses to this type\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n *\n * This error is thrown when there are issues converting an HTTP Response to a ServerSentEventStream.\n * It extends FetcherError to provide additional context about the failed conversion, including\n * the original Response object and any underlying cause.\n *\n * @extends {FetcherError}\n *\n * @example\n * ```typescript\n * try {\n * const eventStream = toServerSentEventStream(response);\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Failed to convert response to event stream:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n *\n * @param response - The Response object associated with the error, providing context\n * about the failed conversion (status, headers, etc.)\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this conversion error\n */\n constructor(\n public readonly response: Response,\n errorMsg?: string,\n cause?: Error | any,\n ) {\n super(errorMsg, cause);\n this.name = 'EventStreamConvertError';\n // Restore prototype chain for proper inheritance\n Object.setPrototypeOf(this, EventStreamConvertError.prototype);\n }\n}\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * This function takes an HTTP Response object and converts its body into a stream of\n * Server-Sent Event objects. The conversion process involves several transformation steps:\n *\n * 1. **TextDecoderStream**: Decodes the raw Uint8Array response body to UTF-8 strings\n * 2. **TextLineTransformStream**: Splits the text stream into individual lines\n * 3. **ServerSentEventTransformStream**: Parses the line-based SSE format into structured events\n *\n * The resulting stream can be consumed using async iteration or other stream methods.\n *\n * @param response - The HTTP Response object to convert. Must have a readable body stream.\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {EventStreamConvertError} If the response body is null or cannot be processed\n *\n * @example\n * ```typescript\n * // Convert an SSE response to an event stream\n * const response = await fetch('/api/events');\n * const eventStream = toServerSentEventStream(response);\n *\n * // Consume events asynchronously\n * for await (const event of eventStream) {\n * console.log(`Event: ${event.event}, Data: ${event.data}`);\n *\n * // Handle different event types\n * switch (event.event) {\n * case 'message':\n * handleMessage(event.data);\n * break;\n * case 'error':\n * handleError(event.data);\n * break;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Handle conversion errors\n * try {\n * const eventStream = toServerSentEventStream(response);\n * // Use the stream...\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Event stream conversion failed:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new EventStreamConvertError(response, 'Response body is null');\n }\n\n return response.body\n .pipeThrough(new TextDecoderStream('utf-8'))\n .pipeThrough(new TextLineTransformStream())\n .pipeThrough(new ServerSentEventTransformStream());\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type ServerSentEvent } from './serverSentEventTransformStream';\nimport { ServerSentEventStream } from './eventStreamConverter';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * This detector function is called for each incoming ServerSentEvent. If it returns true,\n * the stream transformation will be terminated, preventing further events from being processed.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n *\n * @example\n * ```typescript\n * const terminateOnDone: TerminateDetector = (event) => {\n * return event.event === 'done' || event.data === '[DONE]';\n * };\n * ```\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\n *\n * This interface extends the base ServerSentEvent but replaces the string 'data' field\n * with a parsed JSON object of the specified generic type. This allows for type-safe\n * access to the event payload.\n *\n * @template DATA - The expected type of the parsed JSON data\n */\nexport interface JsonServerSentEvent<DATA>\n extends Omit<ServerSentEvent, 'data'> {\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>\n implements Transformer<ServerSentEvent, JsonServerSentEvent<DATA>>\n{\n /**\n * Creates a new JsonServerSentEventTransform instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * If provided, this function is called for each event and can terminate\n * the stream by returning true.\n */\n constructor(private readonly terminateDetector?: TerminateDetector) {}\n\n /**\n * Transforms a ServerSentEvent chunk into a JsonServerSentEvent.\n *\n * This method first checks if the event should terminate the stream using the terminateDetector.\n * If termination is required, the controller is terminated. Otherwise, the event data is parsed\n * as JSON and enqueued as a JsonServerSentEvent.\n *\n * If the terminateDetector throws an exception, the stream is terminated with an error to prevent\n * corrupted state.\n *\n * @param chunk - The ServerSentEvent to transform\n * @param controller - The TransformStream controller for managing the stream\n * @throws {SyntaxError} If the event data is not valid JSON\n * @throws {Error} If the terminateDetector throws an exception\n *\n * @example\n * ```typescript\n * const transformer = new JsonServerSentEventTransform<MyData>();\n * // This will be called automatically by the TransformStream\n * ```\n */\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ) {\n try {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n controller.terminate();\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n controller.enqueue({\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n } catch (error) {\n // If terminate detector throws or JSON parsing fails, terminate the stream to prevent corrupted state\n controller.error(error);\n return;\n }\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to JsonServerSentEvent streams with optional termination detection.\n *\n * This class extends TransformStream to provide a convenient way to transform streams of ServerSentEvent\n * objects into streams of JsonServerSentEvent objects. It supports optional termination detection to\n * automatically end the stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransformStream instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * When provided, the stream will automatically terminate when this\n * function returns true for any event.\n *\n * @example\n * ```typescript\n * // Create a stream that terminates on 'done' events\n * const terminateOnDone: TerminateDetector = (event) => event.event === 'done';\n * const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);\n *\n * // Create a stream without termination detection\n * const basicStream = new JsonServerSentEventTransformStream<MyData>();\n * ```\n */\n constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\n *\n * This type represents a stream that yields parsed JSON server-sent events.\n * Each chunk in the stream contains the event metadata along with parsed JSON data.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\n/**\n * Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.\n *\n * This function takes a stream of raw server-sent events and transforms it into a stream of\n * parsed JSON events. It optionally accepts a termination detector to automatically end the\n * stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform\n * @param terminateDetector - Optional function to detect when the stream should be terminated\n * @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data\n * @throws {SyntaxError} If any event data is not valid JSON (thrown during stream consumption)\n *\n * @example\n * ```typescript\n * // Basic usage without termination detection\n * const jsonStream = toJsonServerSentEventStream<MyData>(serverSentEventStream);\n *\n * // With termination detection\n * const terminateOnDone: TerminateDetector = (event) => event.data === '[DONE]';\n * const terminatingStream = toJsonServerSentEventStream<MyData>(\n * serverSentEventStream,\n * terminateOnDone\n * );\n *\n * // Consume the stream\n * for await (const event of jsonStream) {\n * console.log('Received:', event.data);\n * console.log('Event type:', event.event);\n * }\n * ```\n */\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n terminateDetector?: TerminateDetector,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(terminateDetector),\n );\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FetchExchange, ResultExtractor } from '@ahoo-wang/fetcher';\nimport { ServerSentEventStream } from './eventStreamConverter';\nimport { 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\nconst 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 */\nif (\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\nconst 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 */\nif (\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 */\nif (!Object.prototype.hasOwnProperty.call(Response.prototype, 'eventStream')) {\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 */\nif (\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 */\nif (\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 */\nif (\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 * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper class that converts a ReadableStream into an AsyncIterable.\n *\n * This class enables the use of ReadableStream objects with async iteration syntax\n * (for-await...of loops), providing a more ergonomic way to consume streaming data.\n * It implements the AsyncIterable interface and manages the underlying stream reader,\n * handling proper resource cleanup and error propagation.\n *\n * The wrapper automatically handles stream locking, ensuring that only one consumer\n * can read from the stream at a time, and provides safe cleanup when iteration ends\n * or errors occur.\n *\n * @template T - The type of data yielded by the stream\n *\n * @example\n * ```typescript\n * // Direct usage\n * const response = await fetch('/api/stream');\n * const stream = response.body;\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * console.log('Received:', chunk);\n * }\n * // Stream is automatically cleaned up after iteration\n * ```\n *\n * @example\n * ```typescript\n * // With early termination\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * if (someCondition) {\n * asyncIterable.releaseLock(); // Manually release if needed\n * break;\n * }\n * }\n * ```\n */\nexport class ReadableStreamAsyncIterable<T> implements AsyncIterable<T> {\n private readonly reader: ReadableStreamDefaultReader<T>;\n private _locked: boolean = true;\n\n /**\n * Creates a new ReadableStreamAsyncIterable instance.\n * @param stream - The ReadableStream to wrap.\n */\n constructor(private readonly stream: ReadableStream<T>) {\n this.reader = stream.getReader();\n }\n\n /**\n * Gets the lock status of the reader.\n * @returns True if the reader is currently locked, false otherwise.\n */\n get locked(): boolean {\n return this._locked;\n }\n\n /**\n * Releases the reader lock if currently locked.\n * This method safely releases the reader lock by catching any potential errors.\n */\n releaseLock() {\n if (!this._locked) return false;\n this._locked = false;\n try {\n this.reader.releaseLock();\n return true;\n } catch (error) {\n console.debug('Failed to release reader lock:', error);\n return false;\n }\n }\n\n /**\n * Implements the AsyncIterable interface by returning this iterator.\n * @returns The async iterator for this instance.\n */\n [Symbol.asyncIterator]() {\n return this;\n }\n\n /**\n * Gets the next value from the stream.\n * Reads the next chunk from the stream and returns it as an IteratorResult.\n * If the stream is done, releases the lock and returns a done result.\n * @returns A promise that resolves to an IteratorResult containing the next value or done status.\n * @throws If an error occurs while reading from the stream.\n */\n async next(): Promise<IteratorResult<T>> {\n try {\n const { done, value } = await this.reader.read();\n if (done) {\n this.releaseLock();\n return { done: true, value: undefined };\n }\n\n return { done: false, value };\n } catch (error) {\n this.releaseLock();\n throw error;\n }\n }\n\n /**\n * Implements the return method of the async iterator.\n * Cancels the stream reader and releases the lock.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async return(): Promise<IteratorResult<T>> {\n try {\n await this.reader.cancel();\n } catch (error) {\n console.debug('Failed to cancel stream reader:', error);\n } finally {\n this.releaseLock();\n }\n return { done: true, value: undefined };\n }\n\n /**\n * Implements the throw method of the async iterator.\n * Releases the lock and returns a done result.\n * @param error - The error to be thrown.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async throw(error: any): Promise<IteratorResult<T>> {\n // Ensure the reader lock is released before throwing\n console.debug('Throwing error:', error);\n this.releaseLock();\n return { done: true, value: undefined };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReadableStreamAsyncIterable } from './readableStreamAsyncIterable';\n\ndeclare global {\n interface ReadableStream<R = any> {\n /**\n * Makes ReadableStream async iterable for use with for-await loops.\n *\n * This allows the stream to be consumed using `for await (const chunk of stream)` syntax.\n *\n * @returns An async iterator for the stream\n */\n [Symbol.asyncIterator](): AsyncIterator<R>;\n }\n}\n\n/**\n * Checks if the current environment natively supports async iteration on ReadableStream.\n *\n * This constant determines whether the browser or runtime already provides\n * built-in support for using ReadableStream with for-await...of loops.\n * If not supported, this library will polyfill the functionality by adding\n * the [Symbol.asyncIterator] method to ReadableStream.prototype.\n *\n * @returns true if native async iteration is supported, false if polyfill is needed\n *\n * @example\n * ```typescript\n * import { isReadableStreamAsyncIterableSupported } from '@ahoo-wang/fetcher-eventstream';\n *\n * if (isReadableStreamAsyncIterableSupported) {\n * console.log('Native support available');\n * } else {\n * console.log('Using polyfill');\n * }\n * ```\n */\nexport const isReadableStreamAsyncIterableSupported =\n typeof ReadableStream.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"],"names":["TextLineTransformer","chunk","controller","lines","line","error","TextLineTransformStream","_ServerSentEventFields","ServerSentEventFields","processFieldInternal","field","value","currentEvent","retryValue","DEFAULT_EVENT_TYPE","ServerSentEventTransformer","colonIndex","enhancedError","ServerSentEventTransformStream","EventStreamConvertError","FetcherError","response","errorMsg","cause","toServerSentEventStream","JsonServerSentEventTransform","terminateDetector","json","JsonServerSentEventTransformStream","toJsonServerSentEventStream","serverSentEventStream","EventStreamResultExtractor","exchange","JsonEventStreamResultExtractor","CONTENT_TYPE_PROPERTY_NAME","CONTENT_TYPE_HEADER","IS_EVENT_STREAM_PROPERTY_NAME","contentType","ContentTypeValues","eventStream","ReadableStreamAsyncIterable","stream","done","isReadableStreamAsyncIterableSupported"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es.js","names":[],"sources":["../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/eventStreamResultExtractor.ts","../src/responses.ts","../src/readableStreamAsyncIterable.ts","../src/readableStreams.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Transformer that splits text into lines.\n *\n * This transformer accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. It handles partial lines that span multiple\n * input chunks by maintaining an internal buffer. Lines are emitted without the newline character.\n *\n * The transformer handles various edge cases:\n * - Lines that span multiple input chunks\n * - Empty lines (emitted as empty strings)\n * - Text without trailing newlines (buffered until stream ends)\n * - Mixed line endings (only '\\n' is recognized as line separator)\n *\n * @implements {Transformer<string, string>}\n *\n * @example\n * ```typescript\n * const transformer = new TextLineTransformer();\n * // Input: \"line1\\nline2\\npartial\"\n * // Output: [\"line1\", \"line2\"]\n * // Buffer: \"partial\"\n *\n * // Later input: \"line\\n\"\n * // Output: [\"partialline\"]\n * // Buffer: \"\"\n * ```\n */\nexport class TextLineTransformer implements Transformer<string, string> {\n private buffer = '';\n\n /**\n * Transform input string chunk by splitting it into lines.\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ) {\n try {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n controller.enqueue(line);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n\n /**\n * Flush remaining buffer when the stream ends.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<string>) {\n try {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n controller.enqueue(this.buffer);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * This class provides a convenient way to transform a stream of text chunks into a stream\n * of individual lines. It wraps the TextLineTransformer in a TransformStream for easy\n * integration with other stream processing pipelines.\n *\n * The stream processes text data and emits each line as a separate chunk, handling\n * lines that may span multiple input chunks automatically.\n *\n * @example\n * ```typescript\n * // Create a line-splitting stream\n * const lineStream = new TextLineTransformStream();\n *\n * // Pipe text through it\n * const lines = textStream.pipeThrough(lineStream);\n *\n * // Process each line\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Process SSE response line by line\n * const response = await fetch('/api/stream');\n * const lines = response.body!\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(new TextLineTransformStream());\n *\n * for await (const line of lines) {\n * if (line.startsWith('data: ')) {\n * console.log('SSE data:', line.substring(6));\n * }\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n /**\n * Creates a new TextLineTransformStream instance.\n *\n * Initializes the stream with a TextLineTransformer that handles the line splitting logic.\n */\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * This interface defines the structure of Server-Sent Events (SSE) as specified by the W3C.\n * Each event contains metadata and data that can be processed by clients to handle real-time\n * updates from the server.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\n/**\n * Constants for Server-Sent Event field names.\n *\n * This class provides string constants for the standard SSE field names as defined\n * in the W3C Server-Sent Events specification. These constants help ensure\n * consistent field name usage throughout the parsing logic.\n */\nexport class ServerSentEventFields {\n /** The field name for event ID */\n static readonly ID = 'id';\n /** The field name for retry interval */\n static readonly RETRY = 'retry';\n /** The field name for event type */\n static readonly EVENT = 'event';\n /** The field name for event data */\n static readonly DATA = 'data';\n}\n\n/**\n * Processes a field-value pair and updates the current event state accordingly.\n *\n * This internal function handles the parsing of individual SSE fields according to the\n * Server-Sent Events specification. It updates the provided event state object with\n * the parsed field values.\n *\n * @param field - The field name (e.g., 'event', 'data', 'id', 'retry')\n * @param value - The field value as a string\n * @param currentEvent - The current event state object to update\n *\n * @example\n * ```typescript\n * const eventState: EventState = { event: 'message', data: [] };\n * processFieldInternal('event', 'custom', eventState);\n * // eventState.event is now 'custom'\n * ```\n */\nfunction processFieldInternal(\n field: string,\n value: string,\n currentEvent: EventState,\n) {\n switch (field) {\n case ServerSentEventFields.EVENT:\n currentEvent.event = value;\n break;\n case ServerSentEventFields.DATA:\n currentEvent.data.push(value);\n break;\n case ServerSentEventFields.ID:\n currentEvent.id = value;\n break;\n case ServerSentEventFields.RETRY: {\n const retryValue = parseInt(value, 10);\n if (!isNaN(retryValue)) {\n currentEvent.retry = retryValue;\n }\n break;\n }\n default:\n // Ignore unknown fields\n break;\n }\n}\n\n/**\n * Internal state representation during Server-Sent Event parsing.\n *\n * This interface tracks the current state of an event being parsed from the SSE stream.\n * It accumulates field values until a complete event is ready to be emitted.\n */\ninterface EventState {\n /** The event type (defaults to 'message') */\n event?: string;\n /** The event ID */\n id?: string;\n /** The retry interval in milliseconds */\n retry?: number;\n /** Array of data lines that will be joined with newlines */\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n * This transformer handles the parsing of Server-Sent Events (SSE) according to the W3C specification.\n * It processes incoming text chunks and converts them into structured ServerSentEvent objects.\n */\nexport class ServerSentEventTransformer\n implements Transformer<string, ServerSentEvent>\n{\n // Initialize currentEventState with default values in a closure\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Reset the current event state to default values.\n * This method is called after processing each complete event or when an error occurs.\n */\n private resetEventState() {\n this.currentEventState.event = DEFAULT_EVENT_TYPE;\n this.currentEventState.id = undefined;\n this.currentEventState.retry = undefined;\n this.currentEventState.data = [];\n }\n\n /**\n * Transform input string chunk into ServerSentEvent object.\n * This method processes individual chunks of text data, parsing them according to the SSE format.\n * It handles:\n * - Empty lines (used as event separators)\n * - Comment lines (starting with ':')\n * - Field lines (field: value format)\n * - Event completion and emission\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ) {\n const currentEvent = this.currentEventState;\n try {\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n // If there is accumulated event data, send event\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n\n // Reset current event (preserve id and retry for subsequent events)\n currentEvent.event = DEFAULT_EVENT_TYPE;\n // Preserve id and retry for subsequent events (no need to reassign to themselves)\n currentEvent.data = [];\n }\n return;\n }\n\n // Ignore comment lines (starting with colon)\n if (chunk.startsWith(':')) {\n return;\n }\n\n // Parse fields\n const colonIndex = chunk.indexOf(':');\n let field: string;\n let value: string;\n\n if (colonIndex === -1) {\n // No colon, entire line as field name, value is empty\n field = chunk.toLowerCase();\n value = '';\n } else {\n // Extract field name and value\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n\n // If value starts with space, remove leading space\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n // Remove trailing newlines from field and value\n field = field.trim();\n value = value.trim();\n\n processFieldInternal(field, value, currentEvent);\n } catch (error) {\n const enhancedError = new Error(\n `Failed to process chunk: \"${chunk}\". ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n // Reset state\n this.resetEventState();\n }\n }\n\n /**\n * Called when the stream ends, used to process remaining data.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<ServerSentEvent>) {\n const currentEvent = this.currentEventState;\n try {\n // Send the last event (if any)\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n }\n } catch (error) {\n const enhancedError = new Error(\n `Failed to flush remaining data. ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n } finally {\n // Reset state\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n *\n * This class provides a convenient way to transform raw text streams containing Server-Sent Events\n * into structured event objects. It wraps the ServerSentEventTransformer in a TransformStream\n * for easy integration with other stream processing pipelines.\n *\n * The stream processes SSE format text and emits ServerSentEvent objects as they are completed.\n * Events are separated by empty lines, and the stream handles partial events across multiple chunks.\n *\n * @example\n * ```typescript\n * // Create a transform stream\n * const sseStream = new ServerSentEventTransformStream();\n *\n * // Pipe a text stream through it\n * const eventStream = textStream.pipeThrough(sseStream);\n *\n * // Consume the events\n * for await (const event of eventStream) {\n * console.log('Event:', event.event, 'Data:', event.data);\n * }\n * ```\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n /**\n * Creates a new ServerSentEventTransformStream instance.\n *\n * Initializes the stream with a ServerSentEventTransformer that handles\n * the parsing of SSE format text into structured events.\n */\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n type ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n *\n * This type represents a stream that yields Server-Sent Event objects as they are parsed\n * from a raw event stream. Each chunk in the stream contains a complete SSE event with\n * its metadata (event type, ID, retry interval) and data.\n *\n * @see {@link ServerSentEvent} for the structure of individual events\n * @see {@link toServerSentEventStream} for converting HTTP responses to this type\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n *\n * This error is thrown when there are issues converting an HTTP Response to a ServerSentEventStream.\n * It extends FetcherError to provide additional context about the failed conversion, including\n * the original Response object and any underlying cause.\n *\n * @extends {FetcherError}\n *\n * @example\n * ```typescript\n * try {\n * const eventStream = toServerSentEventStream(response);\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Failed to convert response to event stream:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n *\n * @param response - The Response object associated with the error, providing context\n * about the failed conversion (status, headers, etc.)\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this conversion error\n */\n constructor(\n public readonly response: Response,\n errorMsg?: string,\n cause?: Error | any,\n ) {\n super(errorMsg, cause);\n this.name = 'EventStreamConvertError';\n // Restore prototype chain for proper inheritance\n Object.setPrototypeOf(this, EventStreamConvertError.prototype);\n }\n}\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * This function takes an HTTP Response object and converts its body into a stream of\n * Server-Sent Event objects. The conversion process involves several transformation steps:\n *\n * 1. **TextDecoderStream**: Decodes the raw Uint8Array response body to UTF-8 strings\n * 2. **TextLineTransformStream**: Splits the text stream into individual lines\n * 3. **ServerSentEventTransformStream**: Parses the line-based SSE format into structured events\n *\n * The resulting stream can be consumed using async iteration or other stream methods.\n *\n * @param response - The HTTP Response object to convert. Must have a readable body stream.\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {EventStreamConvertError} If the response body is null or cannot be processed\n *\n * @example\n * ```typescript\n * // Convert an SSE response to an event stream\n * const response = await fetch('/api/events');\n * const eventStream = toServerSentEventStream(response);\n *\n * // Consume events asynchronously\n * for await (const event of eventStream) {\n * console.log(`Event: ${event.event}, Data: ${event.data}`);\n *\n * // Handle different event types\n * switch (event.event) {\n * case 'message':\n * handleMessage(event.data);\n * break;\n * case 'error':\n * handleError(event.data);\n * break;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Handle conversion errors\n * try {\n * const eventStream = toServerSentEventStream(response);\n * // Use the stream...\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Event stream conversion failed:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new EventStreamConvertError(response, 'Response body is null');\n }\n\n return response.body\n .pipeThrough(new TextDecoderStream('utf-8'))\n .pipeThrough(new TextLineTransformStream())\n .pipeThrough(new ServerSentEventTransformStream());\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type ServerSentEvent } from './serverSentEventTransformStream';\nimport type { ServerSentEventStream } from './eventStreamConverter';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * This detector function is called for each incoming ServerSentEvent. If it returns true,\n * the stream transformation will be terminated, preventing further events from being processed.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n *\n * @example\n * ```typescript\n * const terminateOnDone: TerminateDetector = (event) => {\n * return event.event === 'done' || event.data === '[DONE]';\n * };\n * ```\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\n *\n * This interface extends the base ServerSentEvent but replaces the string 'data' field\n * with a parsed JSON object of the specified generic type. This allows for type-safe\n * access to the event payload.\n *\n * @template DATA - The expected type of the parsed JSON data\n */\nexport interface JsonServerSentEvent<DATA>\n extends Omit<ServerSentEvent, 'data'> {\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>\n implements Transformer<ServerSentEvent, JsonServerSentEvent<DATA>>\n{\n /**\n * Creates a new JsonServerSentEventTransform instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * If provided, this function is called for each event and can terminate\n * the stream by returning true.\n */\n constructor(private readonly terminateDetector?: TerminateDetector) {}\n\n /**\n * Transforms a ServerSentEvent chunk into a JsonServerSentEvent.\n *\n * This method first checks if the event should terminate the stream using the terminateDetector.\n * If termination is required, the controller is terminated. Otherwise, the event data is parsed\n * as JSON and enqueued as a JsonServerSentEvent.\n *\n * If the terminateDetector throws an exception, the stream is terminated with an error to prevent\n * corrupted state.\n *\n * @param chunk - The ServerSentEvent to transform\n * @param controller - The TransformStream controller for managing the stream\n * @throws {SyntaxError} If the event data is not valid JSON\n * @throws {Error} If the terminateDetector throws an exception\n *\n * @example\n * ```typescript\n * const transformer = new JsonServerSentEventTransform<MyData>();\n * // This will be called automatically by the TransformStream\n * ```\n */\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ) {\n try {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n controller.terminate();\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n controller.enqueue({\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n } catch (error) {\n // If terminate detector throws or JSON parsing fails, terminate the stream to prevent corrupted state\n controller.error(error);\n return;\n }\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to JsonServerSentEvent streams with optional termination detection.\n *\n * This class extends TransformStream to provide a convenient way to transform streams of ServerSentEvent\n * objects into streams of JsonServerSentEvent objects. It supports optional termination detection to\n * automatically end the stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransformStream instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * When provided, the stream will automatically terminate when this\n * function returns true for any event.\n *\n * @example\n * ```typescript\n * // Create a stream that terminates on 'done' events\n * const terminateOnDone: TerminateDetector = (event) => event.event === 'done';\n * const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);\n *\n * // Create a stream without termination detection\n * const basicStream = new JsonServerSentEventTransformStream<MyData>();\n * ```\n */\n constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\n *\n * This type represents a stream that yields parsed JSON server-sent events.\n * Each chunk in the stream contains the event metadata along with parsed JSON data.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\n/**\n * Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.\n *\n * This function takes a stream of raw server-sent events and transforms it into a stream of\n * parsed JSON events. It optionally accepts a termination detector to automatically end the\n * stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform\n * @param terminateDetector - Optional function to detect when the stream should be terminated\n * @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data\n * @throws {SyntaxError} If any event data is not valid JSON (thrown during stream consumption)\n *\n * @example\n * ```typescript\n * // Basic usage without termination detection\n * const jsonStream = toJsonServerSentEventStream<MyData>(serverSentEventStream);\n *\n * // With termination detection\n * const terminateOnDone: TerminateDetector = (event) => event.data === '[DONE]';\n * const terminatingStream = toJsonServerSentEventStream<MyData>(\n * serverSentEventStream,\n * terminateOnDone\n * );\n *\n * // Consume the stream\n * for await (const event of jsonStream) {\n * console.log('Received:', event.data);\n * console.log('Event type:', event.event);\n * }\n * ```\n */\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n terminateDetector?: TerminateDetector,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(terminateDetector),\n );\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { FetchExchange, ResultExtractor } from '@ahoo-wang/fetcher';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport type { JsonServerSentEventStream } from './jsonServerSentEventTransformStream';\n\n/**\n * ServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a ServerSentEventStream from an HTTP response that contains Server-Sent Events.\n * The extractor validates that the response supports event streaming and converts the\n * response body into a properly typed event stream.\n *\n * This extractor should be used when you want to consume raw Server-Sent Events\n * without JSON parsing, maintaining the original string data format.\n *\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {ExchangeError} When the server response does not support ServerSentEventStream\n * (e.g., wrong content type, no response body)\n *\n *\n * @see {@link ServerSentEventStream} for the stream type\n * @see {@link JsonEventStreamResultExtractor} for JSON-parsed event streams\n */\nexport const EventStreamResultExtractor: ResultExtractor<\n ServerSentEventStream\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredEventStream();\n};\n\n/**\n * JsonServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a JsonServerSentEventStream from an HTTP response that contains Server-Sent Events\n * with JSON data. The extractor validates that the response supports event streaming and converts\n * the response body into a properly typed event stream with automatic JSON parsing.\n *\n * This extractor should be used when you want to consume Server-Sent Events where the event\n * data is JSON-formatted, providing type-safe access to parsed JSON objects instead of raw strings.\n *\n * @template DATA - The expected type of the JSON data in the server-sent events\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects with parsed JSON data as they are received\n * @throws {ExchangeError} When the server response does not support JsonServerSentEventStream\n * (e.g., wrong content type, no response body, invalid JSON)\n *\n *\n * @see {@link JsonServerSentEventStream} for the stream type with JSON data\n * @see {@link EventStreamResultExtractor} for raw string event streams\n */\nexport const JsonEventStreamResultExtractor: ResultExtractor<\n JsonServerSentEventStream<any>\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredJsonEventStream();\n};\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EventStreamConvertError,\n type ServerSentEventStream,\n toServerSentEventStream,\n} from './eventStreamConverter';\nimport {\n type JsonServerSentEventStream,\n type TerminateDetector,\n toJsonServerSentEventStream,\n} from './jsonServerSentEventTransformStream';\nimport { CONTENT_TYPE_HEADER, ContentTypeValues } from '@ahoo-wang/fetcher';\n\ndeclare global {\n interface Response {\n /**\n * Gets the content type of the response.\n *\n * This property provides access to the Content-Type header of the response,\n * which indicates the media type of the resource transmitted in the response.\n *\n * @returns The content type header value as a string, or null if the header is not set\n */\n get contentType(): string | null;\n\n /**\n * Checks if the response is an event stream.\n *\n * This property examines the Content-Type header to determine if the response\n * contains server-sent events data (text/event-stream).\n *\n * @returns true if the response is an event stream, false otherwise\n */\n get isEventStream(): boolean;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects, or null if not an event stream\n */\n eventStream(): ServerSentEventStream | null;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is similar to eventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws {Error} if the event stream is not available\n */\n requiredEventStream(): ServerSentEventStream;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data, or null if not an event stream\n */\n jsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA> | null;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is similar to jsonEventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream with JSON data.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data\n * @throws {Error} if the event stream is not available\n */\n requiredJsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA>;\n }\n}\n\nconst 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 */\nif (\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\nconst 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 */\nif (\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 */\nif (!Object.prototype.hasOwnProperty.call(Response.prototype, 'eventStream')) {\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 */\nif (\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 */\nif (\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 */\nif (\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 * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper class that converts a ReadableStream into an AsyncIterable.\n *\n * This class enables the use of ReadableStream objects with async iteration syntax\n * (for-await...of loops), providing a more ergonomic way to consume streaming data.\n * It implements the AsyncIterable interface and manages the underlying stream reader,\n * handling proper resource cleanup and error propagation.\n *\n * The wrapper automatically handles stream locking, ensuring that only one consumer\n * can read from the stream at a time, and provides safe cleanup when iteration ends\n * or errors occur.\n *\n * @template T - The type of data yielded by the stream\n *\n * @example\n * ```typescript\n * // Direct usage\n * const response = await fetch('/api/stream');\n * const stream = response.body;\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * console.log('Received:', chunk);\n * }\n * // Stream is automatically cleaned up after iteration\n * ```\n *\n * @example\n * ```typescript\n * // With early termination\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * if (someCondition) {\n * asyncIterable.releaseLock(); // Manually release if needed\n * break;\n * }\n * }\n * ```\n */\nexport class ReadableStreamAsyncIterable<T> implements AsyncIterable<T> {\n private readonly reader: ReadableStreamDefaultReader<T>;\n private _locked: boolean = true;\n\n /**\n * Creates a new ReadableStreamAsyncIterable instance.\n * @param stream - The ReadableStream to wrap.\n */\n constructor(private readonly stream: ReadableStream<T>) {\n this.reader = stream.getReader();\n }\n\n /**\n * Gets the lock status of the reader.\n * @returns True if the reader is currently locked, false otherwise.\n */\n get locked(): boolean {\n return this._locked;\n }\n\n /**\n * Releases the reader lock if currently locked.\n * This method safely releases the reader lock by catching any potential errors.\n */\n releaseLock() {\n if (!this._locked) return false;\n this._locked = false;\n try {\n this.reader.releaseLock();\n return true;\n } catch (error) {\n console.debug('Failed to release reader lock:', error);\n return false;\n }\n }\n\n /**\n * Implements the AsyncIterable interface by returning this iterator.\n * @returns The async iterator for this instance.\n */\n [Symbol.asyncIterator]() {\n return this;\n }\n\n /**\n * Gets the next value from the stream.\n * Reads the next chunk from the stream and returns it as an IteratorResult.\n * If the stream is done, releases the lock and returns a done result.\n * @returns A promise that resolves to an IteratorResult containing the next value or done status.\n * @throws If an error occurs while reading from the stream.\n */\n async next(): Promise<IteratorResult<T>> {\n try {\n const { done, value } = await this.reader.read();\n if (done) {\n this.releaseLock();\n return { done: true, value: undefined };\n }\n\n return { done: false, value };\n } catch (error) {\n this.releaseLock();\n throw error;\n }\n }\n\n /**\n * Implements the return method of the async iterator.\n * Cancels the stream reader and releases the lock.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async return(): Promise<IteratorResult<T>> {\n try {\n await this.reader.cancel();\n } catch (error) {\n console.debug('Failed to cancel stream reader:', error);\n } finally {\n this.releaseLock();\n }\n return { done: true, value: undefined };\n }\n\n /**\n * Implements the throw method of the async iterator.\n * Releases the lock and returns a done result.\n * @param error - The error to be thrown.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async throw(error: any): Promise<IteratorResult<T>> {\n // Ensure the reader lock is released before throwing\n console.debug('Throwing error:', error);\n this.releaseLock();\n return { done: true, value: undefined };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReadableStreamAsyncIterable } from './readableStreamAsyncIterable';\n\ndeclare global {\n interface ReadableStream<R = any> {\n /**\n * Makes ReadableStream async iterable for use with for-await loops.\n *\n * This allows the stream to be consumed using `for await (const chunk of stream)` syntax.\n *\n * @returns An async iterator for the stream\n */\n [Symbol.asyncIterator](): AsyncIterator<R>;\n }\n}\n\n/**\n * Checks if the current environment natively supports async iteration on ReadableStream.\n *\n * This constant determines whether the browser or runtime already provides\n * built-in support for using ReadableStream with for-await...of loops.\n * If not supported, this library will polyfill the functionality by adding\n * the [Symbol.asyncIterator] method to ReadableStream.prototype.\n *\n * @returns true if native async iteration is supported, false if polyfill is needed\n *\n * @example\n * ```typescript\n * import { isReadableStreamAsyncIterableSupported } from '@ahoo-wang/fetcher-eventstream';\n *\n * if (isReadableStreamAsyncIterableSupported) {\n * console.log('Native support available');\n * } else {\n * console.log('Using polyfill');\n * }\n * ```\n */\nexport const isReadableStreamAsyncIterableSupported =\n typeof ReadableStream.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":";;AAwCA,IAAa,IAAb,MAAwE;;gBACrD;;CAQjB,UACE,GACA,GACA;AACA,MAAI;AACF,QAAK,UAAU;GACf,IAAM,IAAQ,KAAK,OAAO,MAAM,KAAK;AACrC,QAAK,SAAS,EAAM,KAAK,IAAI;AAE7B,QAAK,IAAM,KAAQ,EACjB,GAAW,QAAQ,EAAK;WAEnB,GAAO;AACd,KAAW,MAAM,EAAM;;;CAS3B,MAAM,GAAsD;AAC1D,MAAI;AAEF,GAAI,KAAK,UACP,EAAW,QAAQ,KAAK,OAAO;WAE1B,GAAO;AACd,KAAW,MAAM,EAAM;;;GA4ChB,IAAb,cAA6C,gBAAgC;CAM3E,cAAc;AACZ,QAAM,IAAI,GAAqB,CAAC;;GCzFvB,IAAb,MAAmC;;YAEZ;;;eAEG;;;eAEA;;;cAED;;;AAqBzB,SAAS,EACP,GACA,GACA,GACA;AACA,SAAQ,GAAR;EACE,KAAK,EAAsB;AACzB,KAAa,QAAQ;AACrB;EACF,KAAK,EAAsB;AACzB,KAAa,KAAK,KAAK,EAAM;AAC7B;EACF,KAAK,EAAsB;AACzB,KAAa,KAAK;AAClB;EACF,KAAK,EAAsB,OAAO;GAChC,IAAM,IAAa,SAAS,GAAO,GAAG;AACtC,GAAK,MAAM,EAAW,KACpB,EAAa,QAAQ;AAEvB;;EAEF,QAEE;;;AAqBN,IAAM,IAAqB,WASd,IAAb,MAEA;;2BAE0C;GACtC,OAAO;GACP,IAAI,KAAA;GACJ,OAAO,KAAA;GACP,MAAM,EAAE;GACT;;CAMD,kBAA0B;AAIxB,EAHA,KAAK,kBAAkB,QAAQ,GAC/B,KAAK,kBAAkB,KAAK,KAAA,GAC5B,KAAK,kBAAkB,QAAQ,KAAA,GAC/B,KAAK,kBAAkB,OAAO,EAAE;;CAelC,UACE,GACA,GACA;EACA,IAAM,IAAe,KAAK;AAC1B,MAAI;AAEF,OAAI,EAAM,MAAM,KAAK,IAAI;AAEvB,IAAI,EAAa,KAAK,SAAS,MAC7B,EAAW,QAAQ;KACjB,OAAO,EAAa,SAAS;KAC7B,MAAM,EAAa,KAAK,KAAK,KAAK;KAClC,IAAI,EAAa,MAAM;KACvB,OAAO,EAAa;KACrB,CAAoB,EAGrB,EAAa,QAAQ,GAErB,EAAa,OAAO,EAAE;AAExB;;AAIF,OAAI,EAAM,WAAW,IAAI,CACvB;GAIF,IAAM,IAAa,EAAM,QAAQ,IAAI,EACjC,GACA;AAqBJ,GAnBI,MAAe,MAEjB,IAAQ,EAAM,aAAa,EAC3B,IAAQ,OAGR,IAAQ,EAAM,UAAU,GAAG,EAAW,CAAC,aAAa,EACpD,IAAQ,EAAM,UAAU,IAAa,EAAE,EAGnC,EAAM,WAAW,IAAI,KACvB,IAAQ,EAAM,UAAU,EAAE,IAK9B,IAAQ,EAAM,MAAM,EACpB,IAAQ,EAAM,MAAM,EAEpB,EAAqB,GAAO,GAAO,EAAa;WACzC,GAAO;GACd,IAAM,IAAgB,gBAAI,MACxB,6BAA6B,EAAM,KAAK,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM,GAC/F;AAGD,GAFA,EAAW,MAAM,EAAc,EAE/B,KAAK,iBAAiB;;;CAS1B,MAAM,GAA+D;EACnE,IAAM,IAAe,KAAK;AAC1B,MAAI;AAEF,GAAI,EAAa,KAAK,SAAS,KAC7B,EAAW,QAAQ;IACjB,OAAO,EAAa,SAAS;IAC7B,MAAM,EAAa,KAAK,KAAK,KAAK;IAClC,IAAI,EAAa,MAAM;IACvB,OAAO,EAAa;IACrB,CAAoB;WAEhB,GAAO;GACd,IAAM,IAAgB,gBAAI,MACxB,mCAAmC,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM,GAC1F;AACD,KAAW,MAAM,EAAc;YACvB;AAER,QAAK,iBAAiB;;;GA6Bf,IAAb,cAAoD,gBAGlD;CAOA,cAAc;AACZ,QAAM,IAAI,GAA4B,CAAC;;GCzO9B,IAAb,MAAa,UAAgC,EAAa;CASxD,YACE,GACA,GACA,GACA;AAIA,EAHA,MAAM,GAAU,EAAM,EAJN,KAAA,WAAA,GAKhB,KAAK,OAAO,2BAEZ,OAAO,eAAe,MAAM,EAAwB,UAAU;;;AAwDlE,SAAgB,EACd,GACuB;AACvB,KAAI,CAAC,EAAS,KACZ,OAAM,IAAI,EAAwB,GAAU,wBAAwB;AAGtE,QAAO,EAAS,KACb,YAAY,IAAI,kBAAkB,QAAQ,CAAC,CAC3C,YAAY,IAAI,GAAyB,CAAC,CAC1C,YAAY,IAAI,GAAgC,CAAC;;;;AC9EtD,IAAa,IAAb,MAEA;CAQE,YAAY,GAAwD;AAAvC,OAAA,oBAAA;;CAuB7B,UACE,GACA,GACA;AACA,MAAI;AAEF,OAAI,KAAK,oBAAoB,EAAM,EAAE;AACnC,MAAW,WAAW;AACtB;;GAGF,IAAM,IAAO,KAAK,MAAM,EAAM,KAAK;AACnC,KAAW,QAAQ;IACjB,MAAM;IACN,OAAO,EAAM;IACb,IAAI,EAAM;IACV,OAAO,EAAM;IACd,CAAC;WACK,GAAO;AAEd,KAAW,MAAM,EAAM;AACvB;;;GAcO,IAAb,cAA8D,gBAG5D;CAkBA,YAAY,GAAuC;AACjD,QAAM,IAAI,EAA6B,EAAkB,CAAC;;;AAgD9D,SAAgB,EACd,GACA,GACiC;AACjC,QAAO,EAAsB,YAC3B,IAAI,EAAyC,EAAkB,CAChE;;;;ACrKH,IAAa,KAER,MACI,EAAS,iBAAiB,qBAAqB,EAwB3C,KAER,MACI,EAAS,iBAAiB,yBAAyB,ECkCtD,IAA6B;AAMhC,OAAO,UAAU,eAAe,KAC/B,SAAS,WACT,EACD,IAED,OAAO,eAAe,SAAS,WAAW,GAA4B;CACpE,MAAM;AACJ,SAAO,KAAK,QAAQ,IAAI,EAAoB;;CAE9C,cAAc;CACf,CAAC;AAGJ,IAAM,IAAgC;AAMnC,OAAO,UAAU,eAAe,KAC/B,SAAS,WACT,EACD,IAED,OAAO,eAAe,SAAS,WAAW,GAA+B;CACvE,MAAM;EACJ,IAAM,IAAc,KAAK;AAIzB,SAHK,IAGE,EAAY,SAAS,EAAkB,kBAAkB,GAFvD;;CAIX,cAAc;CACf,CAAC,EASC,OAAO,UAAU,eAAe,KAAK,SAAS,WAAW,cAAc,KAC1E,SAAS,UAAU,cAAc,WAAY;AAI3C,QAHK,KAAK,gBAGH,EAAwB,KAAK,GAF3B;IAeV,OAAO,UAAU,eAAe,KAC/B,SAAS,WACT,sBACD,KAED,SAAS,UAAU,sBAAsB,WAAY;CACnD,IAAM,IAAc,KAAK,aAAa;AACtC,KAAI,CAAC,EACH,OAAM,IAAI,EACR,MACA,0DAA0D,KAAK,YAAY,GAC5E;AAEH,QAAO;IAaR,OAAO,UAAU,eAAe,KAAK,SAAS,WAAW,kBAAkB,KAE5E,SAAS,UAAU,kBAAkB,SACnC,GACA;CACA,IAAM,IAAc,KAAK,aAAa;AAItC,QAHK,IAGE,EAAkC,GAAa,EAAkB,GAF/D;IAiBV,OAAO,UAAU,eAAe,KAC/B,SAAS,WACT,0BACD,KAED,SAAS,UAAU,0BAA0B,SAC3C,GACA;CACA,IAAM,IAAc,KAAK,gBAAsB,EAAkB;AACjE,KAAI,CAAC,EACH,OAAM,IAAI,EACR,MACA,0DAA0D,KAAK,YAAY,GAC5E;AAEH,QAAO;;;;ACnLX,IAAa,IAAb,MAAwE;CAQtE,YAAY,GAA4C;AACtD,EAD2B,KAAA,SAAA,kBANF,IAOzB,KAAK,SAAS,EAAO,WAAW;;CAOlC,IAAI,SAAkB;AACpB,SAAO,KAAK;;CAOd,cAAc;AACZ,MAAI,CAAC,KAAK,QAAS,QAAO;AAC1B,OAAK,UAAU;AACf,MAAI;AAEF,UADA,KAAK,OAAO,aAAa,EAClB;WACA,GAAO;AAEd,UADA,QAAQ,MAAM,kCAAkC,EAAM,EAC/C;;;CAQX,CAAC,OAAO,iBAAiB;AACvB,SAAO;;CAUT,MAAM,OAAmC;AACvC,MAAI;GACF,IAAM,EAAE,SAAM,aAAU,MAAM,KAAK,OAAO,MAAM;AAMhD,UALI,KACF,KAAK,aAAa,EACX;IAAE,MAAM;IAAM,OAAO,KAAA;IAAW,IAGlC;IAAE,MAAM;IAAO;IAAO;WACtB,GAAO;AAEd,SADA,KAAK,aAAa,EACZ;;;CASV,MAAM,SAAqC;AACzC,MAAI;AACF,SAAM,KAAK,OAAO,QAAQ;WACnB,GAAO;AACd,WAAQ,MAAM,mCAAmC,EAAM;YAC/C;AACR,QAAK,aAAa;;AAEpB,SAAO;GAAE,MAAM;GAAM,OAAO,KAAA;GAAW;;CASzC,MAAM,MAAM,GAAwC;AAIlD,SAFA,QAAQ,MAAM,mBAAmB,EAAM,EACvC,KAAK,aAAa,EACX;GAAE,MAAM;GAAM,OAAO,KAAA;GAAW;;GChG9B,IACX,OAAO,eAAe,UAAU,OAAO,kBAAmB;AAGvD,MACH,eAAe,UAAU,OAAO,iBAAiB,WAAqB;AACpE,QAAO,IAAI,EAA+B,KAA0B"}
|
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
(function(
|
|
2
|
-
`);this.buffer=n.pop()
|
|
3
|
-
`),id:n.id
|
|
4
|
-
`),id:
|
|
5
|
-
//# sourceMappingURL=index.umd.js.map
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`@ahoo-wang/fetcher`)):typeof define==`function`&&define.amd?define([`exports`,`@ahoo-wang/fetcher`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.FetcherEventStream={},e.Fetcher))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=class{constructor(){this.buffer=``}transform(e,t){try{this.buffer+=e;let n=this.buffer.split(`
|
|
2
|
+
`);this.buffer=n.pop()||``;for(let e of n)t.enqueue(e)}catch(e){t.error(e)}}flush(e){try{this.buffer&&e.enqueue(this.buffer)}catch(t){e.error(t)}}},r=class extends TransformStream{constructor(){super(new n)}},i=class{static{this.ID=`id`}static{this.RETRY=`retry`}static{this.EVENT=`event`}static{this.DATA=`data`}};function a(e,t,n){switch(e){case i.EVENT:n.event=t;break;case i.DATA:n.data.push(t);break;case i.ID:n.id=t;break;case i.RETRY:{let e=parseInt(t,10);isNaN(e)||(n.retry=e);break}default:break}}var o=`message`,s=class{constructor(){this.currentEventState={event:o,id:void 0,retry:void 0,data:[]}}resetEventState(){this.currentEventState.event=o,this.currentEventState.id=void 0,this.currentEventState.retry=void 0,this.currentEventState.data=[]}transform(e,t){let n=this.currentEventState;try{if(e.trim()===``){n.data.length>0&&(t.enqueue({event:n.event||o,data:n.data.join(`
|
|
3
|
+
`),id:n.id||``,retry:n.retry}),n.event=o,n.data=[]);return}if(e.startsWith(`:`))return;let r=e.indexOf(`:`),i,s;r===-1?(i=e.toLowerCase(),s=``):(i=e.substring(0,r).toLowerCase(),s=e.substring(r+1),s.startsWith(` `)&&(s=s.substring(1))),i=i.trim(),s=s.trim(),a(i,s,n)}catch(n){let r=Error(`Failed to process chunk: "${e}". ${n instanceof Error?n.message:String(n)}`);t.error(r),this.resetEventState()}}flush(e){let t=this.currentEventState;try{t.data.length>0&&e.enqueue({event:t.event||o,data:t.data.join(`
|
|
4
|
+
`),id:t.id||``,retry:t.retry})}catch(t){let n=Error(`Failed to flush remaining data. ${t instanceof Error?t.message:String(t)}`);e.error(n)}finally{this.resetEventState()}}},c=class extends TransformStream{constructor(){super(new s)}},l=class e extends t.FetcherError{constructor(t,n,r){super(n,r),this.response=t,this.name=`EventStreamConvertError`,Object.setPrototypeOf(this,e.prototype)}};function u(e){if(!e.body)throw new l(e,`Response body is null`);return e.body.pipeThrough(new TextDecoderStream(`utf-8`)).pipeThrough(new r).pipeThrough(new c)}var d=class{constructor(e){this.terminateDetector=e}transform(e,t){try{if(this.terminateDetector?.(e)){t.terminate();return}let n=JSON.parse(e.data);t.enqueue({data:n,event:e.event,id:e.id,retry:e.retry})}catch(e){t.error(e);return}}},f=class extends TransformStream{constructor(e){super(new d(e))}};function p(e,t){return e.pipeThrough(new f(t))}var m=e=>e.requiredResponse.requiredEventStream(),h=e=>e.requiredResponse.requiredJsonEventStream(),g=`contentType`;Object.prototype.hasOwnProperty.call(Response.prototype,g)||Object.defineProperty(Response.prototype,g,{get(){return this.headers.get(t.CONTENT_TYPE_HEADER)},configurable:!0});var _=`isEventStream`;Object.prototype.hasOwnProperty.call(Response.prototype,_)||Object.defineProperty(Response.prototype,_,{get(){let e=this.contentType;return e?e.includes(t.ContentTypeValues.TEXT_EVENT_STREAM):!1},configurable:!0}),Object.prototype.hasOwnProperty.call(Response.prototype,`eventStream`)||(Response.prototype.eventStream=function(){return this.isEventStream?u(this):null}),Object.prototype.hasOwnProperty.call(Response.prototype,`requiredEventStream`)||(Response.prototype.requiredEventStream=function(){let e=this.eventStream();if(!e)throw new l(this,`Event stream is not available. Response content-type: [${this.contentType}]`);return e}),Object.prototype.hasOwnProperty.call(Response.prototype,`jsonEventStream`)||(Response.prototype.jsonEventStream=function(e){let t=this.eventStream();return t?p(t,e):null}),Object.prototype.hasOwnProperty.call(Response.prototype,`requiredJsonEventStream`)||(Response.prototype.requiredJsonEventStream=function(e){let t=this.jsonEventStream(e);if(!t)throw new l(this,`Event stream is not available. Response content-type: [${this.contentType}]`);return t});var v=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}}},y=typeof ReadableStream.prototype[Symbol.asyncIterator]==`function`;y||(ReadableStream.prototype[Symbol.asyncIterator]=function(){return new v(this)}),e.EventStreamConvertError=l,e.EventStreamResultExtractor=m,e.JsonEventStreamResultExtractor=h,e.JsonServerSentEventTransform=d,e.JsonServerSentEventTransformStream=f,e.ReadableStreamAsyncIterable=v,e.ServerSentEventFields=i,e.ServerSentEventTransformStream=c,e.ServerSentEventTransformer=s,e.TextLineTransformStream=r,e.TextLineTransformer=n,e.isReadableStreamAsyncIterableSupported=y,e.toJsonServerSentEventStream=p,e.toServerSentEventStream=u});
|
|
5
|
+
//# sourceMappingURL=index.umd.js.map
|
package/dist/index.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.umd.js","sources":["../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/eventStreamResultExtractor.ts","../src/responses.ts","../src/readableStreamAsyncIterable.ts","../src/readableStreams.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Transformer that splits text into lines.\n *\n * This transformer accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. It handles partial lines that span multiple\n * input chunks by maintaining an internal buffer. Lines are emitted without the newline character.\n *\n * The transformer handles various edge cases:\n * - Lines that span multiple input chunks\n * - Empty lines (emitted as empty strings)\n * - Text without trailing newlines (buffered until stream ends)\n * - Mixed line endings (only '\\n' is recognized as line separator)\n *\n * @implements {Transformer<string, string>}\n *\n * @example\n * ```typescript\n * const transformer = new TextLineTransformer();\n * // Input: \"line1\\nline2\\npartial\"\n * // Output: [\"line1\", \"line2\"]\n * // Buffer: \"partial\"\n *\n * // Later input: \"line\\n\"\n * // Output: [\"partialline\"]\n * // Buffer: \"\"\n * ```\n */\nexport class TextLineTransformer implements Transformer<string, string> {\n private buffer = '';\n\n /**\n * Transform input string chunk by splitting it into lines.\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ) {\n try {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n controller.enqueue(line);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n\n /**\n * Flush remaining buffer when the stream ends.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<string>) {\n try {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n controller.enqueue(this.buffer);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * This class provides a convenient way to transform a stream of text chunks into a stream\n * of individual lines. It wraps the TextLineTransformer in a TransformStream for easy\n * integration with other stream processing pipelines.\n *\n * The stream processes text data and emits each line as a separate chunk, handling\n * lines that may span multiple input chunks automatically.\n *\n * @example\n * ```typescript\n * // Create a line-splitting stream\n * const lineStream = new TextLineTransformStream();\n *\n * // Pipe text through it\n * const lines = textStream.pipeThrough(lineStream);\n *\n * // Process each line\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Process SSE response line by line\n * const response = await fetch('/api/stream');\n * const lines = response.body!\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(new TextLineTransformStream());\n *\n * for await (const line of lines) {\n * if (line.startsWith('data: ')) {\n * console.log('SSE data:', line.substring(6));\n * }\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n /**\n * Creates a new TextLineTransformStream instance.\n *\n * Initializes the stream with a TextLineTransformer that handles the line splitting logic.\n */\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * This interface defines the structure of Server-Sent Events (SSE) as specified by the W3C.\n * Each event contains metadata and data that can be processed by clients to handle real-time\n * updates from the server.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\n/**\n * Constants for Server-Sent Event field names.\n *\n * This class provides string constants for the standard SSE field names as defined\n * in the W3C Server-Sent Events specification. These constants help ensure\n * consistent field name usage throughout the parsing logic.\n */\nexport class ServerSentEventFields {\n /** The field name for event ID */\n static readonly ID = 'id';\n /** The field name for retry interval */\n static readonly RETRY = 'retry';\n /** The field name for event type */\n static readonly EVENT = 'event';\n /** The field name for event data */\n static readonly DATA = 'data';\n}\n\n/**\n * Processes a field-value pair and updates the current event state accordingly.\n *\n * This internal function handles the parsing of individual SSE fields according to the\n * Server-Sent Events specification. It updates the provided event state object with\n * the parsed field values.\n *\n * @param field - The field name (e.g., 'event', 'data', 'id', 'retry')\n * @param value - The field value as a string\n * @param currentEvent - The current event state object to update\n *\n * @example\n * ```typescript\n * const eventState: EventState = { event: 'message', data: [] };\n * processFieldInternal('event', 'custom', eventState);\n * // eventState.event is now 'custom'\n * ```\n */\nfunction processFieldInternal(\n field: string,\n value: string,\n currentEvent: EventState,\n) {\n switch (field) {\n case ServerSentEventFields.EVENT:\n currentEvent.event = value;\n break;\n case ServerSentEventFields.DATA:\n currentEvent.data.push(value);\n break;\n case ServerSentEventFields.ID:\n currentEvent.id = value;\n break;\n case ServerSentEventFields.RETRY: {\n const retryValue = parseInt(value, 10);\n if (!isNaN(retryValue)) {\n currentEvent.retry = retryValue;\n }\n break;\n }\n default:\n // Ignore unknown fields\n break;\n }\n}\n\n/**\n * Internal state representation during Server-Sent Event parsing.\n *\n * This interface tracks the current state of an event being parsed from the SSE stream.\n * It accumulates field values until a complete event is ready to be emitted.\n */\ninterface EventState {\n /** The event type (defaults to 'message') */\n event?: string;\n /** The event ID */\n id?: string;\n /** The retry interval in milliseconds */\n retry?: number;\n /** Array of data lines that will be joined with newlines */\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n * This transformer handles the parsing of Server-Sent Events (SSE) according to the W3C specification.\n * It processes incoming text chunks and converts them into structured ServerSentEvent objects.\n */\nexport class ServerSentEventTransformer\n implements Transformer<string, ServerSentEvent>\n{\n // Initialize currentEventState with default values in a closure\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Reset the current event state to default values.\n * This method is called after processing each complete event or when an error occurs.\n */\n private resetEventState() {\n this.currentEventState.event = DEFAULT_EVENT_TYPE;\n this.currentEventState.id = undefined;\n this.currentEventState.retry = undefined;\n this.currentEventState.data = [];\n }\n\n /**\n * Transform input string chunk into ServerSentEvent object.\n * This method processes individual chunks of text data, parsing them according to the SSE format.\n * It handles:\n * - Empty lines (used as event separators)\n * - Comment lines (starting with ':')\n * - Field lines (field: value format)\n * - Event completion and emission\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ) {\n const currentEvent = this.currentEventState;\n try {\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n // If there is accumulated event data, send event\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n\n // Reset current event (preserve id and retry for subsequent events)\n currentEvent.event = DEFAULT_EVENT_TYPE;\n // Preserve id and retry for subsequent events (no need to reassign to themselves)\n currentEvent.data = [];\n }\n return;\n }\n\n // Ignore comment lines (starting with colon)\n if (chunk.startsWith(':')) {\n return;\n }\n\n // Parse fields\n const colonIndex = chunk.indexOf(':');\n let field: string;\n let value: string;\n\n if (colonIndex === -1) {\n // No colon, entire line as field name, value is empty\n field = chunk.toLowerCase();\n value = '';\n } else {\n // Extract field name and value\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n\n // If value starts with space, remove leading space\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n // Remove trailing newlines from field and value\n field = field.trim();\n value = value.trim();\n\n processFieldInternal(field, value, currentEvent);\n } catch (error) {\n const enhancedError = new Error(\n `Failed to process chunk: \"${chunk}\". ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n // Reset state\n this.resetEventState();\n }\n }\n\n /**\n * Called when the stream ends, used to process remaining data.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<ServerSentEvent>) {\n const currentEvent = this.currentEventState;\n try {\n // Send the last event (if any)\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n }\n } catch (error) {\n const enhancedError = new Error(\n `Failed to flush remaining data. ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n } finally {\n // Reset state\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n *\n * This class provides a convenient way to transform raw text streams containing Server-Sent Events\n * into structured event objects. It wraps the ServerSentEventTransformer in a TransformStream\n * for easy integration with other stream processing pipelines.\n *\n * The stream processes SSE format text and emits ServerSentEvent objects as they are completed.\n * Events are separated by empty lines, and the stream handles partial events across multiple chunks.\n *\n * @example\n * ```typescript\n * // Create a transform stream\n * const sseStream = new ServerSentEventTransformStream();\n *\n * // Pipe a text stream through it\n * const eventStream = textStream.pipeThrough(sseStream);\n *\n * // Consume the events\n * for await (const event of eventStream) {\n * console.log('Event:', event.event, 'Data:', event.data);\n * }\n * ```\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n /**\n * Creates a new ServerSentEventTransformStream instance.\n *\n * Initializes the stream with a ServerSentEventTransformer that handles\n * the parsing of SSE format text into structured events.\n */\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n type ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n *\n * This type represents a stream that yields Server-Sent Event objects as they are parsed\n * from a raw event stream. Each chunk in the stream contains a complete SSE event with\n * its metadata (event type, ID, retry interval) and data.\n *\n * @see {@link ServerSentEvent} for the structure of individual events\n * @see {@link toServerSentEventStream} for converting HTTP responses to this type\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n *\n * This error is thrown when there are issues converting an HTTP Response to a ServerSentEventStream.\n * It extends FetcherError to provide additional context about the failed conversion, including\n * the original Response object and any underlying cause.\n *\n * @extends {FetcherError}\n *\n * @example\n * ```typescript\n * try {\n * const eventStream = toServerSentEventStream(response);\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Failed to convert response to event stream:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n *\n * @param response - The Response object associated with the error, providing context\n * about the failed conversion (status, headers, etc.)\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this conversion error\n */\n constructor(\n public readonly response: Response,\n errorMsg?: string,\n cause?: Error | any,\n ) {\n super(errorMsg, cause);\n this.name = 'EventStreamConvertError';\n // Restore prototype chain for proper inheritance\n Object.setPrototypeOf(this, EventStreamConvertError.prototype);\n }\n}\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * This function takes an HTTP Response object and converts its body into a stream of\n * Server-Sent Event objects. The conversion process involves several transformation steps:\n *\n * 1. **TextDecoderStream**: Decodes the raw Uint8Array response body to UTF-8 strings\n * 2. **TextLineTransformStream**: Splits the text stream into individual lines\n * 3. **ServerSentEventTransformStream**: Parses the line-based SSE format into structured events\n *\n * The resulting stream can be consumed using async iteration or other stream methods.\n *\n * @param response - The HTTP Response object to convert. Must have a readable body stream.\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {EventStreamConvertError} If the response body is null or cannot be processed\n *\n * @example\n * ```typescript\n * // Convert an SSE response to an event stream\n * const response = await fetch('/api/events');\n * const eventStream = toServerSentEventStream(response);\n *\n * // Consume events asynchronously\n * for await (const event of eventStream) {\n * console.log(`Event: ${event.event}, Data: ${event.data}`);\n *\n * // Handle different event types\n * switch (event.event) {\n * case 'message':\n * handleMessage(event.data);\n * break;\n * case 'error':\n * handleError(event.data);\n * break;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Handle conversion errors\n * try {\n * const eventStream = toServerSentEventStream(response);\n * // Use the stream...\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Event stream conversion failed:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new EventStreamConvertError(response, 'Response body is null');\n }\n\n return response.body\n .pipeThrough(new TextDecoderStream('utf-8'))\n .pipeThrough(new TextLineTransformStream())\n .pipeThrough(new ServerSentEventTransformStream());\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type ServerSentEvent } from './serverSentEventTransformStream';\nimport { ServerSentEventStream } from './eventStreamConverter';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * This detector function is called for each incoming ServerSentEvent. If it returns true,\n * the stream transformation will be terminated, preventing further events from being processed.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n *\n * @example\n * ```typescript\n * const terminateOnDone: TerminateDetector = (event) => {\n * return event.event === 'done' || event.data === '[DONE]';\n * };\n * ```\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\n *\n * This interface extends the base ServerSentEvent but replaces the string 'data' field\n * with a parsed JSON object of the specified generic type. This allows for type-safe\n * access to the event payload.\n *\n * @template DATA - The expected type of the parsed JSON data\n */\nexport interface JsonServerSentEvent<DATA>\n extends Omit<ServerSentEvent, 'data'> {\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>\n implements Transformer<ServerSentEvent, JsonServerSentEvent<DATA>>\n{\n /**\n * Creates a new JsonServerSentEventTransform instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * If provided, this function is called for each event and can terminate\n * the stream by returning true.\n */\n constructor(private readonly terminateDetector?: TerminateDetector) {}\n\n /**\n * Transforms a ServerSentEvent chunk into a JsonServerSentEvent.\n *\n * This method first checks if the event should terminate the stream using the terminateDetector.\n * If termination is required, the controller is terminated. Otherwise, the event data is parsed\n * as JSON and enqueued as a JsonServerSentEvent.\n *\n * If the terminateDetector throws an exception, the stream is terminated with an error to prevent\n * corrupted state.\n *\n * @param chunk - The ServerSentEvent to transform\n * @param controller - The TransformStream controller for managing the stream\n * @throws {SyntaxError} If the event data is not valid JSON\n * @throws {Error} If the terminateDetector throws an exception\n *\n * @example\n * ```typescript\n * const transformer = new JsonServerSentEventTransform<MyData>();\n * // This will be called automatically by the TransformStream\n * ```\n */\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ) {\n try {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n controller.terminate();\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n controller.enqueue({\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n } catch (error) {\n // If terminate detector throws or JSON parsing fails, terminate the stream to prevent corrupted state\n controller.error(error);\n return;\n }\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to JsonServerSentEvent streams with optional termination detection.\n *\n * This class extends TransformStream to provide a convenient way to transform streams of ServerSentEvent\n * objects into streams of JsonServerSentEvent objects. It supports optional termination detection to\n * automatically end the stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransformStream instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * When provided, the stream will automatically terminate when this\n * function returns true for any event.\n *\n * @example\n * ```typescript\n * // Create a stream that terminates on 'done' events\n * const terminateOnDone: TerminateDetector = (event) => event.event === 'done';\n * const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);\n *\n * // Create a stream without termination detection\n * const basicStream = new JsonServerSentEventTransformStream<MyData>();\n * ```\n */\n constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\n *\n * This type represents a stream that yields parsed JSON server-sent events.\n * Each chunk in the stream contains the event metadata along with parsed JSON data.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\n/**\n * Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.\n *\n * This function takes a stream of raw server-sent events and transforms it into a stream of\n * parsed JSON events. It optionally accepts a termination detector to automatically end the\n * stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform\n * @param terminateDetector - Optional function to detect when the stream should be terminated\n * @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data\n * @throws {SyntaxError} If any event data is not valid JSON (thrown during stream consumption)\n *\n * @example\n * ```typescript\n * // Basic usage without termination detection\n * const jsonStream = toJsonServerSentEventStream<MyData>(serverSentEventStream);\n *\n * // With termination detection\n * const terminateOnDone: TerminateDetector = (event) => event.data === '[DONE]';\n * const terminatingStream = toJsonServerSentEventStream<MyData>(\n * serverSentEventStream,\n * terminateOnDone\n * );\n *\n * // Consume the stream\n * for await (const event of jsonStream) {\n * console.log('Received:', event.data);\n * console.log('Event type:', event.event);\n * }\n * ```\n */\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n terminateDetector?: TerminateDetector,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(terminateDetector),\n );\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FetchExchange, ResultExtractor } from '@ahoo-wang/fetcher';\nimport { ServerSentEventStream } from './eventStreamConverter';\nimport { 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\nconst 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 */\nif (\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\nconst 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 */\nif (\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 */\nif (!Object.prototype.hasOwnProperty.call(Response.prototype, 'eventStream')) {\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 */\nif (\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 */\nif (\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 */\nif (\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 * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper class that converts a ReadableStream into an AsyncIterable.\n *\n * This class enables the use of ReadableStream objects with async iteration syntax\n * (for-await...of loops), providing a more ergonomic way to consume streaming data.\n * It implements the AsyncIterable interface and manages the underlying stream reader,\n * handling proper resource cleanup and error propagation.\n *\n * The wrapper automatically handles stream locking, ensuring that only one consumer\n * can read from the stream at a time, and provides safe cleanup when iteration ends\n * or errors occur.\n *\n * @template T - The type of data yielded by the stream\n *\n * @example\n * ```typescript\n * // Direct usage\n * const response = await fetch('/api/stream');\n * const stream = response.body;\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * console.log('Received:', chunk);\n * }\n * // Stream is automatically cleaned up after iteration\n * ```\n *\n * @example\n * ```typescript\n * // With early termination\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * if (someCondition) {\n * asyncIterable.releaseLock(); // Manually release if needed\n * break;\n * }\n * }\n * ```\n */\nexport class ReadableStreamAsyncIterable<T> implements AsyncIterable<T> {\n private readonly reader: ReadableStreamDefaultReader<T>;\n private _locked: boolean = true;\n\n /**\n * Creates a new ReadableStreamAsyncIterable instance.\n * @param stream - The ReadableStream to wrap.\n */\n constructor(private readonly stream: ReadableStream<T>) {\n this.reader = stream.getReader();\n }\n\n /**\n * Gets the lock status of the reader.\n * @returns True if the reader is currently locked, false otherwise.\n */\n get locked(): boolean {\n return this._locked;\n }\n\n /**\n * Releases the reader lock if currently locked.\n * This method safely releases the reader lock by catching any potential errors.\n */\n releaseLock() {\n if (!this._locked) return false;\n this._locked = false;\n try {\n this.reader.releaseLock();\n return true;\n } catch (error) {\n console.debug('Failed to release reader lock:', error);\n return false;\n }\n }\n\n /**\n * Implements the AsyncIterable interface by returning this iterator.\n * @returns The async iterator for this instance.\n */\n [Symbol.asyncIterator]() {\n return this;\n }\n\n /**\n * Gets the next value from the stream.\n * Reads the next chunk from the stream and returns it as an IteratorResult.\n * If the stream is done, releases the lock and returns a done result.\n * @returns A promise that resolves to an IteratorResult containing the next value or done status.\n * @throws If an error occurs while reading from the stream.\n */\n async next(): Promise<IteratorResult<T>> {\n try {\n const { done, value } = await this.reader.read();\n if (done) {\n this.releaseLock();\n return { done: true, value: undefined };\n }\n\n return { done: false, value };\n } catch (error) {\n this.releaseLock();\n throw error;\n }\n }\n\n /**\n * Implements the return method of the async iterator.\n * Cancels the stream reader and releases the lock.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async return(): Promise<IteratorResult<T>> {\n try {\n await this.reader.cancel();\n } catch (error) {\n console.debug('Failed to cancel stream reader:', error);\n } finally {\n this.releaseLock();\n }\n return { done: true, value: undefined };\n }\n\n /**\n * Implements the throw method of the async iterator.\n * Releases the lock and returns a done result.\n * @param error - The error to be thrown.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async throw(error: any): Promise<IteratorResult<T>> {\n // Ensure the reader lock is released before throwing\n console.debug('Throwing error:', error);\n this.releaseLock();\n return { done: true, value: undefined };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReadableStreamAsyncIterable } from './readableStreamAsyncIterable';\n\ndeclare global {\n interface ReadableStream<R = any> {\n /**\n * Makes ReadableStream async iterable for use with for-await loops.\n *\n * This allows the stream to be consumed using `for await (const chunk of stream)` syntax.\n *\n * @returns An async iterator for the stream\n */\n [Symbol.asyncIterator](): AsyncIterator<R>;\n }\n}\n\n/**\n * Checks if the current environment natively supports async iteration on ReadableStream.\n *\n * This constant determines whether the browser or runtime already provides\n * built-in support for using ReadableStream with for-await...of loops.\n * If not supported, this library will polyfill the functionality by adding\n * the [Symbol.asyncIterator] method to ReadableStream.prototype.\n *\n * @returns true if native async iteration is supported, false if polyfill is needed\n *\n * @example\n * ```typescript\n * import { isReadableStreamAsyncIterableSupported } from '@ahoo-wang/fetcher-eventstream';\n *\n * if (isReadableStreamAsyncIterableSupported) {\n * console.log('Native support available');\n * } else {\n * console.log('Using polyfill');\n * }\n * ```\n */\nexport const isReadableStreamAsyncIterableSupported =\n typeof ReadableStream.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"],"names":["TextLineTransformer","chunk","controller","lines","line","error","TextLineTransformStream","_ServerSentEventFields","ServerSentEventFields","processFieldInternal","field","value","currentEvent","retryValue","DEFAULT_EVENT_TYPE","ServerSentEventTransformer","colonIndex","enhancedError","ServerSentEventTransformStream","EventStreamConvertError","FetcherError","response","errorMsg","cause","toServerSentEventStream","JsonServerSentEventTransform","terminateDetector","json","JsonServerSentEventTransformStream","toJsonServerSentEventStream","serverSentEventStream","EventStreamResultExtractor","exchange","JsonEventStreamResultExtractor","CONTENT_TYPE_PROPERTY_NAME","CONTENT_TYPE_HEADER","IS_EVENT_STREAM_PROPERTY_NAME","contentType","ContentTypeValues","eventStream","ReadableStreamAsyncIterable","stream","done","isReadableStreamAsyncIterableSupported"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.umd.js","names":[],"sources":["../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/eventStreamResultExtractor.ts","../src/responses.ts","../src/readableStreamAsyncIterable.ts","../src/readableStreams.ts"],"sourcesContent":["/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Transformer that splits text into lines.\n *\n * This transformer accumulates chunks of text and splits them by newline characters ('\\n'),\n * emitting each complete line as a separate chunk. It handles partial lines that span multiple\n * input chunks by maintaining an internal buffer. Lines are emitted without the newline character.\n *\n * The transformer handles various edge cases:\n * - Lines that span multiple input chunks\n * - Empty lines (emitted as empty strings)\n * - Text without trailing newlines (buffered until stream ends)\n * - Mixed line endings (only '\\n' is recognized as line separator)\n *\n * @implements {Transformer<string, string>}\n *\n * @example\n * ```typescript\n * const transformer = new TextLineTransformer();\n * // Input: \"line1\\nline2\\npartial\"\n * // Output: [\"line1\", \"line2\"]\n * // Buffer: \"partial\"\n *\n * // Later input: \"line\\n\"\n * // Output: [\"partialline\"]\n * // Buffer: \"\"\n * ```\n */\nexport class TextLineTransformer implements Transformer<string, string> {\n private buffer = '';\n\n /**\n * Transform input string chunk by splitting it into lines.\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<string>,\n ) {\n try {\n this.buffer += chunk;\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() || '';\n\n for (const line of lines) {\n controller.enqueue(line);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n\n /**\n * Flush remaining buffer when the stream ends.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<string>) {\n try {\n // Only send when buffer is not empty, avoid sending meaningless empty lines\n if (this.buffer) {\n controller.enqueue(this.buffer);\n }\n } catch (error) {\n controller.error(error);\n }\n }\n}\n\n/**\n * A TransformStream that splits text into lines.\n *\n * This class provides a convenient way to transform a stream of text chunks into a stream\n * of individual lines. It wraps the TextLineTransformer in a TransformStream for easy\n * integration with other stream processing pipelines.\n *\n * The stream processes text data and emits each line as a separate chunk, handling\n * lines that may span multiple input chunks automatically.\n *\n * @example\n * ```typescript\n * // Create a line-splitting stream\n * const lineStream = new TextLineTransformStream();\n *\n * // Pipe text through it\n * const lines = textStream.pipeThrough(lineStream);\n *\n * // Process each line\n * for await (const line of lines) {\n * console.log('Line:', line);\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Process SSE response line by line\n * const response = await fetch('/api/stream');\n * const lines = response.body!\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(new TextLineTransformStream());\n *\n * for await (const line of lines) {\n * if (line.startsWith('data: ')) {\n * console.log('SSE data:', line.substring(6));\n * }\n * }\n * ```\n */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n /**\n * Creates a new TextLineTransformStream instance.\n *\n * Initializes the stream with a TextLineTransformer that handles the line splitting logic.\n */\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * This interface defines the structure of Server-Sent Events (SSE) as specified by the W3C.\n * Each event contains metadata and data that can be processed by clients to handle real-time\n * updates from the server.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\n/**\n * Constants for Server-Sent Event field names.\n *\n * This class provides string constants for the standard SSE field names as defined\n * in the W3C Server-Sent Events specification. These constants help ensure\n * consistent field name usage throughout the parsing logic.\n */\nexport class ServerSentEventFields {\n /** The field name for event ID */\n static readonly ID = 'id';\n /** The field name for retry interval */\n static readonly RETRY = 'retry';\n /** The field name for event type */\n static readonly EVENT = 'event';\n /** The field name for event data */\n static readonly DATA = 'data';\n}\n\n/**\n * Processes a field-value pair and updates the current event state accordingly.\n *\n * This internal function handles the parsing of individual SSE fields according to the\n * Server-Sent Events specification. It updates the provided event state object with\n * the parsed field values.\n *\n * @param field - The field name (e.g., 'event', 'data', 'id', 'retry')\n * @param value - The field value as a string\n * @param currentEvent - The current event state object to update\n *\n * @example\n * ```typescript\n * const eventState: EventState = { event: 'message', data: [] };\n * processFieldInternal('event', 'custom', eventState);\n * // eventState.event is now 'custom'\n * ```\n */\nfunction processFieldInternal(\n field: string,\n value: string,\n currentEvent: EventState,\n) {\n switch (field) {\n case ServerSentEventFields.EVENT:\n currentEvent.event = value;\n break;\n case ServerSentEventFields.DATA:\n currentEvent.data.push(value);\n break;\n case ServerSentEventFields.ID:\n currentEvent.id = value;\n break;\n case ServerSentEventFields.RETRY: {\n const retryValue = parseInt(value, 10);\n if (!isNaN(retryValue)) {\n currentEvent.retry = retryValue;\n }\n break;\n }\n default:\n // Ignore unknown fields\n break;\n }\n}\n\n/**\n * Internal state representation during Server-Sent Event parsing.\n *\n * This interface tracks the current state of an event being parsed from the SSE stream.\n * It accumulates field values until a complete event is ready to be emitted.\n */\ninterface EventState {\n /** The event type (defaults to 'message') */\n event?: string;\n /** The event ID */\n id?: string;\n /** The retry interval in milliseconds */\n retry?: number;\n /** Array of data lines that will be joined with newlines */\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n * This transformer handles the parsing of Server-Sent Events (SSE) according to the W3C specification.\n * It processes incoming text chunks and converts them into structured ServerSentEvent objects.\n */\nexport class ServerSentEventTransformer\n implements Transformer<string, ServerSentEvent>\n{\n // Initialize currentEventState with default values in a closure\n private currentEventState: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Reset the current event state to default values.\n * This method is called after processing each complete event or when an error occurs.\n */\n private resetEventState() {\n this.currentEventState.event = DEFAULT_EVENT_TYPE;\n this.currentEventState.id = undefined;\n this.currentEventState.retry = undefined;\n this.currentEventState.data = [];\n }\n\n /**\n * Transform input string chunk into ServerSentEvent object.\n * This method processes individual chunks of text data, parsing them according to the SSE format.\n * It handles:\n * - Empty lines (used as event separators)\n * - Comment lines (starting with ':')\n * - Field lines (field: value format)\n * - Event completion and emission\n *\n * @param chunk Input string chunk\n * @param controller Controller for controlling the transform stream\n */\n transform(\n chunk: string,\n controller: TransformStreamDefaultController<ServerSentEvent>,\n ) {\n const currentEvent = this.currentEventState;\n try {\n // Skip empty lines (event separator)\n if (chunk.trim() === '') {\n // If there is accumulated event data, send event\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n\n // Reset current event (preserve id and retry for subsequent events)\n currentEvent.event = DEFAULT_EVENT_TYPE;\n // Preserve id and retry for subsequent events (no need to reassign to themselves)\n currentEvent.data = [];\n }\n return;\n }\n\n // Ignore comment lines (starting with colon)\n if (chunk.startsWith(':')) {\n return;\n }\n\n // Parse fields\n const colonIndex = chunk.indexOf(':');\n let field: string;\n let value: string;\n\n if (colonIndex === -1) {\n // No colon, entire line as field name, value is empty\n field = chunk.toLowerCase();\n value = '';\n } else {\n // Extract field name and value\n field = chunk.substring(0, colonIndex).toLowerCase();\n value = chunk.substring(colonIndex + 1);\n\n // If value starts with space, remove leading space\n if (value.startsWith(' ')) {\n value = value.substring(1);\n }\n }\n\n // Remove trailing newlines from field and value\n field = field.trim();\n value = value.trim();\n\n processFieldInternal(field, value, currentEvent);\n } catch (error) {\n const enhancedError = new Error(\n `Failed to process chunk: \"${chunk}\". ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n // Reset state\n this.resetEventState();\n }\n }\n\n /**\n * Called when the stream ends, used to process remaining data.\n *\n * @param controller Controller for controlling the transform stream\n */\n flush(controller: TransformStreamDefaultController<ServerSentEvent>) {\n const currentEvent = this.currentEventState;\n try {\n // Send the last event (if any)\n if (currentEvent.data.length > 0) {\n controller.enqueue({\n event: currentEvent.event || DEFAULT_EVENT_TYPE,\n data: currentEvent.data.join('\\n'),\n id: currentEvent.id || '',\n retry: currentEvent.retry,\n } as ServerSentEvent);\n }\n } catch (error) {\n const enhancedError = new Error(\n `Failed to flush remaining data. ${error instanceof Error ? error.message : String(error)}`,\n );\n controller.error(enhancedError);\n } finally {\n // Reset state\n this.resetEventState();\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n *\n * This class provides a convenient way to transform raw text streams containing Server-Sent Events\n * into structured event objects. It wraps the ServerSentEventTransformer in a TransformStream\n * for easy integration with other stream processing pipelines.\n *\n * The stream processes SSE format text and emits ServerSentEvent objects as they are completed.\n * Events are separated by empty lines, and the stream handles partial events across multiple chunks.\n *\n * @example\n * ```typescript\n * // Create a transform stream\n * const sseStream = new ServerSentEventTransformStream();\n *\n * // Pipe a text stream through it\n * const eventStream = textStream.pipeThrough(sseStream);\n *\n * // Consume the events\n * for await (const event of eventStream) {\n * console.log('Event:', event.event, 'Data:', event.data);\n * }\n * ```\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n /**\n * Creates a new ServerSentEventTransformStream instance.\n *\n * Initializes the stream with a ServerSentEventTransformer that handles\n * the parsing of SSE format text into structured events.\n */\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n type ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n *\n * This type represents a stream that yields Server-Sent Event objects as they are parsed\n * from a raw event stream. Each chunk in the stream contains a complete SSE event with\n * its metadata (event type, ID, retry interval) and data.\n *\n * @see {@link ServerSentEvent} for the structure of individual events\n * @see {@link toServerSentEventStream} for converting HTTP responses to this type\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n *\n * This error is thrown when there are issues converting an HTTP Response to a ServerSentEventStream.\n * It extends FetcherError to provide additional context about the failed conversion, including\n * the original Response object and any underlying cause.\n *\n * @extends {FetcherError}\n *\n * @example\n * ```typescript\n * try {\n * const eventStream = toServerSentEventStream(response);\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Failed to convert response to event stream:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n *\n * @param response - The Response object associated with the error, providing context\n * about the failed conversion (status, headers, etc.)\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this conversion error\n */\n constructor(\n public readonly response: Response,\n errorMsg?: string,\n cause?: Error | any,\n ) {\n super(errorMsg, cause);\n this.name = 'EventStreamConvertError';\n // Restore prototype chain for proper inheritance\n Object.setPrototypeOf(this, EventStreamConvertError.prototype);\n }\n}\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * This function takes an HTTP Response object and converts its body into a stream of\n * Server-Sent Event objects. The conversion process involves several transformation steps:\n *\n * 1. **TextDecoderStream**: Decodes the raw Uint8Array response body to UTF-8 strings\n * 2. **TextLineTransformStream**: Splits the text stream into individual lines\n * 3. **ServerSentEventTransformStream**: Parses the line-based SSE format into structured events\n *\n * The resulting stream can be consumed using async iteration or other stream methods.\n *\n * @param response - The HTTP Response object to convert. Must have a readable body stream.\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {EventStreamConvertError} If the response body is null or cannot be processed\n *\n * @example\n * ```typescript\n * // Convert an SSE response to an event stream\n * const response = await fetch('/api/events');\n * const eventStream = toServerSentEventStream(response);\n *\n * // Consume events asynchronously\n * for await (const event of eventStream) {\n * console.log(`Event: ${event.event}, Data: ${event.data}`);\n *\n * // Handle different event types\n * switch (event.event) {\n * case 'message':\n * handleMessage(event.data);\n * break;\n * case 'error':\n * handleError(event.data);\n * break;\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Handle conversion errors\n * try {\n * const eventStream = toServerSentEventStream(response);\n * // Use the stream...\n * } catch (error) {\n * if (error instanceof EventStreamConvertError) {\n * console.error('Event stream conversion failed:', error.message);\n * console.log('Response status:', error.response.status);\n * }\n * }\n * ```\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new EventStreamConvertError(response, 'Response body is null');\n }\n\n return response.body\n .pipeThrough(new TextDecoderStream('utf-8'))\n .pipeThrough(new TextLineTransformStream())\n .pipeThrough(new ServerSentEventTransformStream());\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type ServerSentEvent } from './serverSentEventTransformStream';\nimport type { ServerSentEventStream } from './eventStreamConverter';\n\n/**\n * A function type that determines whether a Server-Sent Event should terminate the stream.\n *\n * This detector function is called for each incoming ServerSentEvent. If it returns true,\n * the stream transformation will be terminated, preventing further events from being processed.\n *\n * @param event - The ServerSentEvent to evaluate for termination\n * @returns true if the stream should be terminated, false otherwise\n *\n * @example\n * ```typescript\n * const terminateOnDone: TerminateDetector = (event) => {\n * return event.event === 'done' || event.data === '[DONE]';\n * };\n * ```\n */\nexport type TerminateDetector = (event: ServerSentEvent) => boolean;\n\n/**\n * Represents a Server-Sent Event with parsed JSON data.\n *\n * This interface extends the base ServerSentEvent but replaces the string 'data' field\n * with a parsed JSON object of the specified generic type. This allows for type-safe\n * access to the event payload.\n *\n * @template DATA - The expected type of the parsed JSON data\n */\nexport interface JsonServerSentEvent<DATA>\n extends Omit<ServerSentEvent, 'data'> {\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>\n implements Transformer<ServerSentEvent, JsonServerSentEvent<DATA>>\n{\n /**\n * Creates a new JsonServerSentEventTransform instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * If provided, this function is called for each event and can terminate\n * the stream by returning true.\n */\n constructor(private readonly terminateDetector?: TerminateDetector) {}\n\n /**\n * Transforms a ServerSentEvent chunk into a JsonServerSentEvent.\n *\n * This method first checks if the event should terminate the stream using the terminateDetector.\n * If termination is required, the controller is terminated. Otherwise, the event data is parsed\n * as JSON and enqueued as a JsonServerSentEvent.\n *\n * If the terminateDetector throws an exception, the stream is terminated with an error to prevent\n * corrupted state.\n *\n * @param chunk - The ServerSentEvent to transform\n * @param controller - The TransformStream controller for managing the stream\n * @throws {SyntaxError} If the event data is not valid JSON\n * @throws {Error} If the terminateDetector throws an exception\n *\n * @example\n * ```typescript\n * const transformer = new JsonServerSentEventTransform<MyData>();\n * // This will be called automatically by the TransformStream\n * ```\n */\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\n ) {\n try {\n // Check if this is a terminate event\n if (this.terminateDetector?.(chunk)) {\n controller.terminate();\n return;\n }\n\n const json = JSON.parse(chunk.data) as DATA;\n controller.enqueue({\n data: json,\n event: chunk.event,\n id: chunk.id,\n retry: chunk.retry,\n });\n } catch (error) {\n // If terminate detector throws or JSON parsing fails, terminate the stream to prevent corrupted state\n controller.error(error);\n return;\n }\n }\n}\n\n/**\n * A TransformStream that converts ServerSentEvent streams to JsonServerSentEvent streams with optional termination detection.\n *\n * This class extends TransformStream to provide a convenient way to transform streams of ServerSentEvent\n * objects into streams of JsonServerSentEvent objects. It supports optional termination detection to\n * automatically end the stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n /**\n * Creates a new JsonServerSentEventTransformStream instance.\n *\n * @param terminateDetector - Optional function to detect when the stream should be terminated.\n * When provided, the stream will automatically terminate when this\n * function returns true for any event.\n *\n * @example\n * ```typescript\n * // Create a stream that terminates on 'done' events\n * const terminateOnDone: TerminateDetector = (event) => event.event === 'done';\n * const transformStream = new JsonServerSentEventTransformStream<MyData>(terminateOnDone);\n *\n * // Create a stream without termination detection\n * const basicStream = new JsonServerSentEventTransformStream<MyData>();\n * ```\n */\n constructor(terminateDetector?: TerminateDetector) {\n super(new JsonServerSentEventTransform(terminateDetector));\n }\n}\n\n/**\n * A ReadableStream of JsonServerSentEvent objects.\n *\n * This type represents a stream that yields parsed JSON server-sent events.\n * Each chunk in the stream contains the event metadata along with parsed JSON data.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n */\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\n/**\n * Converts a ServerSentEventStream to a JsonServerSentEventStream with optional termination detection.\n *\n * This function takes a stream of raw server-sent events and transforms it into a stream of\n * parsed JSON events. It optionally accepts a termination detector to automatically end the\n * stream when certain conditions are met.\n *\n * @template DATA - The expected type of the parsed JSON data in each event\n * @param serverSentEventStream - The input stream of ServerSentEvent objects to transform\n * @param terminateDetector - Optional function to detect when the stream should be terminated\n * @returns A ReadableStream that yields JsonServerSentEvent objects with parsed JSON data\n * @throws {SyntaxError} If any event data is not valid JSON (thrown during stream consumption)\n *\n * @example\n * ```typescript\n * // Basic usage without termination detection\n * const jsonStream = toJsonServerSentEventStream<MyData>(serverSentEventStream);\n *\n * // With termination detection\n * const terminateOnDone: TerminateDetector = (event) => event.data === '[DONE]';\n * const terminatingStream = toJsonServerSentEventStream<MyData>(\n * serverSentEventStream,\n * terminateOnDone\n * );\n *\n * // Consume the stream\n * for await (const event of jsonStream) {\n * console.log('Received:', event.data);\n * console.log('Event type:', event.event);\n * }\n * ```\n */\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n terminateDetector?: TerminateDetector,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(terminateDetector),\n );\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { FetchExchange, ResultExtractor } from '@ahoo-wang/fetcher';\nimport type { ServerSentEventStream } from './eventStreamConverter';\nimport type { JsonServerSentEventStream } from './jsonServerSentEventTransformStream';\n\n/**\n * ServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a ServerSentEventStream from an HTTP response that contains Server-Sent Events.\n * The extractor validates that the response supports event streaming and converts the\n * response body into a properly typed event stream.\n *\n * This extractor should be used when you want to consume raw Server-Sent Events\n * without JSON parsing, maintaining the original string data format.\n *\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects as they are parsed from the response\n * @throws {ExchangeError} When the server response does not support ServerSentEventStream\n * (e.g., wrong content type, no response body)\n *\n *\n * @see {@link ServerSentEventStream} for the stream type\n * @see {@link JsonEventStreamResultExtractor} for JSON-parsed event streams\n */\nexport const EventStreamResultExtractor: ResultExtractor<\n ServerSentEventStream\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredEventStream();\n};\n\n/**\n * JsonServerSentEventStream result extractor for Fetcher HTTP client.\n *\n * This result extractor is designed to work with the Fetcher HTTP client library.\n * It extracts a JsonServerSentEventStream from an HTTP response that contains Server-Sent Events\n * with JSON data. The extractor validates that the response supports event streaming and converts\n * the response body into a properly typed event stream with automatic JSON parsing.\n *\n * This extractor should be used when you want to consume Server-Sent Events where the event\n * data is JSON-formatted, providing type-safe access to parsed JSON objects instead of raw strings.\n *\n * @template DATA - The expected type of the JSON data in the server-sent events\n * @param exchange - The FetchExchange object containing request and response information\n * @returns A ReadableStream that yields ServerSentEvent objects with parsed JSON data as they are received\n * @throws {ExchangeError} When the server response does not support JsonServerSentEventStream\n * (e.g., wrong content type, no response body, invalid JSON)\n *\n *\n * @see {@link JsonServerSentEventStream} for the stream type with JSON data\n * @see {@link EventStreamResultExtractor} for raw string event streams\n */\nexport const JsonEventStreamResultExtractor: ResultExtractor<\n JsonServerSentEventStream<any>\n> = (exchange: FetchExchange) => {\n return exchange.requiredResponse.requiredJsonEventStream();\n};\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n EventStreamConvertError,\n type ServerSentEventStream,\n toServerSentEventStream,\n} from './eventStreamConverter';\nimport {\n type JsonServerSentEventStream,\n type TerminateDetector,\n toJsonServerSentEventStream,\n} from './jsonServerSentEventTransformStream';\nimport { CONTENT_TYPE_HEADER, ContentTypeValues } from '@ahoo-wang/fetcher';\n\ndeclare global {\n interface Response {\n /**\n * Gets the content type of the response.\n *\n * This property provides access to the Content-Type header of the response,\n * which indicates the media type of the resource transmitted in the response.\n *\n * @returns The content type header value as a string, or null if the header is not set\n */\n get contentType(): string | null;\n\n /**\n * Checks if the response is an event stream.\n *\n * This property examines the Content-Type header to determine if the response\n * contains server-sent events data (text/event-stream).\n *\n * @returns true if the response is an event stream, false otherwise\n */\n get isEventStream(): boolean;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects, or null if not an event stream\n */\n eventStream(): ServerSentEventStream | null;\n\n /**\n * Returns a ServerSentEventStream for consuming server-sent events.\n *\n * This method is similar to eventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream.\n *\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws {Error} if the event stream is not available\n */\n requiredEventStream(): ServerSentEventStream;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is added to Response objects by the EventStreamInterceptor\n * when the response content type indicates a server-sent event stream.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data, or null if not an event stream\n */\n jsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA> | null;\n\n /**\n * Returns a JsonServerSentEventStream for consuming server-sent events with JSON data.\n *\n * This method is similar to jsonEventStream() but will throw an error if the event stream is not available.\n * It is added to Response objects by the EventStreamInterceptor when the response content type\n * indicates a server-sent event stream with JSON data.\n *\n * @template DATA - The type of the JSON data in the server-sent events\n * @param terminateDetector - Optional function to detect when the stream should terminate\n * @returns A ReadableStream of ServerSentEvent objects with JSON data\n * @throws {Error} if the event stream is not available\n */\n requiredJsonEventStream<DATA>(\n terminateDetector?: TerminateDetector,\n ): JsonServerSentEventStream<DATA>;\n }\n}\n\nconst 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 */\nif (\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\nconst 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 */\nif (\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 */\nif (!Object.prototype.hasOwnProperty.call(Response.prototype, 'eventStream')) {\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 */\nif (\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 */\nif (\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 */\nif (\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 * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper class that converts a ReadableStream into an AsyncIterable.\n *\n * This class enables the use of ReadableStream objects with async iteration syntax\n * (for-await...of loops), providing a more ergonomic way to consume streaming data.\n * It implements the AsyncIterable interface and manages the underlying stream reader,\n * handling proper resource cleanup and error propagation.\n *\n * The wrapper automatically handles stream locking, ensuring that only one consumer\n * can read from the stream at a time, and provides safe cleanup when iteration ends\n * or errors occur.\n *\n * @template T - The type of data yielded by the stream\n *\n * @example\n * ```typescript\n * // Direct usage\n * const response = await fetch('/api/stream');\n * const stream = response.body;\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * console.log('Received:', chunk);\n * }\n * // Stream is automatically cleaned up after iteration\n * ```\n *\n * @example\n * ```typescript\n * // With early termination\n * const asyncIterable = new ReadableStreamAsyncIterable(stream);\n *\n * for await (const chunk of asyncIterable) {\n * if (someCondition) {\n * asyncIterable.releaseLock(); // Manually release if needed\n * break;\n * }\n * }\n * ```\n */\nexport class ReadableStreamAsyncIterable<T> implements AsyncIterable<T> {\n private readonly reader: ReadableStreamDefaultReader<T>;\n private _locked: boolean = true;\n\n /**\n * Creates a new ReadableStreamAsyncIterable instance.\n * @param stream - The ReadableStream to wrap.\n */\n constructor(private readonly stream: ReadableStream<T>) {\n this.reader = stream.getReader();\n }\n\n /**\n * Gets the lock status of the reader.\n * @returns True if the reader is currently locked, false otherwise.\n */\n get locked(): boolean {\n return this._locked;\n }\n\n /**\n * Releases the reader lock if currently locked.\n * This method safely releases the reader lock by catching any potential errors.\n */\n releaseLock() {\n if (!this._locked) return false;\n this._locked = false;\n try {\n this.reader.releaseLock();\n return true;\n } catch (error) {\n console.debug('Failed to release reader lock:', error);\n return false;\n }\n }\n\n /**\n * Implements the AsyncIterable interface by returning this iterator.\n * @returns The async iterator for this instance.\n */\n [Symbol.asyncIterator]() {\n return this;\n }\n\n /**\n * Gets the next value from the stream.\n * Reads the next chunk from the stream and returns it as an IteratorResult.\n * If the stream is done, releases the lock and returns a done result.\n * @returns A promise that resolves to an IteratorResult containing the next value or done status.\n * @throws If an error occurs while reading from the stream.\n */\n async next(): Promise<IteratorResult<T>> {\n try {\n const { done, value } = await this.reader.read();\n if (done) {\n this.releaseLock();\n return { done: true, value: undefined };\n }\n\n return { done: false, value };\n } catch (error) {\n this.releaseLock();\n throw error;\n }\n }\n\n /**\n * Implements the return method of the async iterator.\n * Cancels the stream reader and releases the lock.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async return(): Promise<IteratorResult<T>> {\n try {\n await this.reader.cancel();\n } catch (error) {\n console.debug('Failed to cancel stream reader:', error);\n } finally {\n this.releaseLock();\n }\n return { done: true, value: undefined };\n }\n\n /**\n * Implements the throw method of the async iterator.\n * Releases the lock and returns a done result.\n * @param error - The error to be thrown.\n * @returns A promise that resolves to a done IteratorResult.\n */\n async throw(error: any): Promise<IteratorResult<T>> {\n // Ensure the reader lock is released before throwing\n console.debug('Throwing error:', error);\n this.releaseLock();\n return { done: true, value: undefined };\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReadableStreamAsyncIterable } from './readableStreamAsyncIterable';\n\ndeclare global {\n interface ReadableStream<R = any> {\n /**\n * Makes ReadableStream async iterable for use with for-await loops.\n *\n * This allows the stream to be consumed using `for await (const chunk of stream)` syntax.\n *\n * @returns An async iterator for the stream\n */\n [Symbol.asyncIterator](): AsyncIterator<R>;\n }\n}\n\n/**\n * Checks if the current environment natively supports async iteration on ReadableStream.\n *\n * This constant determines whether the browser or runtime already provides\n * built-in support for using ReadableStream with for-await...of loops.\n * If not supported, this library will polyfill the functionality by adding\n * the [Symbol.asyncIterator] method to ReadableStream.prototype.\n *\n * @returns true if native async iteration is supported, false if polyfill is needed\n *\n * @example\n * ```typescript\n * import { isReadableStreamAsyncIterableSupported } from '@ahoo-wang/fetcher-eventstream';\n *\n * if (isReadableStreamAsyncIterableSupported) {\n * console.log('Native support available');\n * } else {\n * console.log('Using polyfill');\n * }\n * ```\n */\nexport const isReadableStreamAsyncIterableSupported =\n typeof ReadableStream.prototype[Symbol.asyncIterator] === 'function';\n\n// Add [Symbol.asyncIterator] to ReadableStream if not already implemented\nif (!isReadableStreamAsyncIterableSupported) {\n ReadableStream.prototype[Symbol.asyncIterator] = function <R = any>() {\n return new ReadableStreamAsyncIterable<R>(this as ReadableStream<R>);\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
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,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"jsonServerSentEventTransformStream.d.ts","sourceRoot":"","sources":["../src/jsonServerSentEventTransformStream.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB,CAAC,IAAI,CACvC,SAAQ,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC;IACrC,0CAA0C;IAC1C,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;;;;;;GAQG;AACH,qBAAa,4BAA4B,CAAC,IAAI,CAC5C,YAAW,WAAW,CAAC,eAAe,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAStD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IAP/C;;;;;;OAMG;gBAC0B,iBAAiB,CAAC,EAAE,iBAAiB,YAAA;IAElE;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,CACP,KAAK,EAAE,eAAe,EACtB,UAAU,EAAE,gCAAgC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;CAsB1E;AAED;;;;;;;;GAQG;AACH,qBAAa,kCAAkC,CAAC,IAAI,CAAE,SAAQ,eAAe,CAC3E,eAAe,EACf,mBAAmB,CAAC,IAAI,CAAC,CAC1B;IACC;;;;;;;;;;;;;;;;OAgBG;gBACS,iBAAiB,CAAC,EAAE,iBAAiB;CAGlD;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,yBAAyB,CAAC,IAAI,IAAI,cAAc,CAC1D,mBAAmB,CAAC,IAAI,CAAC,CAC1B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAC9C,qBAAqB,EAAE,qBAAqB,EAC5C,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,yBAAyB,CAAC,IAAI,CAAC,CAIjC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ahoo-wang/fetcher-eventstream",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.1",
|
|
4
4
|
"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
5
|
"keywords": [
|
|
6
6
|
"fetch",
|
|
@@ -43,17 +43,17 @@
|
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@eslint/js": "^9.39.4",
|
|
46
|
-
"@vitest/coverage-v8": "^4.
|
|
47
|
-
"@vitest/ui": "^4.
|
|
46
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
47
|
+
"@vitest/ui": "^4.1.4",
|
|
48
48
|
"eslint": "^9.39.4",
|
|
49
49
|
"globals": "^17.4.0",
|
|
50
50
|
"prettier": "^3.8.1",
|
|
51
|
-
"typescript": "^
|
|
52
|
-
"typescript-eslint": "^8.58.
|
|
51
|
+
"typescript": "^6.0.2",
|
|
52
|
+
"typescript-eslint": "^8.58.1",
|
|
53
53
|
"unplugin-dts": "1.0.0-beta.6",
|
|
54
|
-
"vite": "^
|
|
55
|
-
"vite-bundle-analyzer": "^1.3.
|
|
56
|
-
"vitest": "^4.1.
|
|
54
|
+
"vite": "^8.0.8",
|
|
55
|
+
"vite-bundle-analyzer": "^1.3.7",
|
|
56
|
+
"vitest": "^4.1.4"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "vite build",
|