@ahoo-wang/fetcher-eventstream 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -82
- package/README.zh-CN.md +96 -72
- package/dist/eventStreamConverter.d.ts +15 -0
- package/dist/eventStreamConverter.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +105 -96
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +4 -4
- package/dist/index.umd.js.map +1 -1
- package/dist/responses.d.ts +77 -0
- package/dist/responses.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/eventStreamInterceptor.d.ts +0 -62
- package/dist/eventStreamInterceptor.d.ts.map +0 -1
- package/dist/types.d.ts +0 -32
- package/dist/types.d.ts.map +0 -1
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import { ServerSentEvent } from './serverSentEventTransformStream';
|
|
2
|
+
import { FetcherError } from '@ahoo-wang/fetcher';
|
|
2
3
|
/**
|
|
3
4
|
* A ReadableStream of ServerSentEvent objects.
|
|
4
5
|
*/
|
|
5
6
|
export type ServerSentEventStream = ReadableStream<ServerSentEvent>;
|
|
7
|
+
/**
|
|
8
|
+
* Custom error class for event stream conversion errors.
|
|
9
|
+
* Thrown when there are issues converting a Response to a ServerSentEventStream.
|
|
10
|
+
*/
|
|
11
|
+
export declare class EventStreamConvertError extends FetcherError {
|
|
12
|
+
readonly response: Response;
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new EventStreamConvertError instance.
|
|
15
|
+
* @param response - The Response object associated with the error
|
|
16
|
+
* @param errorMsg - Optional error message describing what went wrong during conversion
|
|
17
|
+
* @param cause - Optional underlying error that caused this error
|
|
18
|
+
*/
|
|
19
|
+
constructor(response: Response, errorMsg?: string, cause?: Error | any);
|
|
20
|
+
}
|
|
6
21
|
/**
|
|
7
22
|
* Converts a Response object to a ServerSentEventStream.
|
|
8
23
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventStreamConverter.d.ts","sourceRoot":"","sources":["../src/eventStreamConverter.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,eAAe,EAEhB,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"eventStreamConverter.d.ts","sourceRoot":"","sources":["../src/eventStreamConverter.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,eAAe,EAEhB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,eAAe,CAAC,CAAC;AAEpE;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,YAAY;aAQrC,QAAQ,EAAE,QAAQ;IAPpC;;;;;OAKG;gBAEe,QAAQ,EAAE,QAAQ,EAClC,QAAQ,CAAC,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,KAAK,GAAG,GAAG;CAOtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,GACjB,qBAAqB,CASvB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export * from './eventStreamConverter';
|
|
2
|
-
export * from './eventStreamInterceptor';
|
|
3
2
|
export * from './jsonServerSentEventTransformStream';
|
|
3
|
+
export * from './responses';
|
|
4
4
|
export * from './serverSentEventTransformStream';
|
|
5
5
|
export * from './textLineTransformStream';
|
|
6
|
-
export * from './types';
|
|
7
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,wBAAwB,CAAC;AACvC,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sCAAsC,CAAC;AACrD,cAAc,aAAa,CAAC;AAC5B,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC"}
|
package/dist/index.es.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CONTENT_TYPE_HEADER as
|
|
2
|
-
class
|
|
1
|
+
import { FetcherError as p, CONTENT_TYPE_HEADER as d, ContentTypeValues as h } from "@ahoo-wang/fetcher";
|
|
2
|
+
class m {
|
|
3
3
|
constructor() {
|
|
4
4
|
this.buffer = "";
|
|
5
5
|
}
|
|
@@ -9,16 +9,16 @@ class d {
|
|
|
9
9
|
* @param chunk Input string chunk
|
|
10
10
|
* @param controller Controller for controlling the transform stream
|
|
11
11
|
*/
|
|
12
|
-
transform(t,
|
|
12
|
+
transform(t, r) {
|
|
13
13
|
try {
|
|
14
14
|
this.buffer += t;
|
|
15
|
-
const
|
|
15
|
+
const e = this.buffer.split(`
|
|
16
16
|
`);
|
|
17
|
-
this.buffer =
|
|
18
|
-
for (const s of
|
|
19
|
-
|
|
20
|
-
} catch (
|
|
21
|
-
|
|
17
|
+
this.buffer = e.pop() || "";
|
|
18
|
+
for (const s of e)
|
|
19
|
+
r.enqueue(s);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
r.error(e);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
@@ -29,43 +29,43 @@ class d {
|
|
|
29
29
|
flush(t) {
|
|
30
30
|
try {
|
|
31
31
|
this.buffer && t.enqueue(this.buffer);
|
|
32
|
-
} catch (
|
|
33
|
-
t.error(
|
|
32
|
+
} catch (r) {
|
|
33
|
+
t.error(r);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
class
|
|
37
|
+
class v extends TransformStream {
|
|
38
38
|
constructor() {
|
|
39
|
-
super(new
|
|
39
|
+
super(new m());
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
const c = class c {
|
|
43
43
|
};
|
|
44
44
|
c.ID = "id", c.RETRY = "retry", c.EVENT = "event", c.DATA = "data";
|
|
45
45
|
let i = c;
|
|
46
|
-
function
|
|
46
|
+
function y(n, t, r) {
|
|
47
47
|
switch (n) {
|
|
48
48
|
case i.EVENT:
|
|
49
|
-
|
|
49
|
+
r.event = t;
|
|
50
50
|
break;
|
|
51
51
|
case i.DATA:
|
|
52
|
-
|
|
52
|
+
r.data.push(t);
|
|
53
53
|
break;
|
|
54
54
|
case i.ID:
|
|
55
|
-
|
|
55
|
+
r.id = t;
|
|
56
56
|
break;
|
|
57
57
|
case i.RETRY: {
|
|
58
|
-
const
|
|
59
|
-
isNaN(
|
|
58
|
+
const e = parseInt(t, 10);
|
|
59
|
+
isNaN(e) || (r.retry = e);
|
|
60
60
|
break;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
const
|
|
65
|
-
class
|
|
64
|
+
const a = "message";
|
|
65
|
+
class T {
|
|
66
66
|
constructor() {
|
|
67
67
|
this.currentEvent = {
|
|
68
|
-
event:
|
|
68
|
+
event: a,
|
|
69
69
|
id: void 0,
|
|
70
70
|
retry: void 0,
|
|
71
71
|
data: []
|
|
@@ -77,28 +77,28 @@ class p {
|
|
|
77
77
|
* @param chunk Input string chunk
|
|
78
78
|
* @param controller Controller for controlling the transform stream
|
|
79
79
|
*/
|
|
80
|
-
transform(t,
|
|
81
|
-
const
|
|
80
|
+
transform(t, r) {
|
|
81
|
+
const e = this.currentEvent;
|
|
82
82
|
try {
|
|
83
83
|
if (t.trim() === "") {
|
|
84
|
-
|
|
85
|
-
event:
|
|
86
|
-
data:
|
|
84
|
+
e.data.length > 0 && (r.enqueue({
|
|
85
|
+
event: e.event || a,
|
|
86
|
+
data: e.data.join(`
|
|
87
87
|
`),
|
|
88
|
-
id:
|
|
89
|
-
retry:
|
|
90
|
-
}),
|
|
88
|
+
id: e.id || "",
|
|
89
|
+
retry: e.retry
|
|
90
|
+
}), e.event = a, e.data = []);
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
93
|
if (t.startsWith(":"))
|
|
94
94
|
return;
|
|
95
95
|
const s = t.indexOf(":");
|
|
96
|
-
let f,
|
|
97
|
-
s === -1 ? (f = t.toLowerCase(),
|
|
96
|
+
let f, o;
|
|
97
|
+
s === -1 ? (f = t.toLowerCase(), o = "") : (f = t.substring(0, s).toLowerCase(), o = t.substring(s + 1), o.startsWith(" ") && (o = o.substring(1))), f = f.trim(), o = o.trim(), y(f, o, e);
|
|
98
98
|
} catch (s) {
|
|
99
|
-
|
|
99
|
+
r.error(
|
|
100
100
|
s instanceof Error ? s : new Error(String(s))
|
|
101
|
-
),
|
|
101
|
+
), e.event = a, e.id = void 0, e.retry = void 0, e.data = [];
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
@@ -107,97 +107,106 @@ class p {
|
|
|
107
107
|
* @param controller Controller for controlling the transform stream
|
|
108
108
|
*/
|
|
109
109
|
flush(t) {
|
|
110
|
-
const
|
|
110
|
+
const r = this.currentEvent;
|
|
111
111
|
try {
|
|
112
|
-
|
|
113
|
-
event:
|
|
114
|
-
data:
|
|
112
|
+
r.data.length > 0 && t.enqueue({
|
|
113
|
+
event: r.event || a,
|
|
114
|
+
data: r.data.join(`
|
|
115
115
|
`),
|
|
116
|
-
id:
|
|
117
|
-
retry:
|
|
116
|
+
id: r.id || "",
|
|
117
|
+
retry: r.retry
|
|
118
118
|
});
|
|
119
|
-
} catch (
|
|
119
|
+
} catch (e) {
|
|
120
120
|
t.error(
|
|
121
|
-
|
|
121
|
+
e instanceof Error ? e : new Error(String(e))
|
|
122
122
|
);
|
|
123
123
|
} finally {
|
|
124
|
-
|
|
124
|
+
r.event = a, r.id = void 0, r.retry = void 0, r.data = [];
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
class
|
|
128
|
+
class E extends TransformStream {
|
|
129
129
|
constructor() {
|
|
130
|
-
super(new
|
|
130
|
+
super(new T());
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
|
|
133
|
+
class u extends p {
|
|
134
|
+
/**
|
|
135
|
+
* Creates a new EventStreamConvertError instance.
|
|
136
|
+
* @param response - The Response object associated with the error
|
|
137
|
+
* @param errorMsg - Optional error message describing what went wrong during conversion
|
|
138
|
+
* @param cause - Optional underlying error that caused this error
|
|
139
|
+
*/
|
|
140
|
+
constructor(t, r, e) {
|
|
141
|
+
super(r, e), this.response = t, this.name = "EventStreamConvertError", Object.setPrototypeOf(this, u.prototype);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function S(n) {
|
|
134
145
|
if (!n.body)
|
|
135
|
-
throw new
|
|
136
|
-
return n.body.pipeThrough(new TextDecoderStream("utf-8")).pipeThrough(new
|
|
146
|
+
throw new u(n, "Response body is null");
|
|
147
|
+
return n.body.pipeThrough(new TextDecoderStream("utf-8")).pipeThrough(new v()).pipeThrough(new E());
|
|
137
148
|
}
|
|
138
|
-
class
|
|
139
|
-
transform(t,
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
data:
|
|
149
|
+
class l {
|
|
150
|
+
transform(t, r) {
|
|
151
|
+
const e = JSON.parse(t.data);
|
|
152
|
+
r.enqueue({
|
|
153
|
+
data: e,
|
|
143
154
|
event: t.event,
|
|
144
155
|
id: t.id,
|
|
145
156
|
retry: t.retry
|
|
146
157
|
});
|
|
147
158
|
}
|
|
148
159
|
}
|
|
149
|
-
class
|
|
160
|
+
class b extends TransformStream {
|
|
150
161
|
constructor() {
|
|
151
|
-
super(new
|
|
162
|
+
super(new l());
|
|
152
163
|
}
|
|
153
164
|
}
|
|
154
|
-
function
|
|
165
|
+
function w(n) {
|
|
155
166
|
return n.pipeThrough(
|
|
156
|
-
new
|
|
167
|
+
new b()
|
|
157
168
|
);
|
|
158
169
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
this.name = N, this.order = R;
|
|
170
|
+
Object.defineProperty(Response.prototype, "contentType", {
|
|
171
|
+
get() {
|
|
172
|
+
return this.headers.get(d);
|
|
163
173
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
* with `text/event-stream` content type and adds an `eventStream()` method to
|
|
170
|
-
* the Response object, which returns a readable stream of Server-Sent Events.
|
|
171
|
-
*
|
|
172
|
-
* @param exchange - The exchange containing the response to enhance
|
|
173
|
-
*
|
|
174
|
-
* @remarks
|
|
175
|
-
* This method executes latest among response interceptors to ensure all response
|
|
176
|
-
* processing is completed before specialized event stream functionality is added.
|
|
177
|
-
* It only enhances responses with `text/event-stream` content type, leaving other
|
|
178
|
-
* responses unchanged. The positioning at the end of the response chain ensures
|
|
179
|
-
* that all response transformations and validations are completed before event
|
|
180
|
-
* stream capabilities are added to the response object.
|
|
181
|
-
*/
|
|
182
|
-
intercept(t) {
|
|
183
|
-
const e = t.response;
|
|
184
|
-
if (!e)
|
|
185
|
-
return;
|
|
186
|
-
e.headers.get(T)?.includes(u.TEXT_EVENT_STREAM) && (e.eventStream = () => E(e), e.jsonEventStream = () => b(E(e)));
|
|
174
|
+
});
|
|
175
|
+
Object.defineProperty(Response.prototype, "isEventStream", {
|
|
176
|
+
get() {
|
|
177
|
+
const n = this.contentType;
|
|
178
|
+
return n ? n.includes(h.TEXT_EVENT_STREAM) : !1;
|
|
187
179
|
}
|
|
188
|
-
}
|
|
180
|
+
});
|
|
181
|
+
Response.prototype.eventStream = function() {
|
|
182
|
+
return this.isEventStream ? S(this) : null;
|
|
183
|
+
};
|
|
184
|
+
Response.prototype.requiredEventStream = function() {
|
|
185
|
+
const n = this.eventStream();
|
|
186
|
+
if (!n)
|
|
187
|
+
throw new u(this, `Event stream is not available. Response content-type: [${this.contentType}]`);
|
|
188
|
+
return n;
|
|
189
|
+
};
|
|
190
|
+
Response.prototype.jsonEventStream = function() {
|
|
191
|
+
const n = this.eventStream();
|
|
192
|
+
return n ? w(n) : null;
|
|
193
|
+
};
|
|
194
|
+
Response.prototype.requiredJsonEventStream = function() {
|
|
195
|
+
const n = this.jsonEventStream();
|
|
196
|
+
if (!n)
|
|
197
|
+
throw new u(this, `Event stream is not available. Response content-type: [${this.contentType}]`);
|
|
198
|
+
return n;
|
|
199
|
+
};
|
|
189
200
|
export {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
y as JsonServerSentEventTransform,
|
|
194
|
-
S as JsonServerSentEventTransformStream,
|
|
201
|
+
u as EventStreamConvertError,
|
|
202
|
+
l as JsonServerSentEventTransform,
|
|
203
|
+
b as JsonServerSentEventTransformStream,
|
|
195
204
|
i as ServerSentEventFields,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
E as ServerSentEventTransformStream,
|
|
206
|
+
T as ServerSentEventTransformer,
|
|
207
|
+
v as TextLineTransformStream,
|
|
208
|
+
m as TextLineTransformer,
|
|
209
|
+
w as toJsonServerSentEventStream,
|
|
210
|
+
S as toServerSentEventStream
|
|
202
211
|
};
|
|
203
212
|
//# 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/eventStreamInterceptor.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 * emitting each line as a separate chunk while preserving the remaining buffer\n * for the next chunk.\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 */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\nexport class ServerSentEventFields {\n static readonly ID = 'id';\n static readonly RETRY = 'retry';\n static readonly EVENT = 'event';\n static readonly DATA = 'data';\n}\n\n/**\n * Process field value\n * @param field Field name\n * @param value Field value\n * @param currentEvent Current event state\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\ninterface EventState {\n event?: string;\n id?: string;\n retry?: number;\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n */\nexport class ServerSentEventTransformer\n implements Transformer<string, ServerSentEvent> {\n // Initialize currentEvent with default values in a closure\n private currentEvent: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Transform input string chunk into ServerSentEvent object.\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.currentEvent;\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 controller.error(\n error instanceof Error ? error : new Error(String(error)),\n );\n // Reset state\n currentEvent.event = DEFAULT_EVENT_TYPE;\n currentEvent.id = undefined;\n currentEvent.retry = undefined;\n currentEvent.data = [];\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.currentEvent;\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 controller.error(\n error instanceof Error ? error : new Error(String(error)),\n );\n } finally {\n // Reset state\n currentEvent.event = DEFAULT_EVENT_TYPE;\n currentEvent.id = undefined;\n currentEvent.retry = undefined;\n currentEvent.data = [];\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * Processes the response body through a series of transform streams:\n * 1. TextDecoderStream: Decode Uint8Array data to UTF-8 strings\n * 2. TextLineStream: Split text by lines\n * 3. ServerSentEventStream: Parse line data into server-sent events\n *\n * @param response - The Response object to convert\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws Error if the response body is null\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new Error('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 { ServerSentEvent } from './serverSentEventTransformStream';\nimport { ServerSentEventStream } from './eventStreamConverter';\n\nexport interface JsonServerSentEvent<DATA>\n extends Omit<ServerSentEvent, 'data'> {\n data: DATA;\n}\n\nexport class JsonServerSentEventTransform<DATA>\n implements Transformer<ServerSentEvent, JsonServerSentEvent<DATA>> {\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\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 }\n}\n\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n constructor() {\n super(new JsonServerSentEventTransform());\n }\n}\n\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(),\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 { toServerSentEventStream } from './eventStreamConverter';\nimport {\n CONTENT_TYPE_HEADER,\n ContentTypeValues,\n FetchExchange,\n ResponseInterceptor,\n} from '@ahoo-wang/fetcher';\nimport { toJsonServerSentEventStream } from './jsonServerSentEventTransformStream';\n\n/**\n * The name of the EventStreamInterceptor.\n */\nexport const EVENT_STREAM_INTERCEPTOR_NAME = 'EventStreamInterceptor';\n\n/**\n * The order of the EventStreamInterceptor.\n * Set to Number.MAX_SAFE_INTEGER - 1000 to ensure it runs latest among response interceptors.\n */\nexport const EVENT_STREAM_INTERCEPTOR_ORDER = Number.MAX_SAFE_INTEGER - 1000;\n\n/**\n * Interceptor that enhances Response objects with event stream capabilities.\n *\n * This interceptor detects responses with `text/event-stream` content type and adds\n * an `eventStream()` method to the Response object, which returns a readable stream\n * of Server-Sent Events that can be consumed using `for await` syntax.\n *\n * @remarks\n * This interceptor runs at the very end of the response interceptor chain to ensure\n * it runs after all standard response processing is complete, as it adds\n * specialized functionality to the response object. The order is set to\n * EVENT_STREAM_INTERCEPTOR_ORDER to ensure it executes latest among response interceptors,\n * allowing for other response interceptors to run before it if needed. This positioning\n * ensures that all response processing is completed before specialized event stream\n * functionality is added to the response object.\n *\n * @example\n * ```typescript\n * // Using the eventStream method\n * const response = await fetcher.get('/events');\n * if (response.headers.get('content-type')?.includes('text/event-stream')) {\n * const eventStream = response.eventStream();\n * for await (const event of eventStream) {\n * console.log('Received event:', event);\n * }\n * }\n * ```\n */\nexport class EventStreamInterceptor implements ResponseInterceptor {\n readonly name = EVENT_STREAM_INTERCEPTOR_NAME;\n readonly order = EVENT_STREAM_INTERCEPTOR_ORDER;\n\n /**\n * Intercepts responses to add event stream capabilities.\n *\n * This method runs at the very end of the response interceptor chain to ensure\n * it runs after all standard response processing is complete. It detects responses\n * with `text/event-stream` content type and adds an `eventStream()` method to\n * the Response object, which returns a readable stream of Server-Sent Events.\n *\n * @param exchange - The exchange containing the response to enhance\n *\n * @remarks\n * This method executes latest among response interceptors to ensure all response\n * processing is completed before specialized event stream functionality is added.\n * It only enhances responses with `text/event-stream` content type, leaving other\n * responses unchanged. The positioning at the end of the response chain ensures\n * that all response transformations and validations are completed before event\n * stream capabilities are added to the response object.\n */\n intercept(exchange: FetchExchange) {\n // Check if the response is an event stream\n const response = exchange.response;\n if (!response) {\n return;\n }\n const contentType = response.headers.get(CONTENT_TYPE_HEADER);\n if (contentType?.includes(ContentTypeValues.TEXT_EVENT_STREAM)) {\n response.eventStream = () => toServerSentEventStream(response);\n response.jsonEventStream = () =>\n toJsonServerSentEventStream(toServerSentEventStream(response));\n }\n }\n}\n"],"names":["TextLineTransformer","chunk","controller","lines","line","error","TextLineTransformStream","_ServerSentEventFields","ServerSentEventFields","processFieldInternal","field","value","currentEvent","retryValue","DEFAULT_EVENT_TYPE","ServerSentEventTransformer","colonIndex","ServerSentEventTransformStream","toServerSentEventStream","response","JsonServerSentEventTransform","json","JsonServerSentEventTransformStream","toJsonServerSentEventStream","serverSentEventStream","EVENT_STREAM_INTERCEPTOR_NAME","EVENT_STREAM_INTERCEPTOR_ORDER","EventStreamInterceptor","exchange","CONTENT_TYPE_HEADER","ContentTypeValues"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/responses.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 * emitting each line as a separate chunk while preserving the remaining buffer\n * for the next chunk.\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 */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\nexport class ServerSentEventFields {\n static readonly ID = 'id';\n static readonly RETRY = 'retry';\n static readonly EVENT = 'event';\n static readonly DATA = 'data';\n}\n\n/**\n * Process field value\n * @param field Field name\n * @param value Field value\n * @param currentEvent Current event state\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\ninterface EventState {\n event?: string;\n id?: string;\n retry?: number;\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n */\nexport class ServerSentEventTransformer\n implements Transformer<string, ServerSentEvent> {\n // Initialize currentEvent with default values in a closure\n private currentEvent: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Transform input string chunk into ServerSentEvent object.\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.currentEvent;\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 controller.error(\n error instanceof Error ? error : new Error(String(error)),\n );\n // Reset state\n currentEvent.event = DEFAULT_EVENT_TYPE;\n currentEvent.id = undefined;\n currentEvent.retry = undefined;\n currentEvent.data = [];\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.currentEvent;\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 controller.error(\n error instanceof Error ? error : new Error(String(error)),\n );\n } finally {\n // Reset state\n currentEvent.event = DEFAULT_EVENT_TYPE;\n currentEvent.id = undefined;\n currentEvent.retry = undefined;\n currentEvent.data = [];\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n * Thrown when there are issues converting a Response to a ServerSentEventStream.\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n * @param response - The Response object associated with the error\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this 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 * Processes the response body through a series of transform streams:\n * 1. TextDecoderStream: Decode Uint8Array data to UTF-8 strings\n * 2. TextLineStream: Split text by lines\n * 3. ServerSentEventStream: Parse line data into server-sent events\n *\n * @param response - The Response object to convert\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws Error if the response body is null\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 { ServerSentEvent } from './serverSentEventTransformStream';\nimport { ServerSentEventStream } from './eventStreamConverter';\n\nexport interface JsonServerSentEvent<DATA>\n extends Omit<ServerSentEvent, 'data'> {\n data: DATA;\n}\n\nexport class JsonServerSentEventTransform<DATA>\n implements Transformer<ServerSentEvent, JsonServerSentEvent<DATA>> {\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\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 }\n}\n\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n constructor() {\n super(new JsonServerSentEventTransform());\n }\n}\n\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(),\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 {\n EventStreamConvertError,\n ServerSentEventStream,\n toServerSentEventStream,\n} from './eventStreamConverter';\nimport {\n JsonServerSentEventStream,\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 * @returns A ReadableStream of ServerSentEvent objects with JSON data, or null if not an event stream\n */\n jsonEventStream<DATA>(): 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 * @returns A ReadableStream of ServerSentEvent objects with JSON data\n * @throws {Error} if the event stream is not available\n */\n requiredJsonEventStream<DATA>(): JsonServerSentEventStream<DATA>;\n }\n\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 * Defines the contentType property on Response prototype.\n * This property provides a convenient way to access the Content-Type header value.\n */\nObject.defineProperty(Response.prototype, 'contentType', {\n get() {\n return this.headers.get(CONTENT_TYPE_HEADER);\n },\n});\n\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 */\nObject.defineProperty(Response.prototype, 'isEventStream', {\n get() {\n const contentType = this.contentType;\n if (!contentType) {\n return false;\n }\n return contentType.includes(ContentTypeValues.TEXT_EVENT_STREAM);\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 */\nResponse.prototype.eventStream = function() {\n if (!this.isEventStream) {\n return null;\n }\n return toServerSentEventStream(this);\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 */\nResponse.prototype.requiredEventStream = function() {\n const eventStream = this.eventStream();\n if (!eventStream) {\n throw new EventStreamConvertError(this, `Event stream is not available. Response content-type: [${this.contentType}]`);\n }\n return eventStream;\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 * @returns A JsonServerSentEventStream if the response is an event stream, null otherwise\n */\nResponse.prototype.jsonEventStream = function <DATA>() {\n const eventStream = this.eventStream();\n if (!eventStream) {\n return null;\n }\n return toJsonServerSentEventStream<DATA>(eventStream);\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 * @returns A JsonServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\nResponse.prototype.requiredJsonEventStream = function <DATA>() {\n const eventStream = this.jsonEventStream<DATA>();\n if (!eventStream) {\n throw new EventStreamConvertError(this, `Event stream is not available. Response content-type: [${this.contentType}]`);\n }\n return eventStream;\n};\n"],"names":["TextLineTransformer","chunk","controller","lines","line","error","TextLineTransformStream","_ServerSentEventFields","ServerSentEventFields","processFieldInternal","field","value","currentEvent","retryValue","DEFAULT_EVENT_TYPE","ServerSentEventTransformer","colonIndex","ServerSentEventTransformStream","EventStreamConvertError","FetcherError","response","errorMsg","cause","toServerSentEventStream","JsonServerSentEventTransform","json","JsonServerSentEventTransformStream","toJsonServerSentEventStream","serverSentEventStream","CONTENT_TYPE_HEADER","contentType","ContentTypeValues","eventStream"],"mappings":"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"}
|
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
(function(
|
|
2
|
-
`);this.buffer=
|
|
3
|
-
`),id:
|
|
4
|
-
`),id:
|
|
1
|
+
(function(s,i){typeof exports=="object"&&typeof module<"u"?i(exports,require("@ahoo-wang/fetcher")):typeof define=="function"&&define.amd?define(["exports","@ahoo-wang/fetcher"],i):(s=typeof globalThis<"u"?globalThis:s||self,i(s.FetcherEventStream={},s.Fetcher))})(this,(function(s,i){"use strict";class v{constructor(){this.buffer=""}transform(t,r){try{this.buffer+=t;const e=this.buffer.split(`
|
|
2
|
+
`);this.buffer=e.pop()||"";for(const o of e)r.enqueue(o)}catch(e){r.error(e)}}flush(t){try{this.buffer&&t.enqueue(this.buffer)}catch(r){t.error(r)}}}class h extends TransformStream{constructor(){super(new v)}}const m=class m{};m.ID="id",m.RETRY="retry",m.EVENT="event",m.DATA="data";let f=m;function b(n,t,r){switch(n){case f.EVENT:r.event=t;break;case f.DATA:r.data.push(t);break;case f.ID:r.id=t;break;case f.RETRY:{const e=parseInt(t,10);isNaN(e)||(r.retry=e);break}}}const c="message";class S{constructor(){this.currentEvent={event:c,id:void 0,retry:void 0,data:[]}}transform(t,r){const e=this.currentEvent;try{if(t.trim()===""){e.data.length>0&&(r.enqueue({event:e.event||c,data:e.data.join(`
|
|
3
|
+
`),id:e.id||"",retry:e.retry}),e.event=c,e.data=[]);return}if(t.startsWith(":"))return;const o=t.indexOf(":");let d,a;o===-1?(d=t.toLowerCase(),a=""):(d=t.substring(0,o).toLowerCase(),a=t.substring(o+1),a.startsWith(" ")&&(a=a.substring(1))),d=d.trim(),a=a.trim(),b(d,a,e)}catch(o){r.error(o instanceof Error?o:new Error(String(o))),e.event=c,e.id=void 0,e.retry=void 0,e.data=[]}}flush(t){const r=this.currentEvent;try{r.data.length>0&&t.enqueue({event:r.event||c,data:r.data.join(`
|
|
4
|
+
`),id:r.id||"",retry:r.retry})}catch(e){t.error(e instanceof Error?e:new Error(String(e)))}finally{r.event=c,r.id=void 0,r.retry=void 0,r.data=[]}}}class p extends TransformStream{constructor(){super(new S)}}class u extends i.FetcherError{constructor(t,r,e){super(r,e),this.response=t,this.name="EventStreamConvertError",Object.setPrototypeOf(this,u.prototype)}}function T(n){if(!n.body)throw new u(n,"Response body is null");return n.body.pipeThrough(new TextDecoderStream("utf-8")).pipeThrough(new h).pipeThrough(new p)}class E{transform(t,r){const e=JSON.parse(t.data);r.enqueue({data:e,event:t.event,id:t.id,retry:t.retry})}}class y extends TransformStream{constructor(){super(new E)}}function l(n){return n.pipeThrough(new y)}Object.defineProperty(Response.prototype,"contentType",{get(){return this.headers.get(i.CONTENT_TYPE_HEADER)}}),Object.defineProperty(Response.prototype,"isEventStream",{get(){const n=this.contentType;return n?n.includes(i.ContentTypeValues.TEXT_EVENT_STREAM):!1}}),Response.prototype.eventStream=function(){return this.isEventStream?T(this):null},Response.prototype.requiredEventStream=function(){const n=this.eventStream();if(!n)throw new u(this,`Event stream is not available. Response content-type: [${this.contentType}]`);return n},Response.prototype.jsonEventStream=function(){const n=this.eventStream();return n?l(n):null},Response.prototype.requiredJsonEventStream=function(){const n=this.jsonEventStream();if(!n)throw new u(this,`Event stream is not available. Response content-type: [${this.contentType}]`);return n},s.EventStreamConvertError=u,s.JsonServerSentEventTransform=E,s.JsonServerSentEventTransformStream=y,s.ServerSentEventFields=f,s.ServerSentEventTransformStream=p,s.ServerSentEventTransformer=S,s.TextLineTransformStream=h,s.TextLineTransformer=v,s.toJsonServerSentEventStream=l,s.toServerSentEventStream=T,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})}));
|
|
5
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/eventStreamInterceptor.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 * emitting each line as a separate chunk while preserving the remaining buffer\n * for the next chunk.\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 */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\nexport class ServerSentEventFields {\n static readonly ID = 'id';\n static readonly RETRY = 'retry';\n static readonly EVENT = 'event';\n static readonly DATA = 'data';\n}\n\n/**\n * Process field value\n * @param field Field name\n * @param value Field value\n * @param currentEvent Current event state\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\ninterface EventState {\n event?: string;\n id?: string;\n retry?: number;\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n */\nexport class ServerSentEventTransformer\n implements Transformer<string, ServerSentEvent> {\n // Initialize currentEvent with default values in a closure\n private currentEvent: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Transform input string chunk into ServerSentEvent object.\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.currentEvent;\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 controller.error(\n error instanceof Error ? error : new Error(String(error)),\n );\n // Reset state\n currentEvent.event = DEFAULT_EVENT_TYPE;\n currentEvent.id = undefined;\n currentEvent.retry = undefined;\n currentEvent.data = [];\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.currentEvent;\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 controller.error(\n error instanceof Error ? error : new Error(String(error)),\n );\n } finally {\n // Reset state\n currentEvent.event = DEFAULT_EVENT_TYPE;\n currentEvent.id = undefined;\n currentEvent.retry = undefined;\n currentEvent.data = [];\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Converts a Response object to a ServerSentEventStream.\n *\n * Processes the response body through a series of transform streams:\n * 1. TextDecoderStream: Decode Uint8Array data to UTF-8 strings\n * 2. TextLineStream: Split text by lines\n * 3. ServerSentEventStream: Parse line data into server-sent events\n *\n * @param response - The Response object to convert\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws Error if the response body is null\n */\nexport function toServerSentEventStream(\n response: Response,\n): ServerSentEventStream {\n if (!response.body) {\n throw new Error('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 { ServerSentEvent } from './serverSentEventTransformStream';\nimport { ServerSentEventStream } from './eventStreamConverter';\n\nexport interface JsonServerSentEvent<DATA>\n extends Omit<ServerSentEvent, 'data'> {\n data: DATA;\n}\n\nexport class JsonServerSentEventTransform<DATA>\n implements Transformer<ServerSentEvent, JsonServerSentEvent<DATA>> {\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\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 }\n}\n\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n constructor() {\n super(new JsonServerSentEventTransform());\n }\n}\n\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(),\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 { toServerSentEventStream } from './eventStreamConverter';\nimport {\n CONTENT_TYPE_HEADER,\n ContentTypeValues,\n FetchExchange,\n ResponseInterceptor,\n} from '@ahoo-wang/fetcher';\nimport { toJsonServerSentEventStream } from './jsonServerSentEventTransformStream';\n\n/**\n * The name of the EventStreamInterceptor.\n */\nexport const EVENT_STREAM_INTERCEPTOR_NAME = 'EventStreamInterceptor';\n\n/**\n * The order of the EventStreamInterceptor.\n * Set to Number.MAX_SAFE_INTEGER - 1000 to ensure it runs latest among response interceptors.\n */\nexport const EVENT_STREAM_INTERCEPTOR_ORDER = Number.MAX_SAFE_INTEGER - 1000;\n\n/**\n * Interceptor that enhances Response objects with event stream capabilities.\n *\n * This interceptor detects responses with `text/event-stream` content type and adds\n * an `eventStream()` method to the Response object, which returns a readable stream\n * of Server-Sent Events that can be consumed using `for await` syntax.\n *\n * @remarks\n * This interceptor runs at the very end of the response interceptor chain to ensure\n * it runs after all standard response processing is complete, as it adds\n * specialized functionality to the response object. The order is set to\n * EVENT_STREAM_INTERCEPTOR_ORDER to ensure it executes latest among response interceptors,\n * allowing for other response interceptors to run before it if needed. This positioning\n * ensures that all response processing is completed before specialized event stream\n * functionality is added to the response object.\n *\n * @example\n * ```typescript\n * // Using the eventStream method\n * const response = await fetcher.get('/events');\n * if (response.headers.get('content-type')?.includes('text/event-stream')) {\n * const eventStream = response.eventStream();\n * for await (const event of eventStream) {\n * console.log('Received event:', event);\n * }\n * }\n * ```\n */\nexport class EventStreamInterceptor implements ResponseInterceptor {\n readonly name = EVENT_STREAM_INTERCEPTOR_NAME;\n readonly order = EVENT_STREAM_INTERCEPTOR_ORDER;\n\n /**\n * Intercepts responses to add event stream capabilities.\n *\n * This method runs at the very end of the response interceptor chain to ensure\n * it runs after all standard response processing is complete. It detects responses\n * with `text/event-stream` content type and adds an `eventStream()` method to\n * the Response object, which returns a readable stream of Server-Sent Events.\n *\n * @param exchange - The exchange containing the response to enhance\n *\n * @remarks\n * This method executes latest among response interceptors to ensure all response\n * processing is completed before specialized event stream functionality is added.\n * It only enhances responses with `text/event-stream` content type, leaving other\n * responses unchanged. The positioning at the end of the response chain ensures\n * that all response transformations and validations are completed before event\n * stream capabilities are added to the response object.\n */\n intercept(exchange: FetchExchange) {\n // Check if the response is an event stream\n const response = exchange.response;\n if (!response) {\n return;\n }\n const contentType = response.headers.get(CONTENT_TYPE_HEADER);\n if (contentType?.includes(ContentTypeValues.TEXT_EVENT_STREAM)) {\n response.eventStream = () => toServerSentEventStream(response);\n response.jsonEventStream = () =>\n toJsonServerSentEventStream(toServerSentEventStream(response));\n }\n }\n}\n"],"names":["TextLineTransformer","chunk","controller","lines","line","error","TextLineTransformStream","_ServerSentEventFields","ServerSentEventFields","processFieldInternal","field","value","currentEvent","retryValue","DEFAULT_EVENT_TYPE","ServerSentEventTransformer","colonIndex","ServerSentEventTransformStream","toServerSentEventStream","response","JsonServerSentEventTransform","json","JsonServerSentEventTransformStream","toJsonServerSentEventStream","serverSentEventStream","EVENT_STREAM_INTERCEPTOR_NAME","EVENT_STREAM_INTERCEPTOR_ORDER","EventStreamInterceptor","exchange","CONTENT_TYPE_HEADER","ContentTypeValues"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.umd.js","sources":["../src/textLineTransformStream.ts","../src/serverSentEventTransformStream.ts","../src/eventStreamConverter.ts","../src/jsonServerSentEventTransformStream.ts","../src/responses.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 * emitting each line as a separate chunk while preserving the remaining buffer\n * for the next chunk.\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 */\nexport class TextLineTransformStream extends TransformStream<string, string> {\n constructor() {\n super(new TextLineTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents a message sent in an event stream.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format}\n */\nexport interface ServerSentEvent {\n /** The event ID to set the EventSource object's last event ID value. */\n id?: string;\n /** A string identifying the type of event described. */\n event: string;\n /** The event data */\n data: string;\n /** The reconnection interval (in milliseconds) to wait before retrying the connection */\n retry?: number;\n}\n\nexport class ServerSentEventFields {\n static readonly ID = 'id';\n static readonly RETRY = 'retry';\n static readonly EVENT = 'event';\n static readonly DATA = 'data';\n}\n\n/**\n * Process field value\n * @param field Field name\n * @param value Field value\n * @param currentEvent Current event state\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\ninterface EventState {\n event?: string;\n id?: string;\n retry?: number;\n data: string[];\n}\n\nconst DEFAULT_EVENT_TYPE = 'message';\n\n/**\n * Transformer responsible for converting a string stream into a ServerSentEvent object stream.\n *\n * Implements the Transformer interface for processing data transformation in TransformStream.\n */\nexport class ServerSentEventTransformer\n implements Transformer<string, ServerSentEvent> {\n // Initialize currentEvent with default values in a closure\n private currentEvent: EventState = {\n event: DEFAULT_EVENT_TYPE,\n id: undefined,\n retry: undefined,\n data: [],\n };\n\n /**\n * Transform input string chunk into ServerSentEvent object.\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.currentEvent;\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 controller.error(\n error instanceof Error ? error : new Error(String(error)),\n );\n // Reset state\n currentEvent.event = DEFAULT_EVENT_TYPE;\n currentEvent.id = undefined;\n currentEvent.retry = undefined;\n currentEvent.data = [];\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.currentEvent;\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 controller.error(\n error instanceof Error ? error : new Error(String(error)),\n );\n } finally {\n // Reset state\n currentEvent.event = DEFAULT_EVENT_TYPE;\n currentEvent.id = undefined;\n currentEvent.retry = undefined;\n currentEvent.data = [];\n }\n }\n}\n\n/**\n * A TransformStream that converts a stream of strings into a stream of ServerSentEvent objects.\n */\nexport class ServerSentEventTransformStream extends TransformStream<\n string,\n ServerSentEvent\n> {\n constructor() {\n super(new ServerSentEventTransformer());\n }\n}\n","/*\n * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * http://www.apache.org/licenses/LICENSE-2.0\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { TextLineTransformStream } from './textLineTransformStream';\nimport {\n ServerSentEvent,\n ServerSentEventTransformStream,\n} from './serverSentEventTransformStream';\nimport { FetcherError } from '@ahoo-wang/fetcher';\n\n/**\n * A ReadableStream of ServerSentEvent objects.\n */\nexport type ServerSentEventStream = ReadableStream<ServerSentEvent>;\n\n/**\n * Custom error class for event stream conversion errors.\n * Thrown when there are issues converting a Response to a ServerSentEventStream.\n */\nexport class EventStreamConvertError extends FetcherError {\n /**\n * Creates a new EventStreamConvertError instance.\n * @param response - The Response object associated with the error\n * @param errorMsg - Optional error message describing what went wrong during conversion\n * @param cause - Optional underlying error that caused this 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 * Processes the response body through a series of transform streams:\n * 1. TextDecoderStream: Decode Uint8Array data to UTF-8 strings\n * 2. TextLineStream: Split text by lines\n * 3. ServerSentEventStream: Parse line data into server-sent events\n *\n * @param response - The Response object to convert\n * @returns A ReadableStream of ServerSentEvent objects\n * @throws Error if the response body is null\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 { ServerSentEvent } from './serverSentEventTransformStream';\nimport { ServerSentEventStream } from './eventStreamConverter';\n\nexport interface JsonServerSentEvent<DATA>\n extends Omit<ServerSentEvent, 'data'> {\n data: DATA;\n}\n\nexport class JsonServerSentEventTransform<DATA>\n implements Transformer<ServerSentEvent, JsonServerSentEvent<DATA>> {\n transform(\n chunk: ServerSentEvent,\n controller: TransformStreamDefaultController<JsonServerSentEvent<DATA>>,\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 }\n}\n\nexport class JsonServerSentEventTransformStream<DATA> extends TransformStream<\n ServerSentEvent,\n JsonServerSentEvent<DATA>\n> {\n constructor() {\n super(new JsonServerSentEventTransform());\n }\n}\n\nexport type JsonServerSentEventStream<DATA> = ReadableStream<\n JsonServerSentEvent<DATA>\n>;\n\nexport function toJsonServerSentEventStream<DATA>(\n serverSentEventStream: ServerSentEventStream,\n): JsonServerSentEventStream<DATA> {\n return serverSentEventStream.pipeThrough(\n new JsonServerSentEventTransformStream<DATA>(),\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 {\n EventStreamConvertError,\n ServerSentEventStream,\n toServerSentEventStream,\n} from './eventStreamConverter';\nimport {\n JsonServerSentEventStream,\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 * @returns A ReadableStream of ServerSentEvent objects with JSON data, or null if not an event stream\n */\n jsonEventStream<DATA>(): 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 * @returns A ReadableStream of ServerSentEvent objects with JSON data\n * @throws {Error} if the event stream is not available\n */\n requiredJsonEventStream<DATA>(): JsonServerSentEventStream<DATA>;\n }\n\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 * Defines the contentType property on Response prototype.\n * This property provides a convenient way to access the Content-Type header value.\n */\nObject.defineProperty(Response.prototype, 'contentType', {\n get() {\n return this.headers.get(CONTENT_TYPE_HEADER);\n },\n});\n\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 */\nObject.defineProperty(Response.prototype, 'isEventStream', {\n get() {\n const contentType = this.contentType;\n if (!contentType) {\n return false;\n }\n return contentType.includes(ContentTypeValues.TEXT_EVENT_STREAM);\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 */\nResponse.prototype.eventStream = function() {\n if (!this.isEventStream) {\n return null;\n }\n return toServerSentEventStream(this);\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 */\nResponse.prototype.requiredEventStream = function() {\n const eventStream = this.eventStream();\n if (!eventStream) {\n throw new EventStreamConvertError(this, `Event stream is not available. Response content-type: [${this.contentType}]`);\n }\n return eventStream;\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 * @returns A JsonServerSentEventStream if the response is an event stream, null otherwise\n */\nResponse.prototype.jsonEventStream = function <DATA>() {\n const eventStream = this.eventStream();\n if (!eventStream) {\n return null;\n }\n return toJsonServerSentEventStream<DATA>(eventStream);\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 * @returns A JsonServerSentEventStream if the response is an event stream\n * @throws {Error} if the response is not an event stream\n */\nResponse.prototype.requiredJsonEventStream = function <DATA>() {\n const eventStream = this.jsonEventStream<DATA>();\n if (!eventStream) {\n throw new EventStreamConvertError(this, `Event stream is not available. Response content-type: [${this.contentType}]`);\n }\n return eventStream;\n};\n"],"names":["TextLineTransformer","chunk","controller","lines","line","error","TextLineTransformStream","_ServerSentEventFields","ServerSentEventFields","processFieldInternal","field","value","currentEvent","retryValue","DEFAULT_EVENT_TYPE","ServerSentEventTransformer","colonIndex","ServerSentEventTransformStream","EventStreamConvertError","FetcherError","response","errorMsg","cause","toServerSentEventStream","JsonServerSentEventTransform","json","JsonServerSentEventTransformStream","toJsonServerSentEventStream","serverSentEventStream","CONTENT_TYPE_HEADER","contentType","ContentTypeValues","eventStream"],"mappings":"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"}
|