···
* The implementation in this file needs to make certain accommodations for:
* - Non-browser or polyfill Fetch APIs
29
-
* - Node.js-like Fetch implementations (see `toString` below)
29
+
* - Node.js-like Fetch implementations
* GraphQL over SSE has a reference implementation, which supports non-HTTP/2
* modes and is a faithful implementation of the spec.
···
import type { Operation, OperationResult, ExecutionResult } from '../types';
import { makeResult, makeErrorResult, mergeResultPatch } from '../utils';
50
-
const decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder() : null;
const boundaryHeaderRe = /boundary="?([^=";]+)"?/i;
const eventStreamRe = /data: ?([^\n]+)/;
type ChunkData = Buffer | Uint8Array;
56
-
// NOTE: We're avoiding referencing the `Buffer` global here to prevent
57
-
// auto-polyfilling in Webpack
58
-
const toString = (input: Buffer | ArrayBuffer): string =>
59
-
input.constructor.name === 'Buffer'
60
-
? (input as Buffer).toString()
61
-
: decoder!.decode(input as ArrayBuffer);
63
-
async function* streamBody(response: Response): AsyncIterableIterator<string> {
55
+
async function* streamBody(
57
+
): AsyncIterableIterator<ChunkData> {
if (response.body![Symbol.asyncIterator]) {
65
-
for await (const chunk of response.body! as any)
66
-
yield toString(chunk as ChunkData);
59
+
for await (const chunk of response.body! as any) yield chunk as ChunkData;
const reader = response.body!.getReader();
let result: ReadableStreamReadResult<ChunkData>;
71
-
while (!(result = await reader.read()).done) yield toString(result.value);
64
+
while (!(result = await reader.read()).done) yield result.value;
78
-
async function* split(
79
-
chunks: AsyncIterableIterator<string>,
71
+
async function* streamToBoundedChunks(
72
+
chunks: AsyncIterableIterator<ChunkData>,
): AsyncIterableIterator<string> {
75
+
const decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder() : null;
let boundaryIndex: number;
for await (const chunk of chunks) {
79
+
// NOTE: We're avoiding referencing the `Buffer` global here to prevent
80
+
// auto-polyfilling in Webpack
82
+
chunk.constructor.name === 'Buffer'
83
+
? (chunk as Buffer).toString()
84
+
: decoder!.decode(chunk as ArrayBuffer, { stream: true });
while ((boundaryIndex = buffer.indexOf(boundary)) > -1) {
yield buffer.slice(0, boundaryIndex);
buffer = buffer.slice(boundaryIndex + boundary.length);
···
): AsyncIterableIterator<ExecutionResult> {
103
-
for await (const chunk of split(streamBody(response), '\n\n')) {
102
+
for await (const chunk of streamToBoundedChunks(
103
+
streamBody(response),
const match = chunk.match(eventStreamRe);
···
const boundary = '--' + (boundaryHeader ? boundaryHeader[1] : '-');
128
-
for await (let chunk of split(streamBody(response), '\r\n' + boundary)) {
130
+
for await (let chunk of streamToBoundedChunks(
131
+
streamBody(response),
const preambleIndex = chunk.indexOf(boundary);