@khanacademy/wonder-blocks-data 13.0.11 → 13.0.12
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 +8 -0
- package/package.json +3 -3
- package/src/components/__tests__/data.test.tsx +0 -832
- package/src/components/__tests__/gql-router.test.tsx +0 -63
- package/src/components/__tests__/intercept-requests.test.tsx +0 -57
- package/src/components/__tests__/track-data.test.tsx +0 -56
- package/src/components/data.ts +0 -73
- package/src/components/gql-router.tsx +0 -63
- package/src/components/intercept-context.ts +0 -19
- package/src/components/intercept-requests.tsx +0 -67
- package/src/components/track-data.tsx +0 -28
- package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.ts.snap +0 -17
- package/src/hooks/__tests__/use-cached-effect.test.tsx +0 -789
- package/src/hooks/__tests__/use-gql-router-context.test.tsx +0 -132
- package/src/hooks/__tests__/use-gql.test.tsx +0 -204
- package/src/hooks/__tests__/use-hydratable-effect.test.ts +0 -708
- package/src/hooks/__tests__/use-request-interception.test.tsx +0 -254
- package/src/hooks/__tests__/use-server-effect.test.ts +0 -293
- package/src/hooks/__tests__/use-shared-cache.test.ts +0 -263
- package/src/hooks/use-cached-effect.ts +0 -297
- package/src/hooks/use-gql-router-context.ts +0 -49
- package/src/hooks/use-gql.ts +0 -58
- package/src/hooks/use-hydratable-effect.ts +0 -201
- package/src/hooks/use-request-interception.ts +0 -53
- package/src/hooks/use-server-effect.ts +0 -75
- package/src/hooks/use-shared-cache.ts +0 -107
- package/src/index.ts +0 -46
- package/src/util/__tests__/__snapshots__/scoped-in-memory-cache.test.ts.snap +0 -19
- package/src/util/__tests__/__snapshots__/serializable-in-memory-cache.test.ts.snap +0 -19
- package/src/util/__tests__/get-gql-data-from-response.test.ts +0 -186
- package/src/util/__tests__/get-gql-request-id.test.ts +0 -132
- package/src/util/__tests__/graphql-document-node-parser.test.ts +0 -535
- package/src/util/__tests__/hydration-cache-api.test.ts +0 -34
- package/src/util/__tests__/merge-gql-context.test.ts +0 -73
- package/src/util/__tests__/purge-caches.test.ts +0 -28
- package/src/util/__tests__/request-api.test.ts +0 -176
- package/src/util/__tests__/request-fulfillment.test.ts +0 -146
- package/src/util/__tests__/request-tracking.test.tsx +0 -321
- package/src/util/__tests__/result-from-cache-response.test.ts +0 -79
- package/src/util/__tests__/scoped-in-memory-cache.test.ts +0 -316
- package/src/util/__tests__/serializable-in-memory-cache.test.ts +0 -397
- package/src/util/__tests__/ssr-cache.test.ts +0 -636
- package/src/util/__tests__/to-gql-operation.test.ts +0 -41
- package/src/util/data-error.ts +0 -63
- package/src/util/get-gql-data-from-response.ts +0 -65
- package/src/util/get-gql-request-id.ts +0 -106
- package/src/util/gql-error.ts +0 -43
- package/src/util/gql-router-context.ts +0 -9
- package/src/util/gql-types.ts +0 -64
- package/src/util/graphql-document-node-parser.ts +0 -132
- package/src/util/graphql-types.ts +0 -28
- package/src/util/hydration-cache-api.ts +0 -30
- package/src/util/merge-gql-context.ts +0 -35
- package/src/util/purge-caches.ts +0 -14
- package/src/util/request-api.ts +0 -65
- package/src/util/request-fulfillment.ts +0 -121
- package/src/util/request-tracking.ts +0 -211
- package/src/util/result-from-cache-response.ts +0 -30
- package/src/util/scoped-in-memory-cache.ts +0 -121
- package/src/util/serializable-in-memory-cache.ts +0 -44
- package/src/util/ssr-cache.ts +0 -193
- package/src/util/status.ts +0 -35
- package/src/util/to-gql-operation.ts +0 -43
- package/src/util/types.ts +0 -145
- package/tsconfig-build.json +0 -12
- package/tsconfig-build.tsbuildinfo +0 -1
package/src/util/data-error.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import {KindError} from "@khanacademy/wonder-stuff-core";
|
|
2
|
-
import type {ErrorOptions} from "./types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Error kinds for DataError.
|
|
6
|
-
*/
|
|
7
|
-
export const DataErrors = Object.freeze({
|
|
8
|
-
/**
|
|
9
|
-
* The kind of error is not known.
|
|
10
|
-
*/
|
|
11
|
-
Unknown: "Unknown",
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* The error is internal to the executing code.
|
|
15
|
-
*/
|
|
16
|
-
Internal: "Internal",
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* There was a problem with the provided input.
|
|
20
|
-
*/
|
|
21
|
-
InvalidInput: "InvalidInput",
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* A network error occurred.
|
|
25
|
-
*/
|
|
26
|
-
Network: "Network",
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* There was a problem due to the state of the system not matching the
|
|
30
|
-
* requested operation or input.
|
|
31
|
-
*/
|
|
32
|
-
NotAllowed: "NotAllowed",
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Response could not be parsed.
|
|
36
|
-
*/
|
|
37
|
-
Parse: "Parse",
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* An error that occurred during SSR and was hydrated from cache
|
|
41
|
-
*/
|
|
42
|
-
Hydrated: "Hydrated",
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* An error from the Wonder Blocks Data API.
|
|
47
|
-
*
|
|
48
|
-
* Errors of this type will have names of the format:
|
|
49
|
-
* `${kind}DataError`
|
|
50
|
-
*/
|
|
51
|
-
export class DataError extends KindError {
|
|
52
|
-
constructor(
|
|
53
|
-
message: string,
|
|
54
|
-
kind: typeof DataErrors[keyof typeof DataErrors],
|
|
55
|
-
{metadata, cause}: ErrorOptions = {} as Partial<ErrorOptions>,
|
|
56
|
-
) {
|
|
57
|
-
super(message, kind, {
|
|
58
|
-
metadata,
|
|
59
|
-
cause,
|
|
60
|
-
name: "Data",
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import {DataError, DataErrors} from "./data-error";
|
|
2
|
-
import {GqlError, GqlErrors} from "./gql-error";
|
|
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: any) {
|
|
17
|
-
throw new DataError("Failed to parse response", DataErrors.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 DataError("Response unsuccessful", DataErrors.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
|
-
!Object.prototype.hasOwnProperty.call(result, "data") &&
|
|
39
|
-
!Object.prototype.hasOwnProperty.call(result, "errors")
|
|
40
|
-
) {
|
|
41
|
-
throw new GqlError("Server response missing", GqlErrors.BadResponse, {
|
|
42
|
-
metadata: {
|
|
43
|
-
statusCode: response.status,
|
|
44
|
-
result,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// If the response payload has errors, throw an error.
|
|
50
|
-
if (
|
|
51
|
-
result.errors != null &&
|
|
52
|
-
Array.isArray(result.errors) &&
|
|
53
|
-
result.errors.length > 0
|
|
54
|
-
) {
|
|
55
|
-
throw new GqlError("GraphQL errors", GqlErrors.ErrorResult, {
|
|
56
|
-
metadata: {
|
|
57
|
-
statusCode: response.status,
|
|
58
|
-
result,
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// We got here, so return the data.
|
|
64
|
-
return result.data;
|
|
65
|
-
};
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import {entries} from "@khanacademy/wonder-stuff-core";
|
|
2
|
-
import type {GqlOperation, GqlContext} from "./gql-types";
|
|
3
|
-
|
|
4
|
-
const toString = (value: unknown): string => {
|
|
5
|
-
if (typeof value === "string") {
|
|
6
|
-
return value;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
if (typeof value === "object" && value != null) {
|
|
10
|
-
if (value instanceof Date) {
|
|
11
|
-
return value.toISOString();
|
|
12
|
-
} else if (typeof value.toString === "function") {
|
|
13
|
-
return value.toString();
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return JSON.stringify(value) ?? "";
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const toStringifiedVariables = (acc: any, key: string, value: unknown): any => {
|
|
20
|
-
if (typeof value === "object" && value !== null) {
|
|
21
|
-
// If we have an object or array, we build sub-variables so that
|
|
22
|
-
// the ID is easily human-readable rather than having lots of
|
|
23
|
-
// extra %-encodings. This means that an object or array variable
|
|
24
|
-
// turns into x variables, where x is the field or element count of
|
|
25
|
-
// variable. See below for example.
|
|
26
|
-
const subValues = entries(value);
|
|
27
|
-
|
|
28
|
-
// If we don't get any entries, it's possible this is a Date, Error,
|
|
29
|
-
// or some other non-standard value. While these generally should be
|
|
30
|
-
// avoided as variables, we should handle them gracefully.
|
|
31
|
-
if (subValues.length !== 0) {
|
|
32
|
-
return subValues.reduce((innerAcc, [i, v]: [any, any]) => {
|
|
33
|
-
const subKey = `${key}.${i}`;
|
|
34
|
-
return toStringifiedVariables(innerAcc, subKey, v);
|
|
35
|
-
}, acc);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
acc[key] = toString(value);
|
|
40
|
-
return acc;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get an identifier for a given request.
|
|
45
|
-
*/
|
|
46
|
-
export const getGqlRequestId = <TData, TVariables extends Record<any, any>>(
|
|
47
|
-
operation: GqlOperation<TData, TVariables>,
|
|
48
|
-
variables: TVariables | null | undefined,
|
|
49
|
-
context: GqlContext,
|
|
50
|
-
): string => {
|
|
51
|
-
// We add all the bits for this into an array and then join them with
|
|
52
|
-
// a chosen separator.
|
|
53
|
-
const parts: Array<string> = [];
|
|
54
|
-
|
|
55
|
-
// First, we push the context values.
|
|
56
|
-
const sortableContext = new URLSearchParams(context);
|
|
57
|
-
sortableContext.sort();
|
|
58
|
-
parts.push(sortableContext.toString());
|
|
59
|
-
|
|
60
|
-
// Now we add the operation identifier.
|
|
61
|
-
parts.push(operation.id);
|
|
62
|
-
|
|
63
|
-
// Finally, if we have variables, we add those too.
|
|
64
|
-
if (variables != null) {
|
|
65
|
-
// We need to turn each variable into a string.
|
|
66
|
-
// We also need to ensure we sort any sub-object keys.
|
|
67
|
-
// `toStringifiedVariables` helps us with this by hoisting nested
|
|
68
|
-
// data to individual variables for the purposes of ID generation.
|
|
69
|
-
//
|
|
70
|
-
// For example, consider variables:
|
|
71
|
-
// {x: [1,2,3], y: {a: 1, b: 2, c: 3}, z: 123}
|
|
72
|
-
//
|
|
73
|
-
// Each variable, x, y and z, would be stringified into
|
|
74
|
-
// stringifiedVariables as follows:
|
|
75
|
-
// x becomes {"x.0": "1", "x.1": "2", "x.2": "3"}
|
|
76
|
-
// y becomes {"y.a": "1", "y.b": "2", "y.c": "3"}
|
|
77
|
-
// z becomes {"z": "123"}
|
|
78
|
-
//
|
|
79
|
-
// This then leads to stringifiedVariables being:
|
|
80
|
-
// {
|
|
81
|
-
// "x.0": "1",
|
|
82
|
-
// "x.1": "2",
|
|
83
|
-
// "x.2": "3",
|
|
84
|
-
// "y.a": "1",
|
|
85
|
-
// "y.b": "2",
|
|
86
|
-
// "y.c": "3",
|
|
87
|
-
// "z": "123",
|
|
88
|
-
// }
|
|
89
|
-
//
|
|
90
|
-
// Thus allowing our use of URLSearchParams to both sort and easily
|
|
91
|
-
// encode the variables into an idempotent identifier for those
|
|
92
|
-
// variable values that is also human-readable.
|
|
93
|
-
const stringifiedVariables = Object.keys(variables).reduce<
|
|
94
|
-
Record<string, any>
|
|
95
|
-
>((acc, key) => {
|
|
96
|
-
const value = variables[key];
|
|
97
|
-
return toStringifiedVariables(acc, key, value);
|
|
98
|
-
}, {});
|
|
99
|
-
// We use the same mechanism as context to sort and arrange the
|
|
100
|
-
// variables.
|
|
101
|
-
const sortableVariables = new URLSearchParams(stringifiedVariables);
|
|
102
|
-
sortableVariables.sort();
|
|
103
|
-
parts.push(sortableVariables.toString());
|
|
104
|
-
}
|
|
105
|
-
return parts.join("|");
|
|
106
|
-
};
|
package/src/util/gql-error.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import {KindError} from "@khanacademy/wonder-stuff-core";
|
|
2
|
-
|
|
3
|
-
import type {ErrorOptions} from "./types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Error kinds for GqlError.
|
|
7
|
-
*/
|
|
8
|
-
export const GqlErrors = Object.freeze({
|
|
9
|
-
/**
|
|
10
|
-
* An internal framework error.
|
|
11
|
-
*/
|
|
12
|
-
Internal: "Internal",
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Response does not have the correct structure for a GraphQL response.
|
|
16
|
-
*/
|
|
17
|
-
BadResponse: "BadResponse",
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* A valid GraphQL result with errors field in the payload.
|
|
21
|
-
*/
|
|
22
|
-
ErrorResult: "ErrorResult",
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* An error from the GQL API.
|
|
27
|
-
*
|
|
28
|
-
* Errors of this type will have names of the format:
|
|
29
|
-
* `${kind}GqlError`
|
|
30
|
-
*/
|
|
31
|
-
export class GqlError extends KindError {
|
|
32
|
-
constructor(
|
|
33
|
-
message: string,
|
|
34
|
-
kind: typeof GqlErrors[keyof typeof GqlErrors],
|
|
35
|
-
{metadata, cause}: ErrorOptions = {} as Partial<ErrorOptions>,
|
|
36
|
-
) {
|
|
37
|
-
super(message, kind, {
|
|
38
|
-
metadata,
|
|
39
|
-
cause,
|
|
40
|
-
name: "Gql",
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type {GqlRouterConfiguration} from "./gql-types";
|
|
3
|
-
|
|
4
|
-
const GqlRouterContext: React.Context<
|
|
5
|
-
GqlRouterConfiguration<any> | null | undefined
|
|
6
|
-
> = React.createContext<GqlRouterConfiguration<any> | null | undefined>(null);
|
|
7
|
-
GqlRouterContext.displayName = "GqlRouterContext";
|
|
8
|
-
|
|
9
|
-
export {GqlRouterContext};
|
package/src/util/gql-types.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Operation types.
|
|
3
|
-
*/
|
|
4
|
-
export type GqlOperationType = "mutation" | "query";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* A GraphQL operation.
|
|
8
|
-
*/
|
|
9
|
-
export type GqlOperation<
|
|
10
|
-
// TData is not used to define a field on this type, but it is used
|
|
11
|
-
// to ensure that calls using this operation will properly return the
|
|
12
|
-
// correct data type.
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
14
|
-
TData, // TVariables is not used to define a field on this type, but it is used
|
|
15
|
-
// to ensure that calls using this operation will properly consume the
|
|
16
|
-
// correct variables type.
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
18
|
-
TVariables extends object = Empty,
|
|
19
|
-
> = {
|
|
20
|
-
type: GqlOperationType;
|
|
21
|
-
id: string;
|
|
22
|
-
// We allow other things here to be passed along to the fetch function.
|
|
23
|
-
// For example, we might want to pass the full query/mutation definition
|
|
24
|
-
// as a string here to allow that to be sent to an Apollo server that
|
|
25
|
-
// expects it. This is a courtesy to calling code; these additional
|
|
26
|
-
// values are ignored by WB Data, and passed through as-is.
|
|
27
|
-
[key: string]: unknown;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export type GqlContext = {
|
|
31
|
-
[key: string]: string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Functions that make fetches of GQL operations.
|
|
36
|
-
*/
|
|
37
|
-
export type GqlFetchFn<
|
|
38
|
-
TData,
|
|
39
|
-
TVariables extends Record<any, any>,
|
|
40
|
-
TContext extends GqlContext,
|
|
41
|
-
> = (
|
|
42
|
-
operation: GqlOperation<TData, TVariables>,
|
|
43
|
-
variables: TVariables | null | undefined,
|
|
44
|
-
context: TContext,
|
|
45
|
-
) => Promise<Response>;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* The configuration stored in the GqlRouterContext context.
|
|
49
|
-
*/
|
|
50
|
-
export type GqlRouterConfiguration<TContext extends GqlContext> = {
|
|
51
|
-
fetch: GqlFetchFn<any, any, any>;
|
|
52
|
-
defaultContext: TContext;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Options for configuring a GQL fetch.
|
|
57
|
-
*/
|
|
58
|
-
export type GqlFetchOptions<
|
|
59
|
-
TVariables extends Record<any, any>,
|
|
60
|
-
TContext extends GqlContext,
|
|
61
|
-
> = {
|
|
62
|
-
variables?: TVariables;
|
|
63
|
-
context?: Partial<TContext>;
|
|
64
|
-
};
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
DocumentNode,
|
|
3
|
-
DefinitionNode,
|
|
4
|
-
VariableDefinitionNode,
|
|
5
|
-
OperationDefinitionNode,
|
|
6
|
-
} from "./graphql-types";
|
|
7
|
-
import {DataError, DataErrors} from "./data-error";
|
|
8
|
-
|
|
9
|
-
export const DocumentTypes = Object.freeze({
|
|
10
|
-
query: "query",
|
|
11
|
-
mutation: "mutation",
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
export type DocumentType = typeof DocumentTypes[keyof typeof DocumentTypes];
|
|
15
|
-
|
|
16
|
-
export interface IDocumentDefinition {
|
|
17
|
-
type: DocumentType;
|
|
18
|
-
name: string;
|
|
19
|
-
variables: ReadonlyArray<VariableDefinitionNode>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const cache = new Map<DocumentNode, IDocumentDefinition>();
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Parse a GraphQL document node to determine some info about it.
|
|
26
|
-
*
|
|
27
|
-
* This is based on:
|
|
28
|
-
* https://github.com/apollographql/react-apollo/blob/3bc993b2ea91704bd6a2667f42d1940656c071ff/src/parser.ts
|
|
29
|
-
*/
|
|
30
|
-
export function graphQLDocumentNodeParser(
|
|
31
|
-
document: DocumentNode,
|
|
32
|
-
): IDocumentDefinition {
|
|
33
|
-
const cached = cache.get(document);
|
|
34
|
-
if (cached) {
|
|
35
|
-
return cached;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Saftey check for proper usage.
|
|
40
|
-
*/
|
|
41
|
-
if (!document?.kind) {
|
|
42
|
-
if (process.env.NODE_ENV === "production") {
|
|
43
|
-
throw new DataError("Bad DocumentNode", DataErrors.InvalidInput);
|
|
44
|
-
} else {
|
|
45
|
-
throw new DataError(
|
|
46
|
-
`Argument of ${JSON.stringify(
|
|
47
|
-
document,
|
|
48
|
-
)} passed to parser was not a valid GraphQL ` +
|
|
49
|
-
`DocumentNode. You may need to use 'graphql-tag' or another method ` +
|
|
50
|
-
`to convert your operation into a document`,
|
|
51
|
-
DataErrors.InvalidInput,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const fragments = document.definitions.filter(
|
|
57
|
-
(x: DefinitionNode) => x.kind === "FragmentDefinition",
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
const queries = document.definitions.filter(
|
|
61
|
-
(x: DefinitionNode) =>
|
|
62
|
-
x.kind === "OperationDefinition" &&
|
|
63
|
-
(x as OperationDefinitionNode).operation === "query",
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const mutations = document.definitions.filter(
|
|
67
|
-
(x: DefinitionNode) =>
|
|
68
|
-
x.kind === "OperationDefinition" &&
|
|
69
|
-
(x as OperationDefinitionNode).operation === "mutation",
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const subscriptions = document.definitions.filter(
|
|
73
|
-
(x: DefinitionNode) =>
|
|
74
|
-
x.kind === "OperationDefinition" &&
|
|
75
|
-
(x as OperationDefinitionNode).operation === "subscription",
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
if (fragments.length && !queries.length && !mutations.length) {
|
|
79
|
-
if (process.env.NODE_ENV === "production") {
|
|
80
|
-
throw new DataError("Fragment only", DataErrors.InvalidInput);
|
|
81
|
-
} else {
|
|
82
|
-
throw new DataError(
|
|
83
|
-
`Passing only a fragment to 'graphql' is not supported. ` +
|
|
84
|
-
`You must include a query or mutation as well`,
|
|
85
|
-
DataErrors.InvalidInput,
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (subscriptions.length) {
|
|
91
|
-
if (process.env.NODE_ENV === "production") {
|
|
92
|
-
throw new DataError("No subscriptions", DataErrors.InvalidInput);
|
|
93
|
-
} else {
|
|
94
|
-
throw new DataError(
|
|
95
|
-
`We do not support subscriptions. ` +
|
|
96
|
-
`${JSON.stringify(document)} had ${
|
|
97
|
-
subscriptions.length
|
|
98
|
-
} subscriptions`,
|
|
99
|
-
DataErrors.InvalidInput,
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (queries.length + mutations.length > 1) {
|
|
105
|
-
if (process.env.NODE_ENV === "production") {
|
|
106
|
-
throw new DataError("Too many ops", DataErrors.InvalidInput);
|
|
107
|
-
} else {
|
|
108
|
-
throw new DataError(
|
|
109
|
-
`We only support one query or mutation per component. ` +
|
|
110
|
-
`${JSON.stringify(document)} had ${
|
|
111
|
-
queries.length
|
|
112
|
-
} queries and ` +
|
|
113
|
-
`${mutations.length} mutations. `,
|
|
114
|
-
DataErrors.InvalidInput,
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const type = queries.length ? DocumentTypes.query : DocumentTypes.mutation;
|
|
120
|
-
const definitions = queries.length ? queries : mutations;
|
|
121
|
-
|
|
122
|
-
const definition: OperationDefinitionNode = definitions[0] as any;
|
|
123
|
-
const variables = definition.variableDefinitions || [];
|
|
124
|
-
|
|
125
|
-
// fallback to using data if no name
|
|
126
|
-
const name =
|
|
127
|
-
definition.name?.kind === "Name" ? definition.name.value : "data";
|
|
128
|
-
|
|
129
|
-
const payload: IDocumentDefinition = {name, type, variables};
|
|
130
|
-
cache.set(document, payload);
|
|
131
|
-
return payload;
|
|
132
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// WARNING: If you modify this file you must update graphql-types.js.flow.
|
|
2
|
-
// NOTE(somewhatabstract):
|
|
3
|
-
// These types are bare minimum to support document parsing. They're derived
|
|
4
|
-
// from graphql@14.5.8, the last version that provided TypeScript types.
|
|
5
|
-
// Doing this avoids us having to take a dependency on that library just for
|
|
6
|
-
// these types.
|
|
7
|
-
export interface DefinitionNode {
|
|
8
|
-
readonly kind: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type VariableDefinitionNode = {
|
|
12
|
-
readonly kind: "VariableDefinition";
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export interface OperationDefinitionNode extends DefinitionNode {
|
|
16
|
-
readonly kind: "OperationDefinition";
|
|
17
|
-
readonly operation: string;
|
|
18
|
-
readonly variableDefinitions: ReadonlyArray<VariableDefinitionNode>;
|
|
19
|
-
readonly name?: {
|
|
20
|
-
readonly kind: unknown;
|
|
21
|
-
readonly value: string;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type DocumentNode = {
|
|
26
|
-
readonly kind: "Document";
|
|
27
|
-
readonly definitions: ReadonlyArray<DefinitionNode>;
|
|
28
|
-
};
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import {SsrCache} from "./ssr-cache";
|
|
2
|
-
|
|
3
|
-
import type {ValidCacheData, CachedResponse, ResponseCache} from "./types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Initialize the hydration cache.
|
|
7
|
-
*
|
|
8
|
-
* @param {ResponseCache} source The cache content to use for initializing the
|
|
9
|
-
* cache.
|
|
10
|
-
* @throws {Error} If the cache is already initialized.
|
|
11
|
-
*/
|
|
12
|
-
export const initializeHydrationCache = (source: ResponseCache): void =>
|
|
13
|
-
SsrCache.Default.initialize(source);
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Purge cached hydration responses that match the given predicate.
|
|
17
|
-
*
|
|
18
|
-
* @param {(id: string) => boolean} [predicate] The predicate to match against
|
|
19
|
-
* the cached hydration responses. If no predicate is provided, all cached
|
|
20
|
-
* hydration responses will be purged.
|
|
21
|
-
*/
|
|
22
|
-
export const purgeHydrationCache = (
|
|
23
|
-
predicate?: (
|
|
24
|
-
key: string,
|
|
25
|
-
cacheEntry?:
|
|
26
|
-
| Readonly<CachedResponse<ValidCacheData>>
|
|
27
|
-
| null
|
|
28
|
-
| undefined,
|
|
29
|
-
) => boolean,
|
|
30
|
-
): void => SsrCache.Default.purgeData(predicate);
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type {GqlContext} from "./gql-types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Construct a complete GqlContext from current defaults and a partial context.
|
|
5
|
-
*
|
|
6
|
-
* Values in the partial context that are `undefined` will be ignored.
|
|
7
|
-
* Values in the partial context that are `null` will be deleted.
|
|
8
|
-
*/
|
|
9
|
-
export const mergeGqlContext = <TContext extends GqlContext>(
|
|
10
|
-
defaultContext: TContext,
|
|
11
|
-
overrides: Partial<TContext>,
|
|
12
|
-
): TContext => {
|
|
13
|
-
// Let's merge the partial context default context. We deliberately
|
|
14
|
-
// don't spread because spreading would overwrite default context
|
|
15
|
-
// values with undefined or null if the partial context includes a value
|
|
16
|
-
// explicitly set to undefined or null.
|
|
17
|
-
return Object.keys(overrides).reduce(
|
|
18
|
-
(acc, key) => {
|
|
19
|
-
// Undefined values are ignored.
|
|
20
|
-
if (overrides[key] !== undefined) {
|
|
21
|
-
if (overrides[key] === null) {
|
|
22
|
-
// Null indicates we delete this context value.
|
|
23
|
-
delete acc[key];
|
|
24
|
-
} else {
|
|
25
|
-
// Otherwise, we set it.
|
|
26
|
-
// @ts-expect-error TypeScript doesn't seem to see that
|
|
27
|
-
// TContext can have string keys.
|
|
28
|
-
acc[key] = overrides[key];
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return acc;
|
|
32
|
-
},
|
|
33
|
-
{...defaultContext},
|
|
34
|
-
);
|
|
35
|
-
};
|
package/src/util/purge-caches.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import {SharedCache} from "../hooks/use-shared-cache";
|
|
2
|
-
import {purgeHydrationCache} from "./hydration-cache-api";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Purge all caches managed by Wonder Blocks Data.
|
|
6
|
-
*
|
|
7
|
-
* This is a convenience method that purges the shared cache and the hydration
|
|
8
|
-
* cache. It is useful for testing purposes to avoid having to reason about
|
|
9
|
-
* which caches may have been used during a given test run.
|
|
10
|
-
*/
|
|
11
|
-
export const purgeCaches = () => {
|
|
12
|
-
SharedCache.purgeAll();
|
|
13
|
-
purgeHydrationCache();
|
|
14
|
-
};
|
package/src/util/request-api.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import {Server} from "@khanacademy/wonder-blocks-core";
|
|
2
|
-
import {RequestTracker} from "./request-tracking";
|
|
3
|
-
import {RequestFulfillment} from "./request-fulfillment";
|
|
4
|
-
import {DataError, DataErrors} from "./data-error";
|
|
5
|
-
|
|
6
|
-
import type {ResponseCache} from "./types";
|
|
7
|
-
|
|
8
|
-
const SSRCheck = () => {
|
|
9
|
-
if (Server.isServerSide()) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (process.env.NODE_ENV === "production") {
|
|
14
|
-
return new DataError("No CSR tracking", DataErrors.NotAllowed);
|
|
15
|
-
} else {
|
|
16
|
-
return new DataError(
|
|
17
|
-
"Data requests are not tracked for fulfillment when when client-side",
|
|
18
|
-
DataErrors.NotAllowed,
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Fetches all tracked data requests.
|
|
25
|
-
*
|
|
26
|
-
* This is for use with the `TrackData` component during server-side rendering.
|
|
27
|
-
*
|
|
28
|
-
* @throws {Error} If executed outside of server-side rendering.
|
|
29
|
-
* @returns {Promise<void>} A promise that resolves when all tracked requests
|
|
30
|
-
* have been fetched.
|
|
31
|
-
*/
|
|
32
|
-
export const fetchTrackedRequests = (): Promise<ResponseCache> => {
|
|
33
|
-
const ssrCheck = SSRCheck();
|
|
34
|
-
if (ssrCheck != null) {
|
|
35
|
-
return Promise.reject(ssrCheck);
|
|
36
|
-
}
|
|
37
|
-
return RequestTracker.Default.fulfillTrackedRequests();
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Indicate if there are tracked requests waiting to be fetched.
|
|
42
|
-
*
|
|
43
|
-
* This is used in conjunction with `TrackData`.
|
|
44
|
-
*
|
|
45
|
-
* @throws {Error} If executed outside of server-side rendering.
|
|
46
|
-
* @returns {boolean} `true` if there are unfetched tracked requests;
|
|
47
|
-
* otherwise, `false`.
|
|
48
|
-
*/
|
|
49
|
-
export const hasTrackedRequestsToBeFetched = (): boolean => {
|
|
50
|
-
const ssrCheck = SSRCheck();
|
|
51
|
-
if (ssrCheck != null) {
|
|
52
|
-
throw ssrCheck;
|
|
53
|
-
}
|
|
54
|
-
return RequestTracker.Default.hasUnfulfilledRequests;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Abort all in-flight requests.
|
|
59
|
-
*
|
|
60
|
-
* This aborts all requests currently inflight via our default request
|
|
61
|
-
* fulfillment.
|
|
62
|
-
*/
|
|
63
|
-
export const abortInflightRequests = (): void => {
|
|
64
|
-
RequestFulfillment.Default.abortAll();
|
|
65
|
-
};
|