UNPKG

6.94 kBJavaScriptView Raw
1'use strict';
2
3const isObject = require('isobject/index.cjs.js');
4
5const ERROR_CODE_FETCH_ERROR = 'FETCH_ERROR';
6const ERROR_CODE_RESPONSE_HTTP_STATUS = 'RESPONSE_HTTP_STATUS';
7const ERROR_CODE_RESPONSE_JSON_PARSE_ERROR = 'RESPONSE_JSON_PARSE_ERROR';
8const ERROR_CODE_RESPONSE_MALFORMED = 'RESPONSE_MALFORMED';
9
10/**
11 * Fetches a GraphQL operation, always resolving a
12 * [GraphQL result]{@link GraphQLResult} suitable for use as a
13 * [cache value]{@link CacheValue}, even if there are errors. Loading errors
14 * are added to the [GraphQL result]{@link GraphQLResult} `errors` property, and
15 * have an `extensions` property containing `client: true`, along with `code`
16 * and sometimes error-specific properties:
17 *
18 * | Error code | Reasons | Error specific properties |
19 * | :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------- |
20 * | `FETCH_ERROR` | Fetch error, e.g. the `fetch` global isn’t defined, or the network is offline. | `fetchErrorMessage` (string). |
21 * | `RESPONSE_HTTP_STATUS` | Response HTTP status code is in the error range. | `statusCode` (number), `statusText` (string). |
22 * | `RESPONSE_JSON_PARSE_ERROR` | Response JSON parse error. | `jsonParseErrorMessage` (string). |
23 * | `RESPONSE_MALFORMED` | Response JSON isn’t an object, is missing an `errors` or `data` property, the `errors` property isn’t an array, or the `data` property isn’t an object or `null`. | |
24 * @kind function
25 * @name fetchGraphQL
26 * @param {string} fetchUri Fetch URI for the GraphQL API.
27 * @param {FetchOptions} fetchOptions Fetch options.
28 * @returns {Promise<GraphQLResult>} Resolves a result suitable for use as a [cache value]{@link CacheValue}. Shouldn’t reject.
29 * @example <caption>Ways to `import`.</caption>
30 * ```js
31 * import { fetchGraphQL } from 'graphql-react';
32 * ```
33 *
34 * ```js
35 * import fetchGraphQL from 'graphql-react/public/fetchGraphQL.js';
36 * ```
37 * @example <caption>Ways to `require`.</caption>
38 * ```js
39 * const { fetchGraphQL } = require('graphql-react');
40 * ```
41 *
42 * ```js
43 * const fetchGraphQL = require('graphql-react/public/fetchGraphQL');
44 * ```
45 */
46module.exports = function fetchGraphQL(fetchUri, fetchOptions) {
47 const result = { errors: [] };
48 const fetcher =
49 typeof fetch === 'function'
50 ? fetch
51 : () => Promise.reject(new TypeError('Global `fetch` API unavailable.'));
52
53 return fetcher(fetchUri, fetchOptions)
54 .then(
55 // Fetch ok.
56 (response) => {
57 // Allow the response to be read in the cache value, but prevent it from
58 // serializing to JSON when sending SSR cache to the client for
59 // hydration.
60 Object.defineProperty(result, 'response', { value: response });
61
62 if (!response.ok)
63 result.errors.push({
64 message: `HTTP ${response.status} status.`,
65 extensions: {
66 client: true,
67 code: ERROR_CODE_RESPONSE_HTTP_STATUS,
68 statusCode: response.status,
69 statusText: response.statusText,
70 },
71 });
72
73 return response.json().then(
74 // Response JSON parse ok.
75 (json) => {
76 // It’s not safe to assume that the response data format conforms to
77 // the GraphQL spec.
78 // https://spec.graphql.org/June2018/#sec-Response-Format
79
80 if (!isObject(json))
81 result.errors.push({
82 message: 'Response JSON isn’t an object.',
83 extensions: {
84 client: true,
85 code: ERROR_CODE_RESPONSE_MALFORMED,
86 },
87 });
88 else {
89 const hasErrors = 'errors' in json;
90 const hasData = 'data' in json;
91
92 if (!hasErrors && !hasData)
93 result.errors.push({
94 message:
95 'Response JSON is missing an `errors` or `data` property.',
96 extensions: {
97 client: true,
98 code: ERROR_CODE_RESPONSE_MALFORMED,
99 },
100 });
101 else {
102 // The `errors` field should be either an array, or not set.
103 // https://spec.graphql.org/June2018/#sec-Errors
104 if (hasErrors)
105 if (!Array.isArray(json.errors))
106 result.errors.push({
107 message:
108 'Response JSON `errors` property isn’t an array.',
109 extensions: {
110 client: true,
111 code: ERROR_CODE_RESPONSE_MALFORMED,
112 },
113 });
114 else result.errors.push(...json.errors);
115
116 // The `data` field should be either an object, null, or not set.
117 // https://spec.graphql.org/June2018/#sec-Data
118 if (hasData)
119 if (!isObject(json.data) && json.data !== null)
120 result.errors.push({
121 message:
122 'Response JSON `data` property isn’t an object or null.',
123 extensions: {
124 client: true,
125 code: ERROR_CODE_RESPONSE_MALFORMED,
126 },
127 });
128 else result.data = json.data;
129 }
130 }
131 },
132
133 // Response JSON parse error.
134 ({ message }) => {
135 result.errors.push({
136 message: 'Response JSON parse error.',
137 extensions: {
138 client: true,
139 code: ERROR_CODE_RESPONSE_JSON_PARSE_ERROR,
140 jsonParseErrorMessage: message,
141 },
142 });
143 }
144 );
145 },
146
147 // Fetch error.
148 ({ message }) => {
149 result.errors.push({
150 message: 'Fetch error.',
151 extensions: {
152 client: true,
153 code: ERROR_CODE_FETCH_ERROR,
154 fetchErrorMessage: message,
155 },
156 });
157 }
158 )
159 .then(() => {
160 if (!result.errors.length) delete result.errors;
161 return result;
162 });
163};