@khanacademy/wonder-blocks-data 3.0.1 → 3.1.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/CHANGELOG.md +7 -0
- package/dist/es/index.js +167 -2
- package/dist/index.js +278 -41
- package/package.json +2 -1
- package/src/components/__tests__/gql-router.test.js +64 -0
- package/src/components/gql-router.js +66 -0
- package/src/hooks/__tests__/use-gql.test.js +233 -0
- package/src/hooks/use-gql.js +75 -0
- package/src/index.js +6 -6
- package/src/util/__tests__/get-gql-data-from-response.test.js +187 -0
- package/src/util/get-gql-data-from-response.js +69 -0
- package/src/util/gql-error.js +36 -0
- package/src/util/gql-router-context.js +6 -0
- package/src/util/gql-types.js +60 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {GqlError, GqlErrors} from "./gql-error.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Validate a GQL operation response and extract the data.
|
|
6
|
+
*/
|
|
7
|
+
export const getGqlDataFromResponse = async <TData>(
|
|
8
|
+
response: Response,
|
|
9
|
+
): Promise<TData> => {
|
|
10
|
+
// Get the response as text, that way we can use the text in error
|
|
11
|
+
// messaging, should our parsing fail.
|
|
12
|
+
const bodyText = await response.text();
|
|
13
|
+
let result;
|
|
14
|
+
try {
|
|
15
|
+
result = JSON.parse(bodyText);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
throw new GqlError("Failed to parse response", GqlErrors.Parse, {
|
|
18
|
+
metadata: {
|
|
19
|
+
statusCode: response.status,
|
|
20
|
+
bodyText,
|
|
21
|
+
},
|
|
22
|
+
cause: e,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check for a bad status code.
|
|
27
|
+
if (response.status >= 300) {
|
|
28
|
+
throw new GqlError("Response unsuccessful", GqlErrors.Network, {
|
|
29
|
+
metadata: {
|
|
30
|
+
statusCode: response.status,
|
|
31
|
+
result,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check that we have a valid result payload.
|
|
37
|
+
if (
|
|
38
|
+
// Flow shouldn't be warning about this.
|
|
39
|
+
// $FlowIgnore[method-unbinding]
|
|
40
|
+
!Object.prototype.hasOwnProperty.call(result, "data") &&
|
|
41
|
+
// Flow shouldn't be warning about this.
|
|
42
|
+
// $FlowIgnore[method-unbinding]
|
|
43
|
+
!Object.prototype.hasOwnProperty.call(result, "errors")
|
|
44
|
+
) {
|
|
45
|
+
throw new GqlError("Server response missing", GqlErrors.BadResponse, {
|
|
46
|
+
metadata: {
|
|
47
|
+
statusCode: response.status,
|
|
48
|
+
result,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If the response payload has errors, throw an error.
|
|
54
|
+
if (
|
|
55
|
+
result.errors != null &&
|
|
56
|
+
Array.isArray(result.errors) &&
|
|
57
|
+
result.errors.length > 0
|
|
58
|
+
) {
|
|
59
|
+
throw new GqlError("GraphQL errors", GqlErrors.ErrorResult, {
|
|
60
|
+
metadata: {
|
|
61
|
+
statusCode: response.status,
|
|
62
|
+
result,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// We got here, so return the data.
|
|
68
|
+
return result.data;
|
|
69
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {KindError, Errors} from "@khanacademy/wonder-stuff-core";
|
|
3
|
+
import type {Metadata} from "@khanacademy/wonder-stuff-core";
|
|
4
|
+
|
|
5
|
+
type GqlErrorOptions = {|
|
|
6
|
+
metadata?: ?Metadata,
|
|
7
|
+
cause?: ?Error,
|
|
8
|
+
|};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Error kinds for GqlError.
|
|
12
|
+
*/
|
|
13
|
+
export const GqlErrors = Object.freeze({
|
|
14
|
+
...Errors,
|
|
15
|
+
Network: "Network",
|
|
16
|
+
Parse: "Parse",
|
|
17
|
+
BadResponse: "BadResponse",
|
|
18
|
+
ErrorResult: "ErrorResult",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An error from the GQL API.
|
|
23
|
+
*/
|
|
24
|
+
export class GqlError extends KindError {
|
|
25
|
+
constructor(
|
|
26
|
+
message: string,
|
|
27
|
+
kind: $Values<typeof GqlErrors>,
|
|
28
|
+
{metadata, cause}: GqlErrorOptions = ({}: $Shape<GqlErrorOptions>),
|
|
29
|
+
) {
|
|
30
|
+
super(message, kind, {
|
|
31
|
+
metadata,
|
|
32
|
+
cause,
|
|
33
|
+
prefix: "Gql",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
/**
|
|
3
|
+
* Operation types.
|
|
4
|
+
*/
|
|
5
|
+
export type GqlOperationType = "mutation" | "query";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A GraphQL operation.
|
|
9
|
+
*/
|
|
10
|
+
export type GqlOperation<
|
|
11
|
+
TType: GqlOperationType,
|
|
12
|
+
// TData is not used to define a field on this type, but it is used
|
|
13
|
+
// to ensure that calls using this operation will properly return the
|
|
14
|
+
// correct data type.
|
|
15
|
+
// eslint-disable-next-line no-unused-vars
|
|
16
|
+
TData,
|
|
17
|
+
// TVariables is not used to define a field on this type, but it is used
|
|
18
|
+
// to ensure that calls using this operation will properly consume the
|
|
19
|
+
// correct variables type.
|
|
20
|
+
// eslint-disable-next-line no-unused-vars
|
|
21
|
+
TVariables: {...} = Empty,
|
|
22
|
+
> = {
|
|
23
|
+
type: TType,
|
|
24
|
+
id: string,
|
|
25
|
+
// We allow other things here to be passed along to the fetch function.
|
|
26
|
+
// For example, we might want to pass the full query/mutation definition
|
|
27
|
+
// as a string here to allow that to be sent to an Apollo server that
|
|
28
|
+
// expects it. This is a courtesy to calling code; these additional
|
|
29
|
+
// values are ignored by WB Data, and passed through as-is.
|
|
30
|
+
...
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type GqlContext = {|
|
|
34
|
+
[key: string]: string,
|
|
35
|
+
|};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Functions that make fetches of GQL operations.
|
|
39
|
+
*/
|
|
40
|
+
export type FetchFn<TType, TData, TVariables: {...}, TContext: GqlContext> = (
|
|
41
|
+
operation: GqlOperation<TType, TData, TVariables>,
|
|
42
|
+
variables: ?TVariables,
|
|
43
|
+
context: TContext,
|
|
44
|
+
) => Promise<Response>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The configuration stored in the GqlRouterContext context.
|
|
48
|
+
*/
|
|
49
|
+
export type GqlRouterConfiguration<TContext: GqlContext> = {|
|
|
50
|
+
fetch: FetchFn<any, any, any, any>,
|
|
51
|
+
defaultContext: TContext,
|
|
52
|
+
|};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Options for configuring a GQL fetch.
|
|
56
|
+
*/
|
|
57
|
+
export type GqlFetchOptions<TVariables: {...}, TContext: GqlContext> = {|
|
|
58
|
+
variables?: TVariables,
|
|
59
|
+
context?: Partial<TContext>,
|
|
60
|
+
|};
|