@urql/core
Version:
The shared core for the highly customizable and versatile GraphQL client
895 lines (862 loc) • 33.3 kB
JavaScript
var graphql_web = require('@0no-co/graphql.web');
var wonka = require('wonka');
var generateErrorMessage = (networkErr, graphQlErrs) => {
var error = '';
if (networkErr) return `[Network] ${networkErr.message}`;
if (graphQlErrs) {
for (var i = 0, l = graphQlErrs.length; i < l; i++) {
if (error) error += '\n';
error += `[GraphQL] ${graphQlErrs[i].message}`;
}
}
return error;
};
var rehydrateGraphQlError = error => {
if (error && typeof error.message === 'string' && (error.extensions || error.name === 'GraphQLError')) {
return error;
} else if (typeof error === 'object' && typeof error.message === 'string') {
return new graphql_web.GraphQLError(error.message, error.nodes, error.source, error.positions, error.path, error, error.extensions || {});
} else {
return new graphql_web.GraphQLError(error);
}
};
/** An abstracted `Error` that provides either a `networkError` or `graphQLErrors`.
*
* @remarks
* During a GraphQL request, either the request can fail entirely, causing a network error,
* or the GraphQL execution or fields can fail, which will cause an {@link ExecutionResult}
* to contain an array of GraphQL errors.
*
* The `CombinedError` abstracts and normalizes both failure cases. When {@link OperationResult.error}
* is set to this error, the `CombinedError` abstracts all errors, making it easier to handle only
* a subset of error cases.
*
* @see {@link https://urql.dev/goto/docs/basics/errors} for more information on handling
* GraphQL errors and the `CombinedError`.
*/
class CombinedError extends Error {
/** A list of GraphQL errors rehydrated from a {@link ExecutionResult}.
*
* @remarks
* If an {@link ExecutionResult} received from the API contains a list of errors,
* the `CombinedError` will rehydrate them, normalize them to
* {@link GraphQLError | GraphQLErrors} and list them here.
* An empty list indicates that no GraphQL error has been sent by the API.
*/
/** Set to an error, if a GraphQL request has failed outright.
*
* @remarks
* A GraphQL over HTTP request may fail and not reach the API. Any error that
* prevents a GraphQl request outright, will be considered a “network error” and
* set here.
*/
/** Set to the {@link Response} object a fetch exchange received.
*
* @remarks
* If a built-in fetch {@link Exchange} is used in `urql`, this may
* be set to the {@link Response} object of the Fetch API response.
* However, since `urql` doesn’t assume that all users will use HTTP
* as the only or exclusive transport for GraphQL this property is
* neither typed nor guaranteed and may be re-used for other purposes
* by non-fetch exchanges.
*
* Hint: It can be useful to use `response.status` here, however, if
* you plan on relying on this being a {@link Response} in your app,
* which it is by default, then make sure you add some extra checks
* before blindly assuming so!
*/
constructor(input) {
var normalizedGraphQLErrors = (input.graphQLErrors || []).map(rehydrateGraphQlError);
var message = generateErrorMessage(input.networkError, normalizedGraphQLErrors);
super(message);
this.name = 'CombinedError';
this.message = message;
this.graphQLErrors = normalizedGraphQLErrors;
this.networkError = input.networkError;
this.response = input.response;
}
toString() {
return this.message;
}
}
/** A hash value as computed by {@link phash}.
*
* @remarks
* Typically `HashValue`s are used as hashes and keys of GraphQL documents,
* variables, and combined, for GraphQL requests.
*/
/** Computes a djb2 hash of the given string.
*
* @param x - the string to be hashed
* @param seed - optionally a prior hash for progressive hashing
* @returns a hash value, i.e. a number
*
* @remark
* This is the hashing function used throughout `urql`, primarily to compute
* {@link Operation.key}.
*
* @see {@link http://www.cse.yorku.ca/~oz/hash.html#djb2} for a further description of djb2.
*/
var phash = (x, seed) => {
var h = (seed || 5381) | 0;
for (var i = 0, l = x.length | 0; i < l; i++) h = (h << 5) + h + x.charCodeAt(i);
return h;
};
var seen = new Set();
var cache = new WeakMap();
var stringify = (x, includeFiles) => {
if (x === null || seen.has(x)) {
return 'null';
} else if (typeof x !== 'object') {
return JSON.stringify(x) || '';
} else if (x.toJSON) {
return stringify(x.toJSON(), includeFiles);
} else if (Array.isArray(x)) {
var _out = '[';
for (var i = 0, l = x.length; i < l; i++) {
if (_out.length > 1) _out += ',';
_out += stringify(x[i], includeFiles) || 'null';
}
_out += ']';
return _out;
} else if (!includeFiles && (FileConstructor !== NoopConstructor && x instanceof FileConstructor || BlobConstructor !== NoopConstructor && x instanceof BlobConstructor)) {
return 'null';
}
var keys = Object.keys(x).sort();
if (!keys.length && x.constructor && Object.getPrototypeOf(x).constructor !== Object.prototype.constructor) {
var key = cache.get(x) || Math.random().toString(36).slice(2);
cache.set(x, key);
return stringify({
__key: key
}, includeFiles);
}
seen.add(x);
var out = '{';
for (var _i = 0, _l = keys.length; _i < _l; _i++) {
var value = stringify(x[keys[_i]], includeFiles);
if (value) {
if (out.length > 1) out += ',';
out += stringify(keys[_i], includeFiles) + ':' + value;
}
}
seen.delete(x);
out += '}';
return out;
};
var extract = (map, path, x) => {
if (x == null || typeof x !== 'object' || x.toJSON || seen.has(x)) ; else if (Array.isArray(x)) {
for (var i = 0, l = x.length; i < l; i++) extract(map, `${path}.${i}`, x[i]);
} else if (x instanceof FileConstructor || x instanceof BlobConstructor) {
map.set(path, x);
} else {
seen.add(x);
for (var key in x) extract(map, `${path}.${key}`, x[key]);
}
};
/** A stable stringifier for GraphQL variables objects.
*
* @param x - any JSON-like data.
* @return A JSON string.
*
* @remarks
* This utility creates a stable JSON string from any passed data,
* and protects itself from throwing.
*
* The JSON string is stable insofar as objects’ keys are sorted,
* and instances of non-plain objects are replaced with random keys
* replacing their values, which remain stable for the objects’
* instance.
*/
var stringifyVariables = (x, includeFiles) => {
seen.clear();
return stringify(x, includeFiles || false);
};
class NoopConstructor {}
var FileConstructor = typeof File !== 'undefined' ? File : NoopConstructor;
var BlobConstructor = typeof Blob !== 'undefined' ? Blob : NoopConstructor;
var extractFiles = x => {
var map = new Map();
if (FileConstructor !== NoopConstructor || BlobConstructor !== NoopConstructor) {
seen.clear();
extract(map, 'variables', x);
}
return map;
};
/** A `DocumentNode` annotated with its hashed key.
* @internal
*/
var SOURCE_NAME = 'gql';
var GRAPHQL_STRING_RE = /("{3}[\s\S]*"{3}|"(?:\\.|[^"])*")/g;
var REPLACE_CHAR_RE = /(?:#[^\n\r]+)?(?:[\r\n]+|$)/g;
var replaceOutsideStrings = (str, idx) => idx % 2 === 0 ? str.replace(REPLACE_CHAR_RE, '\n') : str;
/** Sanitizes a GraphQL document string by replacing comments and redundant newlines in it. */
var sanitizeDocument = node => node.split(GRAPHQL_STRING_RE).map(replaceOutsideStrings).join('').trim();
var prints = new Map();
var docs = new Map();
/** A cached printing function for GraphQL documents.
*
* @param node - A string of a document or a {@link DocumentNode}
* @returns A normalized printed string of the passed GraphQL document.
*
* @remarks
* This function accepts a GraphQL query string or {@link DocumentNode},
* then prints and sanitizes it. The sanitizer takes care of removing
* comments, which otherwise alter the key of the document although the
* document is otherwise equivalent to another.
*
* When a {@link DocumentNode} is passed to this function, it caches its
* output by modifying the `loc.source.body` property on the GraphQL node.
*/
var stringifyDocument = node => {
var printed;
if (typeof node === 'string') {
printed = sanitizeDocument(node);
} else if (node.loc && docs.get(node.__key) === node) {
printed = node.loc.source.body;
} else {
printed = prints.get(node) || sanitizeDocument(graphql_web.print(node));
prints.set(node, printed);
}
if (typeof node !== 'string' && !node.loc) {
node.loc = {
start: 0,
end: printed.length,
source: {
body: printed,
name: SOURCE_NAME,
locationOffset: {
line: 1,
column: 1
}
}
};
}
return printed;
};
/** Computes the hash for a document's string using {@link stringifyDocument}'s output.
*
* @param node - A string of a document or a {@link DocumentNode}
* @returns A {@link HashValue}
*
* @privateRemarks
* This function adds the operation name of the document to the hash, since sometimes
* a merged document with multiple operations may be used. Although `urql` requires a
* `DocumentNode` to only contain a single operation, when the cached `loc.source.body`
* of a `DocumentNode` is used, this string may still contain multiple operations and
* the resulting hash should account for only one at a time.
*/
var hashDocument = node => {
var key;
if (node.documentId) {
key = phash(node.documentId);
} else {
key = phash(stringifyDocument(node));
// Add the operation name to the produced hash
if (node.definitions) {
var operationName = getOperationName(node);
if (operationName) key = phash(`\n# ${operationName}`, key);
}
}
return key;
};
/** Returns a canonical version of the passed `DocumentNode` with an added hash key.
*
* @param node - A string of a document or a {@link DocumentNode}
* @returns A {@link KeyedDocumentNode}
*
* @remarks
* `urql` will always avoid unnecessary work, no matter whether a user passes `DocumentNode`s
* or strings of GraphQL documents to its APIs.
*
* This function will return a canonical version of a {@link KeyedDocumentNode} no matter
* which kind of input is passed, avoiding parsing or hashing of passed data as needed.
*/
var keyDocument = node => {
var key;
var query;
if (typeof node === 'string') {
key = hashDocument(node);
query = docs.get(key) || graphql_web.parse(node, {
noLocation: true
});
} else {
key = node.__key || hashDocument(node);
query = docs.get(key) || node;
}
// Add location information if it's missing
if (!query.loc) stringifyDocument(query);
query.__key = key;
docs.set(key, query);
return query;
};
/** Creates a `GraphQLRequest` from the passed parameters.
*
* @param q - A string of a document or a {@link DocumentNode}
* @param variables - A variables object for the defined GraphQL operation.
* @returns A {@link GraphQLRequest}
*
* @remarks
* `createRequest` creates a {@link GraphQLRequest} from the passed parameters,
* while replacing the document as needed with a canonical version of itself,
* to avoid parsing, printing, or hashing the same input multiple times.
*
* If no variables are passed, canonically it'll default to an empty object,
* which is removed from the resulting hash key.
*/
var createRequest = (_query, _variables, extensions) => {
var variables = _variables || {};
var query = keyDocument(_query);
var printedVars = stringifyVariables(variables, true);
var key = query.__key;
if (printedVars !== '{}') key = phash(printedVars, key);
return {
key,
query,
variables,
extensions
};
};
/** Returns the name of the `DocumentNode`'s operation, if any.
* @param query - A {@link DocumentNode}
* @returns the operation's name contained within the document, or `undefined`
*/
var getOperationName = query => {
for (var i = 0, l = query.definitions.length; i < l; i++) {
var node = query.definitions[i];
if (node.kind === graphql_web.Kind.OPERATION_DEFINITION) {
return node.name ? node.name.value : undefined;
}
}
};
/** Returns the type of the `DocumentNode`'s operation, if any.
* @param query - A {@link DocumentNode}
* @returns the operation's type contained within the document, or `undefined`
*/
var getOperationType = query => {
for (var i = 0, l = query.definitions.length; i < l; i++) {
var node = query.definitions[i];
if (node.kind === graphql_web.Kind.OPERATION_DEFINITION) {
return node.operation;
}
}
};
/** Converts the `ExecutionResult` received for a given `Operation` to an `OperationResult`.
*
* @param operation - The {@link Operation} for which the API’s result is for.
* @param result - The GraphQL API’s {@link ExecutionResult}.
* @param response - Optionally, a raw object representing the API’s result (Typically a {@link Response}).
* @returns An {@link OperationResult}.
*
* @remarks
* This utility can be used to create {@link OperationResult | OperationResults} in the shape
* that `urql` expects and defines, and should be used rather than creating the results manually.
*
* @throws
* If no data, or errors are contained within the result, or the result is instead an incremental
* response containing a `path` property, a “No Content” error is thrown.
*
* @see {@link ExecutionResult} for the type definition of GraphQL API results.
*/
var makeResult = (operation, result, response) => {
if (!('data' in result) && (!('errors' in result) || !Array.isArray(result.errors))) {
throw new Error('No Content');
}
var defaultHasNext = operation.kind === 'subscription';
return {
operation,
data: result.data,
error: Array.isArray(result.errors) ? new CombinedError({
graphQLErrors: result.errors,
response
}) : undefined,
extensions: result.extensions ? {
...result.extensions
} : undefined,
hasNext: result.hasNext == null ? defaultHasNext : result.hasNext,
stale: false
};
};
var deepMerge = (target, source) => {
if (typeof target === 'object' && target != null) {
if (Array.isArray(target)) {
target = [...target];
for (var i = 0, l = source.length; i < l; i++) target[i] = deepMerge(target[i], source[i]);
return target;
}
if (!target.constructor || target.constructor === Object) {
target = {
...target
};
for (var key in source) target[key] = deepMerge(target[key], source[key]);
return target;
}
}
return source;
};
/** Merges an incrementally delivered `ExecutionResult` into a previous `OperationResult`.
*
* @param prevResult - The {@link OperationResult} that preceded this result.
* @param path - The GraphQL API’s {@link ExecutionResult} that should be patching the `prevResult`.
* @param response - Optionally, a raw object representing the API’s result (Typically a {@link Response}).
* @returns A new {@link OperationResult} patched with the incremental result.
*
* @remarks
* This utility should be used to merge subsequent {@link ExecutionResult | ExecutionResults} of
* incremental responses into a prior {@link OperationResult}.
*
* When directives like `@defer`, `@stream`, and `@live` are used, GraphQL may deliver new
* results that modify previous results. In these cases, it'll set a `path` property to modify
* the result it sent last. This utility is built to handle these cases and merge these payloads
* into existing {@link OperationResult | OperationResults}.
*
* @see {@link ExecutionResult} for the type definition of GraphQL API results.
*/
var mergeResultPatch = (prevResult, nextResult, response, pending) => {
var errors = prevResult.error ? prevResult.error.graphQLErrors : [];
var hasExtensions = !!prevResult.extensions || !!(nextResult.payload || nextResult).extensions;
var extensions = {
...prevResult.extensions,
...(nextResult.payload || nextResult).extensions
};
var incremental = nextResult.incremental;
// NOTE: We handle the old version of the incremental delivery payloads as well
if ('path' in nextResult) {
incremental = [nextResult];
}
var withData = {
data: prevResult.data
};
if (incremental) {
var _loop = function () {
var patch = incremental[i];
if (Array.isArray(patch.errors)) {
errors.push(...patch.errors);
}
if (patch.extensions) {
Object.assign(extensions, patch.extensions);
hasExtensions = true;
}
var prop = 'data';
var part = withData;
var path = [];
if (patch.path) {
path = patch.path;
} else if (pending) {
var res = pending.find(pendingRes => pendingRes.id === patch.id);
if (patch.subPath) {
path = [...res.path, ...patch.subPath];
} else {
path = res.path;
}
}
for (var _i = 0, _l = path.length; _i < _l; prop = path[_i++]) {
part = part[prop] = Array.isArray(part[prop]) ? [...part[prop]] : {
...part[prop]
};
}
if (patch.items) {
var startIndex = +prop >= 0 ? prop : 0;
for (var _i2 = 0, _l2 = patch.items.length; _i2 < _l2; _i2++) part[startIndex + _i2] = deepMerge(part[startIndex + _i2], patch.items[_i2]);
} else if (patch.data !== undefined) {
part[prop] = deepMerge(part[prop], patch.data);
}
};
for (var i = 0, l = incremental.length; i < l; i++) {
_loop();
}
} else {
withData.data = (nextResult.payload || nextResult).data || prevResult.data;
errors = nextResult.errors || nextResult.payload && nextResult.payload.errors || errors;
}
return {
operation: prevResult.operation,
data: withData.data,
error: errors.length ? new CombinedError({
graphQLErrors: errors,
response
}) : undefined,
extensions: hasExtensions ? extensions : undefined,
hasNext: nextResult.hasNext != null ? nextResult.hasNext : prevResult.hasNext,
stale: false
};
};
/** Creates an `OperationResult` containing a network error for requests that encountered unexpected errors.
*
* @param operation - The {@link Operation} for which the API’s result is for.
* @param error - The network-like error that prevented an API result from being delivered.
* @param response - Optionally, a raw object representing the API’s result (Typically a {@link Response}).
* @returns An {@link OperationResult} containing only a {@link CombinedError}.
*
* @remarks
* This utility can be used to create {@link OperationResult | OperationResults} in the shape
* that `urql` expects and defines, and should be used rather than creating the results manually.
* This function should be used for when the {@link CombinedError.networkError} property is
* populated and no GraphQL execution actually occurred.
*/
var makeErrorResult = (operation, error, response) => ({
operation,
data: undefined,
error: new CombinedError({
networkError: error,
response
}),
extensions: undefined,
hasNext: false,
stale: false
});
/** Abstract definition of the JSON data sent during GraphQL HTTP POST requests. */
/** Creates a GraphQL over HTTP compliant JSON request body.
* @param request - An object containing a `query` document and `variables`.
* @returns A {@link FetchBody}
* @see {@link https://github.com/graphql/graphql-over-http} for the GraphQL over HTTP spec.
*/
function makeFetchBody(request) {
var body = {
query: undefined,
documentId: undefined,
operationName: getOperationName(request.query),
variables: request.variables || undefined,
extensions: request.extensions
};
if ('documentId' in request.query && request.query.documentId && (
// NOTE: We have to check that the document will definitely be sent
// as a persisted document to avoid breaking changes
!request.query.definitions || !request.query.definitions.length)) {
body.documentId = request.query.documentId;
} else if (!request.extensions || !request.extensions.persistedQuery || !!request.extensions.persistedQuery.miss) {
body.query = stringifyDocument(request.query);
}
return body;
}
/** Creates a URL that will be called for a GraphQL HTTP request.
*
* @param operation - An {@link Operation} for which to make the request.
* @param body - A {@link FetchBody} which may be replaced with a URL.
*
* @remarks
* Creates the URL that’ll be called as part of a GraphQL HTTP request.
* Built-in fetch exchanges support sending GET requests, even for
* non-persisted full requests, which this function supports by being
* able to serialize GraphQL requests into the URL.
*/
var makeFetchURL = (operation, body) => {
var useGETMethod = operation.kind === 'query' && operation.context.preferGetMethod;
if (!useGETMethod || !body) return operation.context.url;
var urlParts = splitOutSearchParams(operation.context.url);
for (var key in body) {
var value = body[key];
if (value) {
urlParts[1].set(key, typeof value === 'object' ? stringifyVariables(value) : value);
}
}
var finalUrl = urlParts.join('?');
if (finalUrl.length > 2047 && useGETMethod !== 'force') {
operation.context.preferGetMethod = false;
return operation.context.url;
}
return finalUrl;
};
var splitOutSearchParams = url => {
var start = url.indexOf('?');
return start > -1 ? [url.slice(0, start), new URLSearchParams(url.slice(start + 1))] : [url, new URLSearchParams()];
};
/** Serializes a {@link FetchBody} into a {@link RequestInit.body} format. */
var serializeBody = (operation, body) => {
var omitBody = operation.kind === 'query' && !!operation.context.preferGetMethod;
if (body && !omitBody) {
var json = stringifyVariables(body);
var files = extractFiles(body.variables);
if (files.size) {
var form = new FormData();
form.append('operations', json);
form.append('map', stringifyVariables({
...[...files.keys()].map(value => [value])
}));
var index = 0;
for (var file of files.values()) form.append(`${index++}`, file);
return form;
}
return json;
}
};
var isHeaders = headers => 'has' in headers && !Object.keys(headers).length;
/** Creates a `RequestInit` object for a given `Operation`.
*
* @param operation - An {@link Operation} for which to make the request.
* @param body - A {@link FetchBody} which is added to the options, if the request isn’t a GET request.
*
* @remarks
* Creates the fetch options {@link RequestInit} object that’ll be passed to the Fetch API
* as part of a GraphQL over HTTP request. It automatically sets a default `Content-Type`
* header.
*
* @see {@link https://github.com/graphql/graphql-over-http} for the GraphQL over HTTP spec.
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} for the Fetch API spec.
*/
var makeFetchOptions = (operation, body) => {
var headers = {
accept: operation.kind === 'subscription' ? 'text/event-stream, multipart/mixed' : 'application/graphql-response+json, application/graphql+json, application/json, text/event-stream, multipart/mixed'
};
var extraOptions = (typeof operation.context.fetchOptions === 'function' ? operation.context.fetchOptions() : operation.context.fetchOptions) || {};
if (extraOptions.headers) {
if (isHeaders(extraOptions.headers)) {
extraOptions.headers.forEach((value, key) => {
headers[key] = value;
});
} else if (Array.isArray(extraOptions.headers)) {
extraOptions.headers.forEach((value, key) => {
if (Array.isArray(value)) {
if (headers[value[0]]) {
headers[value[0]] = `${headers[value[0]]},${value[1]}`;
} else {
headers[value[0]] = value[1];
}
} else {
headers[key] = value;
}
});
} else {
for (var key in extraOptions.headers) {
headers[key.toLowerCase()] = extraOptions.headers[key];
}
}
}
var serializedBody = serializeBody(operation, body);
if (typeof serializedBody === 'string' && !headers['content-type']) headers['content-type'] = 'application/json';
return {
...extraOptions,
method: serializedBody ? 'POST' : 'GET',
body: serializedBody,
headers
};
};
/* Summary: This file handles the HTTP transport via GraphQL over HTTP
* See: https://graphql.github.io/graphql-over-http/draft/
*
* `@urql/core`, by default, implements several RFC'd protocol extensions
* on top of this. As such, this implementation supports:
* - [Incremental Delivery](https://github.com/graphql/graphql-over-http/blob/main/rfcs/IncrementalDelivery.md)
* - [GraphQL over SSE](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverSSE.md)
*
* This also supports the "Defer Stream" payload format.
* See: https://github.com/graphql/graphql-wg/blob/main/rfcs/DeferStream.md
* Implementation for this is located in `../utils/result.ts` in `mergeResultPatch`
*
* And; this also supports the GraphQL Multipart spec for file uploads.
* See: https://github.com/jaydenseric/graphql-multipart-request-spec
* Implementation for this is located in `../utils/variables.ts` in `extractFiles`,
* and `./fetchOptions.ts` in `serializeBody`.
*
* And; this also supports GET requests (and hence; automatic persisted queries)
* via the `@urql/exchange-persisted` package.
*
* This implementation DOES NOT support Batching.
* See: https://github.com/graphql/graphql-over-http/blob/main/rfcs/Batching.md
* Which is deemed out-of-scope, as it's sufficiently unnecessary given
* modern handling of HTTP requests being in parallel.
*
* The implementation in this file needs to make certain accommodations for:
* - The Web Fetch API
* - Non-browser or polyfill Fetch APIs
* - 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.
* See: https://github.com/enisdenjo/graphql-sse
*
* GraphQL Inremental Delivery (aka “GraphQL Multipart Responses”) has a
* reference implementation, which a prior implementation of this file heavily
* leaned on (See prior attribution comments)
* See: https://github.com/maraisr/meros
*
* This file merges support for all three GraphQL over HTTP response formats
* via async generators and Wonka’s `fromAsyncIterable`. As part of this, `streamBody`
* and `split` are the common, cross-compatible base implementations.
*/
var boundaryHeaderRe = /boundary="?([^=";]+)"?/i;
var eventStreamRe = /data: ?([^\n]+)/;
async function* streamBody(response) {
if (response.body[Symbol.asyncIterator]) {
for await (var chunk of response.body) yield chunk;
} else {
var reader = response.body.getReader();
var result;
try {
while (!(result = await reader.read()).done) yield result.value;
} finally {
reader.cancel();
}
}
}
async function* streamToBoundedChunks(chunks, boundary) {
var decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder() : null;
var buffer = '';
var boundaryIndex;
for await (var chunk of chunks) {
// NOTE: We're avoiding referencing the `Buffer` global here to prevent
// auto-polyfilling in Webpack
buffer += chunk.constructor.name === 'Buffer' ? chunk.toString() : decoder.decode(chunk, {
stream: true
});
while ((boundaryIndex = buffer.indexOf(boundary)) > -1) {
yield buffer.slice(0, boundaryIndex);
buffer = buffer.slice(boundaryIndex + boundary.length);
}
}
}
async function* parseJSON(response) {
yield JSON.parse(await response.text());
}
async function* parseEventStream(response) {
var payload;
for await (var chunk of streamToBoundedChunks(streamBody(response), '\n\n')) {
var match = chunk.match(eventStreamRe);
if (match) {
var _chunk = match[1];
try {
yield payload = JSON.parse(_chunk);
} catch (error) {
if (!payload) throw error;
}
if (payload && payload.hasNext === false) break;
}
}
if (payload && payload.hasNext !== false) {
yield {
hasNext: false
};
}
}
async function* parseMultipartMixed(contentType, response) {
var boundaryHeader = contentType.match(boundaryHeaderRe);
var boundary = '--' + (boundaryHeader ? boundaryHeader[1] : '-');
var isPreamble = true;
var payload;
for await (var chunk of streamToBoundedChunks(streamBody(response), '\r\n' + boundary)) {
if (isPreamble) {
isPreamble = false;
var preambleIndex = chunk.indexOf(boundary);
if (preambleIndex > -1) {
chunk = chunk.slice(preambleIndex + boundary.length);
} else {
continue;
}
}
try {
yield payload = JSON.parse(chunk.slice(chunk.indexOf('\r\n\r\n') + 4));
} catch (error) {
if (!payload) throw error;
}
if (payload && payload.hasNext === false) break;
}
if (payload && payload.hasNext !== false) {
yield {
hasNext: false
};
}
}
async function* parseMaybeJSON(response) {
var text = await response.text();
try {
var result = JSON.parse(text);
if (process.env.NODE_ENV !== 'production') {
console.warn(`Found response with content-type "text/plain" but it had a valid "application/json" response.`);
}
yield result;
} catch (e) {
throw new Error(text);
}
}
async function* fetchOperation(operation, url, fetchOptions) {
var networkMode = true;
var result = null;
var response;
try {
// Delay for a tick to give the Client a chance to cancel the request
// if a teardown comes in immediately
yield await Promise.resolve();
response = await (operation.context.fetch || fetch)(url, fetchOptions);
var contentType = response.headers.get('Content-Type') || '';
var results;
if (/multipart\/mixed/i.test(contentType)) {
results = parseMultipartMixed(contentType, response);
} else if (/text\/event-stream/i.test(contentType)) {
results = parseEventStream(response);
} else if (!/text\//i.test(contentType)) {
results = parseJSON(response);
} else {
results = parseMaybeJSON(response);
}
var pending;
for await (var payload of results) {
if (payload.pending && !result) {
pending = payload.pending;
} else if (payload.pending) {
pending = [...pending, ...payload.pending];
}
result = result ? mergeResultPatch(result, payload, response, pending) : makeResult(operation, payload, response);
networkMode = false;
yield result;
networkMode = true;
}
if (!result) {
yield result = makeResult(operation, {}, response);
}
} catch (error) {
if (!networkMode) {
throw error;
}
yield makeErrorResult(operation, response && (response.status < 200 || response.status >= 300) && response.statusText ? new Error(response.statusText) : error, response);
}
}
/** Makes a GraphQL HTTP request to a given API by wrapping around the Fetch API.
*
* @param operation - The {@link Operation} that should be sent via GraphQL over HTTP.
* @param url - The endpoint URL for the GraphQL HTTP API.
* @param fetchOptions - The {@link RequestInit} fetch options for the request.
* @returns A Wonka {@link Source} of {@link OperationResult | OperationResults}.
*
* @remarks
* This utility defines how all built-in fetch exchanges make GraphQL HTTP requests,
* supporting multipart incremental responses, cancellation and other smaller
* implementation details.
*
* If you’re implementing a modified fetch exchange for a GraphQL over HTTP API
* it’s recommended you use this utility.
*
* Hint: This function does not use the passed `operation` to create or modify the
* `fetchOptions` and instead expects that the options have already been created
* using {@link makeFetchOptions} and modified as needed.
*
* @throws
* If the `fetch` polyfill or globally available `fetch` function doesn’t support
* streamed multipart responses while trying to handle a `multipart/mixed` GraphQL response,
* the source will throw “Streaming requests unsupported”.
* This shouldn’t happen in modern browsers and Node.js.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} for the Fetch API spec.
*/
function makeFetchSource(operation, url, fetchOptions) {
var abortController;
if (typeof AbortController !== 'undefined') {
fetchOptions.signal = (abortController = new AbortController()).signal;
}
return wonka.onEnd(() => {
if (abortController) abortController.abort();
})(wonka.filter(result => !!result)(wonka.fromAsyncIterable(fetchOperation(operation, url, fetchOptions))));
}
exports.CombinedError = CombinedError;
exports.createRequest = createRequest;
exports.getOperationName = getOperationName;
exports.getOperationType = getOperationType;
exports.keyDocument = keyDocument;
exports.makeErrorResult = makeErrorResult;
exports.makeFetchBody = makeFetchBody;
exports.makeFetchOptions = makeFetchOptions;
exports.makeFetchSource = makeFetchSource;
exports.makeFetchURL = makeFetchURL;
exports.makeResult = makeResult;
exports.mergeResultPatch = mergeResultPatch;
exports.stringifyDocument = stringifyDocument;
exports.stringifyVariables = stringifyVariables;
//# sourceMappingURL=urql-core-chunk.js.map