@khanacademy/wonder-blocks-data 15.0.3 → 15.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 +17 -0
- package/dist/es/index.js +6 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +7 -1
- package/dist/util/error-result-gql-error.d.ts +26 -0
- package/dist/util/gql-types.d.ts +33 -0
- package/dist/util/is-error-result-gql-error.d.ts +12 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-data
|
|
2
2
|
|
|
3
|
+
## 15.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6c55a12: Add `ErrorResultGqlError`, a typed `GqlError` thrown when a GraphQL response reports errors, exposing `statusCode` and the parsed `result` payload (partial `data` plus `errors`) so consumers can use partial results without type suppression. Also adds the `isErrorResultGqlError` type guard and the `GqlPartialData`, `GqlResponseError`, and `GqlErrorResultPayload` types.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- @khanacademy/wonder-blocks-core@12.5.0
|
|
12
|
+
|
|
13
|
+
## 15.0.4
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [63dcc94]
|
|
18
|
+
- @khanacademy/wonder-blocks-core@12.5.0
|
|
19
|
+
|
|
3
20
|
## 15.0.3
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/dist/es/index.js
CHANGED
|
@@ -50,7 +50,9 @@ const toString=value=>{if(typeof value==="string"){return value}if(typeof value=
|
|
|
50
50
|
|
|
51
51
|
const GqlErrors=Object.freeze({Internal:"Internal",BadResponse:"BadResponse",ErrorResult:"ErrorResult"});class GqlError extends KindError{constructor(message,kind,{metadata,cause}={}){super(message,kind,{metadata,cause,name:"Gql"});}}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
class ErrorResultGqlError extends GqlError{constructor(statusCode,result){super("GraphQL errors",GqlErrors.ErrorResult,{metadata:{statusCode,result:result}});this.statusCode=statusCode;this.result=result;}}
|
|
54
|
+
|
|
55
|
+
const getGqlDataFromResponse=async response=>{const bodyText=await response.text();let result;try{result=JSON.parse(bodyText);}catch(e){throw new DataError("Failed to parse response",DataErrors.Parse,{metadata:{statusCode:response.status,bodyText},cause:e})}if(response.status>=300){throw new DataError("Response unsuccessful",DataErrors.Network,{metadata:{statusCode:response.status,result}})}if(!Object.prototype.hasOwnProperty.call(result,"data")&&!Object.prototype.hasOwnProperty.call(result,"errors")){throw new GqlError("Server response missing",GqlErrors.BadResponse,{metadata:{statusCode:response.status,result}})}if(result.errors!=null&&Array.isArray(result.errors)&&result.errors.length>0){throw new ErrorResultGqlError(response.status,result)}return result.data};
|
|
54
56
|
|
|
55
57
|
const DocumentTypes=Object.freeze({query:"query",mutation:"mutation"});const cache=new Map;function graphQLDocumentNodeParser(document){const cached=cache.get(document);if(cached){return cached}if(!document?.kind){if(process.env.NODE_ENV==="production"){throw new DataError("Bad DocumentNode",DataErrors.InvalidInput)}else {throw new DataError(`Argument of ${JSON.stringify(document)} passed to parser was not a valid GraphQL `+`DocumentNode. You may need to use 'graphql-tag' or another method `+`to convert your operation into a document`,DataErrors.InvalidInput)}}const fragments=document.definitions.filter(x=>x.kind==="FragmentDefinition");const queries=document.definitions.filter(x=>x.kind==="OperationDefinition"&&x.operation==="query");const mutations=document.definitions.filter(x=>x.kind==="OperationDefinition"&&x.operation==="mutation");const subscriptions=document.definitions.filter(x=>x.kind==="OperationDefinition"&&x.operation==="subscription");if(fragments.length&&!queries.length&&!mutations.length){if(process.env.NODE_ENV==="production"){throw new DataError("Fragment only",DataErrors.InvalidInput)}else {throw new DataError(`Passing only a fragment to 'graphql' is not supported. `+`You must include a query or mutation as well`,DataErrors.InvalidInput)}}if(subscriptions.length){if(process.env.NODE_ENV==="production"){throw new DataError("No subscriptions",DataErrors.InvalidInput)}else {throw new DataError(`We do not support subscriptions. `+`${JSON.stringify(document)} had ${subscriptions.length} subscriptions`,DataErrors.InvalidInput)}}if(queries.length+mutations.length>1){if(process.env.NODE_ENV==="production"){throw new DataError("Too many ops",DataErrors.InvalidInput)}else {throw new DataError(`We only support one query or mutation per component. `+`${JSON.stringify(document)} had ${queries.length} queries and `+`${mutations.length} mutations. `,DataErrors.InvalidInput)}}const type=queries.length?DocumentTypes.query:DocumentTypes.mutation;const definitions=queries.length?queries:mutations;const definition=definitions[0];const variables=definition.variableDefinitions||[];const name=definition.name?.kind==="Name"?definition.name.value:"data";const payload={name,type,variables};cache.set(document,payload);return payload}
|
|
56
58
|
|
|
@@ -66,4 +68,6 @@ const useGqlRouterContext=(contextOverrides={})=>{const gqlRouterContext=useCont
|
|
|
66
68
|
|
|
67
69
|
const useGql=(context={})=>{const gqlRouterContext=useGqlRouterContext(context);const gqlFetch=useCallback((operation,options=Object.freeze({}))=>{const{fetch,defaultContext}=gqlRouterContext;const{variables,context={}}=options;const finalContext=mergeGqlContext(defaultContext,context);return fetch(operation,variables,finalContext).then(response=>getGqlDataFromResponse(response))},[gqlRouterContext]);return gqlFetch};
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
const isErrorResultGqlError=error=>error instanceof ErrorResultGqlError;
|
|
72
|
+
|
|
73
|
+
export { Data, DataError, DataErrors, ErrorResultGqlError, FetchPolicy, GqlError, GqlErrors, GqlRouter, InterceptRequests, ScopedInMemoryCache, SerializableInMemoryCache, SharedCache, Status, TrackData, WhenClientSide, abortInflightRequests, fetchTrackedRequests, getGqlDataFromResponse, getGqlRequestId, graphQLDocumentNodeParser, hasTrackedRequestsToBeFetched, initializeHydrationCache, isErrorResultGqlError, purgeCaches, purgeHydrationCache, toGqlOperation, useCachedEffect, useGql, useHydratableEffect, useServerEffect, useSharedCache };
|
package/dist/index.d.ts
CHANGED
|
@@ -21,4 +21,6 @@ export { toGqlOperation } from "./util/to-gql-operation";
|
|
|
21
21
|
export { GqlRouter } from "./components/gql-router";
|
|
22
22
|
export { useGql } from "./hooks/use-gql";
|
|
23
23
|
export { GqlError, GqlErrors } from "./util/gql-error";
|
|
24
|
-
export
|
|
24
|
+
export { ErrorResultGqlError } from "./util/error-result-gql-error";
|
|
25
|
+
export { isErrorResultGqlError } from "./util/is-error-result-gql-error";
|
|
26
|
+
export type { GqlContext, GqlOperation, GqlOperationType, GqlFetchOptions, GqlFetchFn, GqlPartialData, GqlResponseError, GqlErrorResultPayload, } from "./util/gql-types";
|
package/dist/index.js
CHANGED
|
@@ -73,7 +73,9 @@ const toString=value=>{if(typeof value==="string"){return value}if(typeof value=
|
|
|
73
73
|
|
|
74
74
|
const GqlErrors=Object.freeze({Internal:"Internal",BadResponse:"BadResponse",ErrorResult:"ErrorResult"});class GqlError extends wonderStuffCore.KindError{constructor(message,kind,{metadata,cause}={}){super(message,kind,{metadata,cause,name:"Gql"});}}
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
class ErrorResultGqlError extends GqlError{constructor(statusCode,result){super("GraphQL errors",GqlErrors.ErrorResult,{metadata:{statusCode,result:result}});this.statusCode=statusCode;this.result=result;}}
|
|
77
|
+
|
|
78
|
+
const getGqlDataFromResponse=async response=>{const bodyText=await response.text();let result;try{result=JSON.parse(bodyText);}catch(e){throw new DataError("Failed to parse response",DataErrors.Parse,{metadata:{statusCode:response.status,bodyText},cause:e})}if(response.status>=300){throw new DataError("Response unsuccessful",DataErrors.Network,{metadata:{statusCode:response.status,result}})}if(!Object.prototype.hasOwnProperty.call(result,"data")&&!Object.prototype.hasOwnProperty.call(result,"errors")){throw new GqlError("Server response missing",GqlErrors.BadResponse,{metadata:{statusCode:response.status,result}})}if(result.errors!=null&&Array.isArray(result.errors)&&result.errors.length>0){throw new ErrorResultGqlError(response.status,result)}return result.data};
|
|
77
79
|
|
|
78
80
|
const DocumentTypes=Object.freeze({query:"query",mutation:"mutation"});const cache=new Map;function graphQLDocumentNodeParser(document){const cached=cache.get(document);if(cached){return cached}if(!document?.kind){if(process.env.NODE_ENV==="production"){throw new DataError("Bad DocumentNode",DataErrors.InvalidInput)}else {throw new DataError(`Argument of ${JSON.stringify(document)} passed to parser was not a valid GraphQL `+`DocumentNode. You may need to use 'graphql-tag' or another method `+`to convert your operation into a document`,DataErrors.InvalidInput)}}const fragments=document.definitions.filter(x=>x.kind==="FragmentDefinition");const queries=document.definitions.filter(x=>x.kind==="OperationDefinition"&&x.operation==="query");const mutations=document.definitions.filter(x=>x.kind==="OperationDefinition"&&x.operation==="mutation");const subscriptions=document.definitions.filter(x=>x.kind==="OperationDefinition"&&x.operation==="subscription");if(fragments.length&&!queries.length&&!mutations.length){if(process.env.NODE_ENV==="production"){throw new DataError("Fragment only",DataErrors.InvalidInput)}else {throw new DataError(`Passing only a fragment to 'graphql' is not supported. `+`You must include a query or mutation as well`,DataErrors.InvalidInput)}}if(subscriptions.length){if(process.env.NODE_ENV==="production"){throw new DataError("No subscriptions",DataErrors.InvalidInput)}else {throw new DataError(`We do not support subscriptions. `+`${JSON.stringify(document)} had ${subscriptions.length} subscriptions`,DataErrors.InvalidInput)}}if(queries.length+mutations.length>1){if(process.env.NODE_ENV==="production"){throw new DataError("Too many ops",DataErrors.InvalidInput)}else {throw new DataError(`We only support one query or mutation per component. `+`${JSON.stringify(document)} had ${queries.length} queries and `+`${mutations.length} mutations. `,DataErrors.InvalidInput)}}const type=queries.length?DocumentTypes.query:DocumentTypes.mutation;const definitions=queries.length?queries:mutations;const definition=definitions[0];const variables=definition.variableDefinitions||[];const name=definition.name?.kind==="Name"?definition.name.value:"data";const payload={name,type,variables};cache.set(document,payload);return payload}
|
|
79
81
|
|
|
@@ -89,9 +91,12 @@ const useGqlRouterContext=(contextOverrides={})=>{const gqlRouterContext=React.u
|
|
|
89
91
|
|
|
90
92
|
const useGql=(context={})=>{const gqlRouterContext=useGqlRouterContext(context);const gqlFetch=React.useCallback((operation,options=Object.freeze({}))=>{const{fetch,defaultContext}=gqlRouterContext;const{variables,context={}}=options;const finalContext=mergeGqlContext(defaultContext,context);return fetch(operation,variables,finalContext).then(response=>getGqlDataFromResponse(response))},[gqlRouterContext]);return gqlFetch};
|
|
91
93
|
|
|
94
|
+
const isErrorResultGqlError=error=>error instanceof ErrorResultGqlError;
|
|
95
|
+
|
|
92
96
|
exports.Data = Data;
|
|
93
97
|
exports.DataError = DataError;
|
|
94
98
|
exports.DataErrors = DataErrors;
|
|
99
|
+
exports.ErrorResultGqlError = ErrorResultGqlError;
|
|
95
100
|
exports.FetchPolicy = FetchPolicy;
|
|
96
101
|
exports.GqlError = GqlError;
|
|
97
102
|
exports.GqlErrors = GqlErrors;
|
|
@@ -110,6 +115,7 @@ exports.getGqlRequestId = getGqlRequestId;
|
|
|
110
115
|
exports.graphQLDocumentNodeParser = graphQLDocumentNodeParser;
|
|
111
116
|
exports.hasTrackedRequestsToBeFetched = hasTrackedRequestsToBeFetched;
|
|
112
117
|
exports.initializeHydrationCache = initializeHydrationCache;
|
|
118
|
+
exports.isErrorResultGqlError = isErrorResultGqlError;
|
|
113
119
|
exports.purgeCaches = purgeCaches;
|
|
114
120
|
exports.purgeHydrationCache = purgeHydrationCache;
|
|
115
121
|
exports.toGqlOperation = toGqlOperation;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { GqlError } from "./gql-error";
|
|
2
|
+
import type { GqlErrorResultPayload } from "./gql-types";
|
|
3
|
+
/**
|
|
4
|
+
* A GraphQL response that completed but reported errors.
|
|
5
|
+
*
|
|
6
|
+
* GraphQL servers can return a valid response payload that carries both an
|
|
7
|
+
* `errors` array and a `data` object. Fields that failed are nulled, with the
|
|
8
|
+
* null propagating up to the nearest nullable ancestor, so the `data` that
|
|
9
|
+
* comes back is a partial version of the requested `TData`, or null if the
|
|
10
|
+
* whole result failed. This error exposes that payload with types so callers
|
|
11
|
+
* can make use of the partial data without guessing at the shape.
|
|
12
|
+
*
|
|
13
|
+
* The payload is also attached as `metadata` so that logging sees the same
|
|
14
|
+
* details it always did.
|
|
15
|
+
*/
|
|
16
|
+
export declare class ErrorResultGqlError<TData> extends GqlError {
|
|
17
|
+
/**
|
|
18
|
+
* The HTTP status code of the response.
|
|
19
|
+
*/
|
|
20
|
+
readonly statusCode: number;
|
|
21
|
+
/**
|
|
22
|
+
* The parsed response payload, including the partial data and errors.
|
|
23
|
+
*/
|
|
24
|
+
readonly result: GqlErrorResultPayload<TData>;
|
|
25
|
+
constructor(statusCode: number, result: GqlErrorResultPayload<TData>);
|
|
26
|
+
}
|
package/dist/util/gql-types.d.ts
CHANGED
|
@@ -32,3 +32,36 @@ export type GqlFetchOptions<TVariables extends Record<any, any>, TContext extend
|
|
|
32
32
|
variables?: TVariables;
|
|
33
33
|
context?: Partial<TContext>;
|
|
34
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* The data of a GraphQL response that reported errors.
|
|
37
|
+
*
|
|
38
|
+
* Each top-level field is either the requested value or null. A field that
|
|
39
|
+
* errored is nulled, and that null propagates to the nearest nullable
|
|
40
|
+
* ancestor, so the top level of the result is the only level whose shape we
|
|
41
|
+
* can state with certainty.
|
|
42
|
+
*/
|
|
43
|
+
export type GqlPartialData<TData> = {
|
|
44
|
+
[K in keyof TData]: TData[K] | null;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* An error entry from the `errors` array of a GraphQL response.
|
|
48
|
+
*/
|
|
49
|
+
export type GqlResponseError = {
|
|
50
|
+
message: string;
|
|
51
|
+
locations?: ReadonlyArray<{
|
|
52
|
+
line: number;
|
|
53
|
+
column: number;
|
|
54
|
+
}>;
|
|
55
|
+
path?: ReadonlyArray<string | number>;
|
|
56
|
+
extensions?: Record<string, unknown>;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* The payload of a GraphQL response that reported errors.
|
|
60
|
+
*
|
|
61
|
+
* `data` is absent or null when the whole operation failed; otherwise it is
|
|
62
|
+
* the partial data the server was able to resolve.
|
|
63
|
+
*/
|
|
64
|
+
export type GqlErrorResultPayload<TData> = {
|
|
65
|
+
data?: GqlPartialData<TData> | null;
|
|
66
|
+
errors: ReadonlyArray<GqlResponseError>;
|
|
67
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ErrorResultGqlError } from "./error-result-gql-error";
|
|
2
|
+
/**
|
|
3
|
+
* Determine if an error is an `ErrorResultGqlError`.
|
|
4
|
+
*
|
|
5
|
+
* The runtime check cannot verify the shape of the partial data, so `TData`
|
|
6
|
+
* is asserted rather than proven. Callers should only supply a `TData` that
|
|
7
|
+
* matches the operation whose response produced the error.
|
|
8
|
+
*
|
|
9
|
+
* @param error The value to check.
|
|
10
|
+
* @returns True if the error is an `ErrorResultGqlError`; otherwise, false.
|
|
11
|
+
*/
|
|
12
|
+
export declare const isErrorResultGqlError: <TData>(error: unknown) => error is ErrorResultGqlError<TData>;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "",
|
|
4
4
|
"author": "Khan Academy",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "15.0
|
|
6
|
+
"version": "15.1.0",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"module": "dist/es/index.js",
|
|
21
21
|
"types": "dist/index.d.ts",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@khanacademy/wonder-blocks-core": "12.
|
|
23
|
+
"@khanacademy/wonder-blocks-core": "12.5.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@khanacademy/wonder-stuff-core": "^3.0.0",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@khanacademy/wonder-stuff-testing": "^4.0.3",
|
|
31
31
|
"@khanacademy/wb-dev-build-settings": "3.3.0",
|
|
32
|
-
"@khanacademy/wonder-blocks-testing-core": "5.
|
|
32
|
+
"@khanacademy/wonder-blocks-testing-core": "5.3.0"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"test": "echo \"Error: no test specified\" && exit 1"
|