File size: 2,955 Bytes
9ada4bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import * as utils from './utils.js';
/**
 * Convert a `Response` body containing Server Sent Events (SSE) into an Async Iterator that yields {@linkcode ServerSentEventMessage} objects.
 *
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events}
 *
 * @example
 * ```js
 * // Optional
 * let abort = new AbortController;
 *
 * // Manually fetch a Response
 * let res = await fetch('https://...', {
 *   method: 'POST',
 *   signal: abort.signal,
 *   headers: {
 *     'api-key': 'token <value>',
 *     'content-type': 'application/json',
 *   },
 *   body: JSON.stringify({
 *     stream: true, // <- hypothetical
 *     // ...
 *   })
 * });
 *
 * if (res.ok) {
 *   let stream = events(res, abort.signal);
 *   for await (let event of stream) {
 *     console.log('<<', event.data);
 *   }
 * }
 * ```
 */
export async function* events(res, signal) {
    // TODO: throw error?
    if (!res.body)
        return;
    let iter = utils.stream(res.body);
    let line, reader = iter.getReader();
    let event;
    for (;;) {
        if (signal && signal.aborted) {
            return reader.cancel();
        }
        line = await reader.read();
        if (line.done)
            return;
        if (!line.value) {
            if (event)
                yield event;
            event = undefined;
            continue;
        }
        let [field, value] = utils.split(line.value) || [];
        if (!field)
            continue; // comment or invalid
        if (field === 'data') {
            event ||= {};
            event[field] = event[field] ? (event[field] + '\n' + value) : value;
        }
        else if (field === 'event') {
            event ||= {};
            event[field] = value;
        }
        else if (field === 'id') {
            event ||= {};
            event[field] = +value || value;
        }
        else if (field === 'retry') {
            event ||= {};
            event[field] = +value || undefined;
        }
    }
}
/**
 * Convenience function that will `fetch` with the given arguments and, if ok, will return the {@linkcode events} async iterator.
 *
 * If the response is not ok (status 200-299), the `Response` is thrown.
 *
 * @example
 * ```js
 * // NOTE: throws `Response` if not 2xx status
 * let events = await stream('https://api.openai.com/...', {
 *   method: 'POST',
 *   headers: {
 *     'Authorization': 'Bearer <token>',
 *     'Content-Type': 'application/json',
 *   },
 *   body: JSON.stringify({
 *     stream: true,
 *     // ...
 *   })
 * });
 *
 * for await (let event of events) {
 *   console.log('<<', JSON.parse(event.data));
 * }
 * ```
 */
export async function stream(input, init) {
    let req = new Request(input, init);
    utils.fallback(req.headers, 'Accept', 'text/event-stream');
    utils.fallback(req.headers, 'Content-Type', 'application/json');
    let r = await fetch(req);
    if (!r.ok)
        throw r;
    return events(r, req.signal);
}