@ahoo-wang/fetcher-eventstream 1.0.8 → 1.2.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 +111 -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
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ APIs.
|
|
|
23
23
|
- **💬 Comment Handling**: Properly ignores comment lines (lines starting with `:`) as per SSE specification
|
|
24
24
|
- **🛡️ TypeScript Support**: Complete TypeScript type definitions
|
|
25
25
|
- **⚡ Performance Optimized**: Efficient parsing and streaming for high-performance applications
|
|
26
|
+
- **🤖 LLM Streaming Ready**: Native support for streaming responses from popular LLM APIs like OpenAI GPT, Claude, etc.
|
|
26
27
|
|
|
27
28
|
## 🚀 Quick Start
|
|
28
29
|
|
|
@@ -39,6 +40,26 @@ pnpm add @ahoo-wang/fetcher-eventstream
|
|
|
39
40
|
yarn add @ahoo-wang/fetcher-eventstream
|
|
40
41
|
```
|
|
41
42
|
|
|
43
|
+
### Module Import
|
|
44
|
+
|
|
45
|
+
To use the event stream functionality, you need to import the module for its side effects:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This import automatically extends the `Response` interface with methods for handling Server-Sent Events streams:
|
|
52
|
+
|
|
53
|
+
- `eventStream()` - Converts a Response with `text/event-stream` content type to a `ServerSentEventStream`
|
|
54
|
+
- `jsonEventStream<DATA>()` - Converts a Response with `text/event-stream` content type to a
|
|
55
|
+
`JsonServerSentEventStream<DATA>`
|
|
56
|
+
- `isEventStream` getter - Checks if the Response has a `text/event-stream` content type
|
|
57
|
+
- `requiredEventStream()` - Gets a `ServerSentEventStream`, throwing an error if not available
|
|
58
|
+
- `requiredJsonEventStream<DATA>()` - Gets a `JsonServerSentEventStream<DATA>`, throwing an error if not available
|
|
59
|
+
|
|
60
|
+
This is a common pattern in JavaScript/TypeScript for extending existing types with additional functionality without
|
|
61
|
+
modifying the original type definitions.
|
|
62
|
+
|
|
42
63
|
### Integration Test Example: LLM Client with Event Stream
|
|
43
64
|
|
|
44
65
|
The following example shows how to create an LLM client with event stream support, similar to the integration test in
|
|
@@ -64,10 +85,8 @@ import {
|
|
|
64
85
|
post,
|
|
65
86
|
ResultExtractors,
|
|
66
87
|
} from '@ahoo-wang/fetcher-decorator';
|
|
67
|
-
import
|
|
68
|
-
|
|
69
|
-
JsonServerSentEventStream,
|
|
70
|
-
} from '@ahoo-wang/fetcher-eventstream';
|
|
88
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
89
|
+
import { JsonServerSentEventStream } from '@ahoo-wang/fetcher-eventstream';
|
|
71
90
|
import { ChatRequest, ChatResponse } from './types';
|
|
72
91
|
|
|
73
92
|
export const llmFetcherName = 'llm';
|
|
@@ -100,7 +119,6 @@ export function createLlmFetcher(options: LlmOptions): NamedFetcher {
|
|
|
100
119
|
},
|
|
101
120
|
});
|
|
102
121
|
llmFetcher.interceptors.request.use(new LlmRequestInterceptor(options));
|
|
103
|
-
llmFetcher.interceptors.response.use(new EventStreamInterceptor());
|
|
104
122
|
return llmFetcher;
|
|
105
123
|
}
|
|
106
124
|
|
|
@@ -111,14 +129,14 @@ export function createLlmFetcher(options: LlmOptions): NamedFetcher {
|
|
|
111
129
|
export class LlmClient {
|
|
112
130
|
@post('/completions')
|
|
113
131
|
streamChat(
|
|
114
|
-
@body()
|
|
132
|
+
@body() body: ChatRequest,
|
|
115
133
|
): Promise<JsonServerSentEventStream<ChatResponse>> {
|
|
116
|
-
throw autoGeneratedError();
|
|
134
|
+
throw autoGeneratedError(body);
|
|
117
135
|
}
|
|
118
136
|
|
|
119
137
|
@post('/completions', { resultExtractor: ResultExtractors.Json })
|
|
120
|
-
chat(@body()
|
|
121
|
-
throw autoGeneratedError();
|
|
138
|
+
chat(@body() body: ChatRequest): Promise<ChatResponse> {
|
|
139
|
+
throw autoGeneratedError(body);
|
|
122
140
|
}
|
|
123
141
|
}
|
|
124
142
|
```
|
|
@@ -203,33 +221,27 @@ try {
|
|
|
203
221
|
}
|
|
204
222
|
```
|
|
205
223
|
|
|
206
|
-
### Basic Usage
|
|
224
|
+
### Basic Usage
|
|
207
225
|
|
|
208
226
|
```typescript
|
|
209
227
|
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
210
|
-
import
|
|
228
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
211
229
|
|
|
212
230
|
const fetcher = new Fetcher({
|
|
213
231
|
baseURL: 'https://api.example.com',
|
|
214
232
|
});
|
|
215
233
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// Using the eventStream method on responses with text/event-stream content type
|
|
234
|
+
// In responses with text/event-stream content type,
|
|
235
|
+
// Response objects will automatically have eventStream() and jsonEventStream() methods
|
|
220
236
|
const response = await fetcher.get('/events');
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
console.log('Received event:', event);
|
|
224
|
-
}
|
|
237
|
+
for await (const event of response.requiredEventStream()) {
|
|
238
|
+
console.log('Received event:', event);
|
|
225
239
|
}
|
|
226
240
|
|
|
227
|
-
// Using
|
|
241
|
+
// Using jsonEventStream for JSON data
|
|
228
242
|
const jsonResponse = await fetcher.get('/json-events');
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
console.log('Received JSON event:', event.data);
|
|
232
|
-
}
|
|
243
|
+
for await (const event of response.requiredJsonEventStream<MyDataType>()) {
|
|
244
|
+
console.log('Received JSON event:', event.data);
|
|
233
245
|
}
|
|
234
246
|
```
|
|
235
247
|
|
|
@@ -257,20 +269,48 @@ try {
|
|
|
257
269
|
|
|
258
270
|
## 📚 API Reference
|
|
259
271
|
|
|
260
|
-
###
|
|
272
|
+
### Module Import
|
|
273
|
+
|
|
274
|
+
To use the event stream functionality, you need to import the module for its side effects:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
This import automatically extends the global `Response` interface with methods for handling Server-Sent Events streams:
|
|
261
281
|
|
|
262
|
-
|
|
263
|
-
type
|
|
282
|
+
- `eventStream()` - Converts a Response with `text/event-stream` content type to a `ServerSentEventStream`
|
|
283
|
+
- `jsonEventStream<DATA>()` - Converts a Response with `text/event-stream` content type to a
|
|
284
|
+
`JsonServerSentEventStream<DATA>`
|
|
285
|
+
- `isEventStream` getter - Checks if the Response has a `text/event-stream` content type
|
|
286
|
+
- `requiredEventStream()` - Gets a `ServerSentEventStream`, throwing an error if not available
|
|
287
|
+
- `requiredJsonEventStream<DATA>()` - Gets a `JsonServerSentEventStream<DATA>`, throwing an error if not available
|
|
264
288
|
|
|
265
|
-
|
|
289
|
+
This is a common pattern in JavaScript/TypeScript for extending existing types with additional functionality without
|
|
290
|
+
modifying the original type definitions.
|
|
291
|
+
|
|
292
|
+
In integration tests and real applications, this import is essential for working with event streams. For example:
|
|
266
293
|
|
|
267
294
|
```typescript
|
|
268
|
-
fetcher
|
|
295
|
+
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
296
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
297
|
+
|
|
298
|
+
const fetcher = new Fetcher({
|
|
299
|
+
baseURL: 'https://api.example.com',
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Response objects will automatically have eventStream() and jsonEventStream() methods
|
|
303
|
+
const response = await fetcher.get('/events');
|
|
304
|
+
// Handle event stream
|
|
305
|
+
for await (const event of response.requiredEventStream()) {
|
|
306
|
+
console.log('Received event:', event);
|
|
307
|
+
}
|
|
269
308
|
```
|
|
270
309
|
|
|
271
310
|
### toJsonServerSentEventStream
|
|
272
311
|
|
|
273
|
-
Converts a ServerSentEventStream to a JsonServerSentEventStream for
|
|
312
|
+
Converts a `ServerSentEventStream` to a `JsonServerSentEventStream<DATA>` for handling Server-Sent Events with JSON
|
|
313
|
+
data.
|
|
274
314
|
|
|
275
315
|
#### Signature
|
|
276
316
|
|
|
@@ -282,11 +322,11 @@ function toJsonServerSentEventStream<DATA>(
|
|
|
282
322
|
|
|
283
323
|
#### Parameters
|
|
284
324
|
|
|
285
|
-
- `serverSentEventStream`: The ServerSentEventStream to convert
|
|
325
|
+
- `serverSentEventStream`: The `ServerSentEventStream` to convert
|
|
286
326
|
|
|
287
327
|
#### Returns
|
|
288
328
|
|
|
289
|
-
- `JsonServerSentEventStream<DATA>`: A readable stream of
|
|
329
|
+
- `JsonServerSentEventStream<DATA>`: A readable stream of `JsonServerSentEvent<DATA>` objects
|
|
290
330
|
|
|
291
331
|
### JsonServerSentEvent
|
|
292
332
|
|
|
@@ -294,13 +334,13 @@ Interface defining the structure of a Server-Sent Event with JSON data.
|
|
|
294
334
|
|
|
295
335
|
```typescript
|
|
296
336
|
interface JsonServerSentEvent<DATA> extends Omit<ServerSentEvent, 'data'> {
|
|
297
|
-
data: DATA; //
|
|
337
|
+
data: DATA; // The event data parsed as JSON
|
|
298
338
|
}
|
|
299
339
|
```
|
|
300
340
|
|
|
301
341
|
### JsonServerSentEventStream
|
|
302
342
|
|
|
303
|
-
Type alias for a readable stream of
|
|
343
|
+
Type alias for a readable stream of `JsonServerSentEvent<DATA>` objects.
|
|
304
344
|
|
|
305
345
|
```typescript
|
|
306
346
|
type JsonServerSentEventStream<DATA> = ReadableStream<
|
|
@@ -310,7 +350,7 @@ type JsonServerSentEventStream<DATA> = ReadableStream<
|
|
|
310
350
|
|
|
311
351
|
### toServerSentEventStream
|
|
312
352
|
|
|
313
|
-
Converts a Response object with a `text/event-stream` body to a
|
|
353
|
+
Converts a `Response` object with a `text/event-stream` body to a `ServerSentEventStream`.
|
|
314
354
|
|
|
315
355
|
#### Signature
|
|
316
356
|
|
|
@@ -320,11 +360,11 @@ function toServerSentEventStream(response: Response): ServerSentEventStream;
|
|
|
320
360
|
|
|
321
361
|
#### Parameters
|
|
322
362
|
|
|
323
|
-
- `response`:
|
|
363
|
+
- `response`: An HTTP response with `text/event-stream` content type
|
|
324
364
|
|
|
325
365
|
#### Returns
|
|
326
366
|
|
|
327
|
-
- `ServerSentEventStream`: A readable stream of ServerSentEvent objects
|
|
367
|
+
- `ServerSentEventStream`: A readable stream of `ServerSentEvent` objects
|
|
328
368
|
|
|
329
369
|
### ServerSentEvent
|
|
330
370
|
|
|
@@ -332,16 +372,16 @@ Interface defining the structure of a Server-Sent Event.
|
|
|
332
372
|
|
|
333
373
|
```typescript
|
|
334
374
|
interface ServerSentEvent {
|
|
335
|
-
data: string; //
|
|
336
|
-
event?: string; //
|
|
337
|
-
id?: string; //
|
|
338
|
-
retry?: number; //
|
|
375
|
+
data: string; // The event data (required)
|
|
376
|
+
event?: string; // The event type (optional, defaults to 'message')
|
|
377
|
+
id?: string; // The event ID (optional)
|
|
378
|
+
retry?: number; // The reconnection time in milliseconds (optional)
|
|
339
379
|
}
|
|
340
380
|
```
|
|
341
381
|
|
|
342
382
|
### ServerSentEventStream
|
|
343
383
|
|
|
344
|
-
Type alias for a readable stream of ServerSentEvent objects.
|
|
384
|
+
Type alias for a readable stream of `ServerSentEvent` objects.
|
|
345
385
|
|
|
346
386
|
```typescript
|
|
347
387
|
type ServerSentEventStream = ReadableStream<ServerSentEvent>;
|
|
@@ -353,30 +393,27 @@ type ServerSentEventStream = ReadableStream<ServerSentEvent>;
|
|
|
353
393
|
|
|
354
394
|
```typescript
|
|
355
395
|
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
356
|
-
import
|
|
396
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
357
397
|
|
|
358
398
|
const fetcher = new Fetcher({
|
|
359
399
|
baseURL: 'https://api.example.com',
|
|
360
400
|
});
|
|
361
|
-
fetcher.interceptors.response.use(new EventStreamInterceptor());
|
|
362
401
|
|
|
363
402
|
// Listen for real-time notifications
|
|
364
403
|
const response = await fetcher.get('/notifications');
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
console.log('Unknown event:', event);
|
|
379
|
-
}
|
|
404
|
+
for await (const event of response.requiredEventStream()) {
|
|
405
|
+
switch (event.event) {
|
|
406
|
+
case 'message':
|
|
407
|
+
showNotification('Message', event.data);
|
|
408
|
+
break;
|
|
409
|
+
case 'alert':
|
|
410
|
+
showAlert('Alert', event.data);
|
|
411
|
+
break;
|
|
412
|
+
case 'update':
|
|
413
|
+
handleUpdate(JSON.parse(event.data));
|
|
414
|
+
break;
|
|
415
|
+
default:
|
|
416
|
+
console.log('Unknown event:', event);
|
|
380
417
|
}
|
|
381
418
|
}
|
|
382
419
|
```
|
|
@@ -385,24 +422,20 @@ if (response.eventStream) {
|
|
|
385
422
|
|
|
386
423
|
```typescript
|
|
387
424
|
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
388
|
-
import { EventStreamInterceptor } from '@ahoo-wang/fetcher-eventstream';
|
|
389
425
|
|
|
390
426
|
const fetcher = new Fetcher({
|
|
391
427
|
baseURL: 'https://api.example.com',
|
|
392
428
|
});
|
|
393
|
-
fetcher.interceptors.response.use(new EventStreamInterceptor());
|
|
394
429
|
|
|
395
430
|
// Track long-running task progress
|
|
396
431
|
const response = await fetcher.get('/tasks/123/progress');
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
432
|
+
for await (const event of response.requiredEventStream()) {
|
|
433
|
+
if (event.event === 'progress') {
|
|
434
|
+
const progress = JSON.parse(event.data);
|
|
435
|
+
updateProgressBar(progress.percentage);
|
|
436
|
+
} else if (event.event === 'complete') {
|
|
437
|
+
showCompletionMessage(event.data);
|
|
438
|
+
break;
|
|
406
439
|
}
|
|
407
440
|
}
|
|
408
441
|
```
|
|
@@ -411,25 +444,21 @@ if (response.eventStream) {
|
|
|
411
444
|
|
|
412
445
|
```typescript
|
|
413
446
|
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
414
|
-
import { EventStreamInterceptor } from '@ahoo-wang/fetcher-eventstream';
|
|
415
447
|
|
|
416
448
|
const fetcher = new Fetcher({
|
|
417
449
|
baseURL: 'https://chat-api.example.com',
|
|
418
450
|
});
|
|
419
|
-
fetcher.interceptors.response.use(new EventStreamInterceptor());
|
|
420
451
|
|
|
421
452
|
// Real-time chat messages
|
|
422
453
|
const response = await fetcher.get('/rooms/123/messages');
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
showUserLeft(event.data);
|
|
432
|
-
}
|
|
454
|
+
for await (const event of response.requiredEventStream()) {
|
|
455
|
+
if (event.event === 'message') {
|
|
456
|
+
const message = JSON.parse(event.data);
|
|
457
|
+
displayMessage(message);
|
|
458
|
+
} else if (event.event === 'user-joined') {
|
|
459
|
+
showUserJoined(event.data);
|
|
460
|
+
} else if (event.event === 'user-left') {
|
|
461
|
+
showUserLeft(event.data);
|
|
433
462
|
}
|
|
434
463
|
}
|
|
435
464
|
```
|
package/README.zh-CN.md
CHANGED
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
## 🌟 特性
|
|
14
14
|
|
|
15
15
|
- **📡 事件流转换**:将 `text/event-stream` 响应转换为 `ServerSentEvent` 对象的异步生成器
|
|
16
|
-
- **🔌
|
|
16
|
+
- **🔌 自动扩展**:模块导入时自动扩展 `Response` 原型,添加事件流方法
|
|
17
17
|
- **📋 SSE 解析**:根据规范解析服务器发送事件,包括数据、事件、ID 和重试字段
|
|
18
18
|
- **🔄 流支持**:正确处理分块数据和多行事件
|
|
19
19
|
- **💬 注释处理**:正确忽略注释行(以 `:` 开头的行)
|
|
20
20
|
- **🛡️ TypeScript 支持**:完整的 TypeScript 类型定义
|
|
21
21
|
- **⚡ 性能优化**:高效的解析和流处理,适用于高性能应用
|
|
22
|
+
- **🤖 LLM 流准备就绪**: 原生支持来自流行 LLM API(如 OpenAI GPT、Claude 等)的流式响应
|
|
22
23
|
|
|
23
24
|
## 🚀 快速开始
|
|
24
25
|
|
|
@@ -35,6 +36,24 @@ pnpm add @ahoo-wang/fetcher-eventstream
|
|
|
35
36
|
yarn add @ahoo-wang/fetcher-eventstream
|
|
36
37
|
```
|
|
37
38
|
|
|
39
|
+
### 模块导入
|
|
40
|
+
|
|
41
|
+
要使用事件流功能,您需要导入模块以执行其副作用:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
此导入会自动扩展 `Response` 接口以处理服务器发送事件流:
|
|
48
|
+
|
|
49
|
+
- `eventStream()` - 将带有 `text/event-stream` 内容类型的响应转换为 `ServerSentEventStream`
|
|
50
|
+
- `jsonEventStream<DATA>()` - 将带有 `text/event-stream` 内容类型的响应转换为 `JsonServerSentEventStream<DATA>`
|
|
51
|
+
- `isEventStream` getter - 检查响应是否具有 `text/event-stream` 内容类型
|
|
52
|
+
- `requiredEventStream()` - 获取 `ServerSentEventStream`,如果不可用则抛出错误
|
|
53
|
+
- `requiredJsonEventStream<DATA>()` - 获取 `JsonServerSentEventStream<DATA>`,如果不可用则抛出错误
|
|
54
|
+
|
|
55
|
+
这是 JavaScript/TypeScript 中常见的模式,用于在不修改原始类型定义的情况下扩展现有类型的功能。
|
|
56
|
+
|
|
38
57
|
### 集成测试示例:带事件流的 LLM 客户端
|
|
39
58
|
|
|
40
59
|
以下示例展示了如何创建带事件流支持的 LLM 客户端,类似于 Fetcher
|
|
@@ -59,10 +78,8 @@ import {
|
|
|
59
78
|
post,
|
|
60
79
|
ResultExtractors,
|
|
61
80
|
} from '@ahoo-wang/fetcher-decorator';
|
|
62
|
-
import
|
|
63
|
-
|
|
64
|
-
JsonServerSentEventStream,
|
|
65
|
-
} from '@ahoo-wang/fetcher-eventstream';
|
|
81
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
82
|
+
import { JsonServerSentEventStream } from '@ahoo-wang/fetcher-eventstream';
|
|
66
83
|
import { ChatRequest, ChatResponse } from './types';
|
|
67
84
|
|
|
68
85
|
export const llmFetcherName = 'llm';
|
|
@@ -95,7 +112,6 @@ export function createLlmFetcher(options: LlmOptions): NamedFetcher {
|
|
|
95
112
|
},
|
|
96
113
|
});
|
|
97
114
|
llmFetcher.interceptors.request.use(new LlmRequestInterceptor(options));
|
|
98
|
-
llmFetcher.interceptors.response.use(new EventStreamInterceptor());
|
|
99
115
|
return llmFetcher;
|
|
100
116
|
}
|
|
101
117
|
|
|
@@ -106,14 +122,14 @@ export function createLlmFetcher(options: LlmOptions): NamedFetcher {
|
|
|
106
122
|
export class LlmClient {
|
|
107
123
|
@post('/completions')
|
|
108
124
|
streamChat(
|
|
109
|
-
@body()
|
|
125
|
+
@body() body: ChatRequest,
|
|
110
126
|
): Promise<JsonServerSentEventStream<ChatResponse>> {
|
|
111
|
-
throw autoGeneratedError();
|
|
127
|
+
throw autoGeneratedError(body);
|
|
112
128
|
}
|
|
113
129
|
|
|
114
130
|
@post('/completions', { resultExtractor: ResultExtractors.Json })
|
|
115
|
-
chat(@body()
|
|
116
|
-
throw autoGeneratedError();
|
|
131
|
+
chat(@body() body: ChatRequest): Promise<ChatResponse> {
|
|
132
|
+
throw autoGeneratedError(body);
|
|
117
133
|
}
|
|
118
134
|
}
|
|
119
135
|
```
|
|
@@ -176,33 +192,27 @@ function updateUI(content: string) {
|
|
|
176
192
|
}
|
|
177
193
|
```
|
|
178
194
|
|
|
179
|
-
###
|
|
195
|
+
### 基本用法
|
|
180
196
|
|
|
181
197
|
```typescript
|
|
182
198
|
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
183
|
-
import
|
|
199
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
184
200
|
|
|
185
201
|
const fetcher = new Fetcher({
|
|
186
202
|
baseURL: 'https://api.example.com',
|
|
187
203
|
});
|
|
188
204
|
|
|
189
|
-
// 添加事件流拦截器
|
|
190
|
-
fetcher.interceptors.response.use(new EventStreamInterceptor());
|
|
191
|
-
|
|
192
205
|
// 在响应中使用 eventStream 方法处理 text/event-stream 内容类型
|
|
206
|
+
// Response 对象将自动具有 eventStream() 和 jsonEventStream() 方法
|
|
193
207
|
const response = await fetcher.get('/events');
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
console.log('收到事件:', event);
|
|
197
|
-
}
|
|
208
|
+
for await (const event of response.requiredEventStream()) {
|
|
209
|
+
console.log('收到事件:', event);
|
|
198
210
|
}
|
|
199
211
|
|
|
200
212
|
// 使用 jsonEventStream 方法处理 JSON 数据
|
|
201
213
|
const jsonResponse = await fetcher.get('/json-events');
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
console.log('收到 JSON 事件:', event.data);
|
|
205
|
-
}
|
|
214
|
+
for await (const event of response.requiredJsonEventStream<MyDataType>()) {
|
|
215
|
+
console.log('收到 JSON 事件:', event.data);
|
|
206
216
|
}
|
|
207
217
|
```
|
|
208
218
|
|
|
@@ -230,19 +240,45 @@ try {
|
|
|
230
240
|
|
|
231
241
|
## 📚 API 参考
|
|
232
242
|
|
|
233
|
-
###
|
|
243
|
+
### 模块导入
|
|
244
|
+
|
|
245
|
+
要使用事件流功能,您需要导入模块以执行其副作用:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
此导入会自动扩展全局 `Response` 接口以处理服务器发送事件流:
|
|
234
252
|
|
|
235
|
-
|
|
253
|
+
- `eventStream()` - 将带有 `text/event-stream` 内容类型的响应转换为 `ServerSentEventStream`
|
|
254
|
+
- `jsonEventStream<DATA>()` - 将带有 `text/event-stream` 内容类型的响应转换为 `JsonServerSentEventStream<DATA>`
|
|
255
|
+
- `isEventStream` getter - 检查响应是否具有 `text/event-stream` 内容类型
|
|
256
|
+
- `requiredEventStream()` - 获取 `ServerSentEventStream`,如果不可用则抛出错误
|
|
257
|
+
- `requiredJsonEventStream<DATA>()` - 获取 `JsonServerSentEventStream<DATA>`,如果不可用则抛出错误
|
|
236
258
|
|
|
237
|
-
|
|
259
|
+
这是 JavaScript/TypeScript 中常见的模式,用于在不修改原始类型定义的情况下扩展现有类型的功能。
|
|
260
|
+
|
|
261
|
+
在集成测试和实际应用中,此导入对于处理事件流至关重要。例如:
|
|
238
262
|
|
|
239
263
|
```typescript
|
|
240
|
-
fetcher
|
|
264
|
+
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
265
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
266
|
+
|
|
267
|
+
const fetcher = new Fetcher({
|
|
268
|
+
baseURL: 'https://api.example.com',
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Response 对象将自动具有 eventStream() 和 jsonEventStream() 方法
|
|
272
|
+
const response = await fetcher.get('/events');
|
|
273
|
+
// 处理事件流
|
|
274
|
+
for await (const event of response.requiredEventStream()) {
|
|
275
|
+
console.log('收到事件:', event);
|
|
276
|
+
}
|
|
241
277
|
```
|
|
242
278
|
|
|
243
279
|
### toJsonServerSentEventStream
|
|
244
280
|
|
|
245
|
-
将 ServerSentEventStream 转换为 JsonServerSentEventStream
|
|
281
|
+
将 `ServerSentEventStream` 转换为 `JsonServerSentEventStream<DATA>`,用于处理带有 JSON 数据的服务器发送事件。
|
|
246
282
|
|
|
247
283
|
#### 签名
|
|
248
284
|
|
|
@@ -325,30 +361,27 @@ type ServerSentEventStream = ReadableStream<ServerSentEvent>;
|
|
|
325
361
|
|
|
326
362
|
```typescript
|
|
327
363
|
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
328
|
-
import
|
|
364
|
+
import '@ahoo-wang/fetcher-eventstream';
|
|
329
365
|
|
|
330
366
|
const fetcher = new Fetcher({
|
|
331
367
|
baseURL: 'https://api.example.com',
|
|
332
368
|
});
|
|
333
|
-
fetcher.interceptors.response.use(new EventStreamInterceptor());
|
|
334
369
|
|
|
335
370
|
// 监听实时通知
|
|
336
371
|
const response = await fetcher.get('/notifications');
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
console.log('未知事件:', event);
|
|
351
|
-
}
|
|
372
|
+
for await (const event of response.requiredEventStream()) {
|
|
373
|
+
switch (event.event) {
|
|
374
|
+
case 'message':
|
|
375
|
+
showNotification('消息', event.data);
|
|
376
|
+
break;
|
|
377
|
+
case 'alert':
|
|
378
|
+
showAlert('警报', event.data);
|
|
379
|
+
break;
|
|
380
|
+
case 'update':
|
|
381
|
+
handleUpdate(JSON.parse(event.data));
|
|
382
|
+
break;
|
|
383
|
+
default:
|
|
384
|
+
console.log('未知事件:', event);
|
|
352
385
|
}
|
|
353
386
|
}
|
|
354
387
|
```
|
|
@@ -357,24 +390,20 @@ if (response.eventStream) {
|
|
|
357
390
|
|
|
358
391
|
```typescript
|
|
359
392
|
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
360
|
-
import { EventStreamInterceptor } from '@ahoo-wang/fetcher-eventstream';
|
|
361
393
|
|
|
362
394
|
const fetcher = new Fetcher({
|
|
363
395
|
baseURL: 'https://api.example.com',
|
|
364
396
|
});
|
|
365
|
-
fetcher.interceptors.response.use(new EventStreamInterceptor());
|
|
366
397
|
|
|
367
398
|
// 跟踪长时间运行的任务进度
|
|
368
399
|
const response = await fetcher.get('/tasks/123/progress');
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
break;
|
|
377
|
-
}
|
|
400
|
+
for await (const event of response.requiredEventStream()) {
|
|
401
|
+
if (event.event === 'progress') {
|
|
402
|
+
const progress = JSON.parse(event.data);
|
|
403
|
+
updateProgressBar(progress.percentage);
|
|
404
|
+
} else if (event.event === 'complete') {
|
|
405
|
+
showCompletionMessage(event.data);
|
|
406
|
+
break;
|
|
378
407
|
}
|
|
379
408
|
}
|
|
380
409
|
```
|
|
@@ -383,25 +412,21 @@ if (response.eventStream) {
|
|
|
383
412
|
|
|
384
413
|
```typescript
|
|
385
414
|
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
386
|
-
import { EventStreamInterceptor } from '@ahoo-wang/fetcher-eventstream';
|
|
387
415
|
|
|
388
416
|
const fetcher = new Fetcher({
|
|
389
417
|
baseURL: 'https://chat-api.example.com',
|
|
390
418
|
});
|
|
391
|
-
fetcher.interceptors.response.use(new EventStreamInterceptor());
|
|
392
419
|
|
|
393
420
|
// 实时聊天消息
|
|
394
421
|
const response = await fetcher.get('/rooms/123/messages');
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
showUserLeft(event.data);
|
|
404
|
-
}
|
|
422
|
+
for await (const event of response.requiredEventStream()) {
|
|
423
|
+
if (event.event === 'message') {
|
|
424
|
+
const message = JSON.parse(event.data);
|
|
425
|
+
displayMessage(message);
|
|
426
|
+
} else if (event.event === 'user-joined') {
|
|
427
|
+
showUserJoined(event.data);
|
|
428
|
+
} else if (event.event === 'user-left') {
|
|
429
|
+
showUserLeft(event.data);
|
|
405
430
|
}
|
|
406
431
|
}
|
|
407
432
|
```
|
|
@@ -413,17 +438,16 @@ if (response.eventStream) {
|
|
|
413
438
|
pnpm test
|
|
414
439
|
|
|
415
440
|
# 运行带覆盖率的测试
|
|
416
|
-
pnpm test --coverage
|
|
441
|
+
pnpm test -- --coverage
|
|
417
442
|
```
|
|
418
443
|
|
|
419
444
|
测试套件包括:
|
|
420
445
|
|
|
421
446
|
- 事件流转换测试
|
|
422
|
-
-
|
|
423
|
-
- 边界情况处理(畸形事件、分块数据等)
|
|
447
|
+
- 边缘情况处理(格式错误的事件、分块数据等)
|
|
424
448
|
- 大事件流的性能测试
|
|
425
449
|
|
|
426
|
-
## 📋
|
|
450
|
+
## 📋 服务器发送事件规范兼容性
|
|
427
451
|
|
|
428
452
|
此包完全实现了 [服务器发送事件规范](https://html.spec.whatwg.org/multipage/server-sent-events.html):
|
|
429
453
|
|