@khanacademy/wonder-blocks-data 10.1.0 → 10.1.2
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 +31 -0
- package/dist/components/data.d.ts +52 -0
- package/dist/components/data.js.flow +63 -0
- package/dist/components/gql-router.d.ts +24 -0
- package/dist/components/gql-router.js.flow +33 -0
- package/dist/components/intercept-context.d.ts +10 -0
- package/dist/components/intercept-context.js.flow +19 -0
- package/dist/components/intercept-requests.d.ts +42 -0
- package/dist/components/intercept-requests.js.flow +51 -0
- package/dist/components/track-data.d.ts +11 -0
- package/dist/components/track-data.js.flow +18 -0
- package/dist/es/index.js +184 -212
- package/dist/hooks/use-cached-effect.d.ts +70 -0
- package/dist/hooks/use-cached-effect.js.flow +85 -0
- package/dist/hooks/use-gql-router-context.d.ts +5 -0
- package/dist/hooks/use-gql-router-context.js.flow +15 -0
- package/dist/hooks/use-gql.d.ts +12 -0
- package/dist/hooks/use-gql.js.flow +29 -0
- package/dist/hooks/use-hydratable-effect.d.ts +102 -0
- package/dist/hooks/use-hydratable-effect.js.flow +125 -0
- package/dist/hooks/use-request-interception.d.ts +14 -0
- package/dist/hooks/use-request-interception.js.flow +25 -0
- package/dist/hooks/use-server-effect.d.ts +39 -0
- package/dist/hooks/use-server-effect.js.flow +51 -0
- package/dist/hooks/use-shared-cache.d.ts +32 -0
- package/dist/hooks/use-shared-cache.js.flow +43 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +186 -217
- package/dist/index.js.flow +48 -2
- package/dist/util/data-error.d.ts +45 -0
- package/dist/util/data-error.js.flow +64 -0
- package/dist/util/get-gql-data-from-response.d.ts +4 -0
- package/dist/util/get-gql-data-from-response.js.flow +13 -0
- package/dist/util/get-gql-request-id.d.ts +5 -0
- package/dist/util/get-gql-request-id.js.flow +20 -0
- package/dist/util/gql-error.d.ts +28 -0
- package/dist/util/gql-error.js.flow +43 -0
- package/dist/util/gql-router-context.d.ts +3 -0
- package/dist/util/gql-router-context.js.flow +10 -0
- package/dist/util/gql-types.d.ts +34 -0
- package/dist/util/gql-types.js.flow +53 -0
- package/dist/util/graphql-document-node-parser.d.ts +18 -0
- package/dist/util/graphql-document-node-parser.js.flow +31 -0
- package/dist/util/graphql-types.d.ts +19 -0
- package/dist/util/graphql-types.js.flow +30 -0
- package/dist/util/hydration-cache-api.d.ts +17 -0
- package/dist/util/hydration-cache-api.js.flow +30 -0
- package/dist/util/merge-gql-context.d.ts +8 -0
- package/dist/util/merge-gql-context.js.flow +19 -0
- package/dist/util/purge-caches.d.ts +8 -0
- package/dist/util/purge-caches.js.flow +15 -0
- package/dist/util/request-api.d.ts +28 -0
- package/dist/util/request-api.js.flow +34 -0
- package/dist/util/request-fulfillment.d.ts +37 -0
- package/dist/util/request-fulfillment.js.flow +50 -0
- package/dist/util/request-tracking.d.ts +62 -0
- package/dist/util/request-tracking.js.flow +81 -0
- package/dist/util/result-from-cache-response.d.ts +5 -0
- package/dist/util/result-from-cache-response.js.flow +15 -0
- package/dist/util/scoped-in-memory-cache.d.ts +38 -0
- package/dist/util/scoped-in-memory-cache.js.flow +57 -0
- package/dist/util/serializable-in-memory-cache.d.ts +16 -0
- package/dist/util/serializable-in-memory-cache.js.flow +26 -0
- package/dist/util/ssr-cache.d.ts +51 -0
- package/dist/util/ssr-cache.js.flow +87 -0
- package/dist/util/status.d.ts +10 -0
- package/dist/util/status.js.flow +19 -0
- package/dist/util/to-gql-operation.d.ts +32 -0
- package/dist/util/to-gql-operation.js.flow +45 -0
- package/dist/util/types.d.ts +111 -0
- package/dist/util/types.js.flow +151 -0
- package/package.json +5 -6
- package/src/components/__tests__/{data.test.js → data.test.tsx} +42 -2
- package/src/components/__tests__/{gql-router.test.js → gql-router.test.tsx} +4 -5
- package/src/components/__tests__/{intercept-requests.test.js → intercept-requests.test.tsx} +2 -3
- package/src/components/__tests__/{track-data.test.js → track-data.test.tsx} +2 -3
- package/src/components/{data.js → data.ts} +11 -15
- package/src/components/{gql-router.js → gql-router.tsx} +12 -14
- package/src/components/{intercept-context.js → intercept-context.ts} +4 -3
- package/src/components/{intercept-requests.js → intercept-requests.tsx} +7 -8
- package/src/components/{track-data.js → track-data.tsx} +4 -5
- package/src/hooks/__tests__/{use-cached-effect.test.js → use-cached-effect.test.tsx} +55 -50
- package/src/hooks/__tests__/{use-gql-router-context.test.js → use-gql-router-context.test.tsx} +7 -7
- package/src/hooks/__tests__/{use-gql.test.js → use-gql.test.tsx} +20 -21
- package/src/hooks/__tests__/{use-hydratable-effect.test.js → use-hydratable-effect.test.ts} +42 -37
- package/src/hooks/__tests__/{use-request-interception.test.js → use-request-interception.test.tsx} +5 -3
- package/src/hooks/__tests__/{use-server-effect.test.js → use-server-effect.test.ts} +8 -2
- package/src/hooks/__tests__/{use-shared-cache.test.js → use-shared-cache.test.ts} +12 -12
- package/src/hooks/{use-cached-effect.js → use-cached-effect.ts} +27 -20
- package/src/hooks/{use-gql-router-context.js → use-gql-router-context.ts} +2 -3
- package/src/hooks/{use-gql.js → use-gql.ts} +5 -5
- package/src/hooks/{use-hydratable-effect.js → use-hydratable-effect.ts} +53 -58
- package/src/hooks/{use-request-interception.js → use-request-interception.ts} +4 -4
- package/src/hooks/{use-server-effect.js → use-server-effect.ts} +7 -9
- package/src/hooks/{use-shared-cache.js → use-shared-cache.ts} +13 -8
- package/src/{index.js → index.ts} +0 -1
- package/src/util/__tests__/{get-gql-data-from-response.test.js → get-gql-data-from-response.test.ts} +0 -1
- package/src/util/__tests__/{get-gql-request-id.test.js → get-gql-request-id.test.ts} +9 -11
- package/src/util/__tests__/{graphql-document-node-parser.test.js → graphql-document-node-parser.test.ts} +11 -12
- package/src/util/__tests__/{hydration-cache-api.test.js → hydration-cache-api.test.ts} +1 -2
- package/src/util/__tests__/{merge-gql-context.test.js → merge-gql-context.test.ts} +4 -5
- package/src/util/__tests__/{purge-caches.test.js → purge-caches.test.ts} +0 -1
- package/src/util/__tests__/{request-api.test.js → request-api.test.ts} +2 -2
- package/src/util/__tests__/{request-fulfillment.test.js → request-fulfillment.test.ts} +0 -1
- package/src/util/__tests__/{request-tracking.test.js → request-tracking.test.tsx} +13 -6
- package/src/util/__tests__/{result-from-cache-response.test.js → result-from-cache-response.test.ts} +2 -4
- package/src/util/__tests__/{scoped-in-memory-cache.test.js → scoped-in-memory-cache.test.ts} +4 -5
- package/src/util/__tests__/{serializable-in-memory-cache.test.js → serializable-in-memory-cache.test.ts} +7 -7
- package/src/util/__tests__/{ssr-cache.test.js → ssr-cache.test.ts} +3 -2
- package/src/util/__tests__/{to-gql-operation.test.js → to-gql-operation.test.ts} +2 -1
- package/src/util/{data-error.js → data-error.ts} +2 -3
- package/src/util/{get-gql-data-from-response.js → get-gql-data-from-response.ts} +1 -6
- package/src/util/{get-gql-request-id.js → get-gql-request-id.ts} +12 -16
- package/src/util/{gql-error.js → gql-error.ts} +2 -3
- package/src/util/gql-router-context.ts +6 -0
- package/src/util/{gql-types.js → gql-types.ts} +27 -23
- package/src/util/{graphql-document-node-parser.js → graphql-document-node-parser.ts} +6 -7
- package/src/util/graphql-types.ts +27 -0
- package/src/util/{hydration-cache-api.js → hydration-cache-api.ts} +4 -2
- package/src/util/{merge-gql-context.js → merge-gql-context.ts} +2 -2
- package/src/util/{purge-caches.js → purge-caches.ts} +0 -1
- package/src/util/{request-api.js → request-api.ts} +0 -1
- package/src/util/{request-fulfillment.js → request-fulfillment.ts} +13 -12
- package/src/util/{request-tracking.js → request-tracking.ts} +12 -13
- package/src/util/{result-from-cache-response.js → result-from-cache-response.ts} +3 -4
- package/src/util/{scoped-in-memory-cache.js → scoped-in-memory-cache.ts} +1 -2
- package/src/util/{serializable-in-memory-cache.js → serializable-in-memory-cache.ts} +2 -3
- package/src/util/{ssr-cache.js → ssr-cache.ts} +19 -18
- package/src/util/{status.js → status.ts} +4 -5
- package/src/util/{to-gql-operation.js → to-gql-operation.ts} +1 -2
- package/src/util/{types.js → types.ts} +39 -48
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/src/__docs__/_overview_.stories.mdx +0 -18
- package/src/__docs__/_overview_graphql.stories.mdx +0 -35
- package/src/__docs__/_overview_ssr_.stories.mdx +0 -185
- package/src/__docs__/_overview_testing_.stories.mdx +0 -123
- package/src/__docs__/exports.abort-inflight-requests.stories.mdx +0 -20
- package/src/__docs__/exports.data-error.stories.mdx +0 -23
- package/src/__docs__/exports.data-errors.stories.mdx +0 -23
- package/src/__docs__/exports.data.stories.mdx +0 -146
- package/src/__docs__/exports.fetch-tracked-requests.stories.mdx +0 -24
- package/src/__docs__/exports.get-gql-request-id.stories.mdx +0 -24
- package/src/__docs__/exports.gql-error.stories.mdx +0 -23
- package/src/__docs__/exports.gql-errors.stories.mdx +0 -20
- package/src/__docs__/exports.gql-router.stories.mdx +0 -29
- package/src/__docs__/exports.has-tracked-requests-to-be-fetched.stories.mdx +0 -20
- package/src/__docs__/exports.intercept-requests.stories.mdx +0 -69
- package/src/__docs__/exports.intialize-hydration-cache.stories.mdx +0 -29
- package/src/__docs__/exports.purge-caches.stories.mdx +0 -23
- package/src/__docs__/exports.purge-hydration-cache.stories.mdx +0 -24
- package/src/__docs__/exports.scoped-in-memory-cache.stories.mdx +0 -92
- package/src/__docs__/exports.serializable-in-memory-cache.stories.mdx +0 -112
- package/src/__docs__/exports.shared-cache.stories.mdx +0 -16
- package/src/__docs__/exports.status.stories.mdx +0 -31
- package/src/__docs__/exports.track-data.stories.mdx +0 -209
- package/src/__docs__/exports.use-cached-effect.stories.mdx +0 -44
- package/src/__docs__/exports.use-gql.stories.mdx +0 -41
- package/src/__docs__/exports.use-hydratable-effect.stories.mdx +0 -43
- package/src/__docs__/exports.use-server-effect.stories.mdx +0 -50
- package/src/__docs__/exports.use-shared-cache.stories.mdx +0 -30
- package/src/__docs__/exports.when-client-side.stories.mdx +0 -33
- package/src/__docs__/types.cached-response.stories.mdx +0 -29
- package/src/__docs__/types.error-options.stories.mdx +0 -21
- package/src/__docs__/types.fetch-policy.stories.mdx +0 -44
- package/src/__docs__/types.gql-context.stories.mdx +0 -20
- package/src/__docs__/types.gql-fetch-fn.stories.mdx +0 -24
- package/src/__docs__/types.gql-fetch-options.stories.mdx +0 -24
- package/src/__docs__/types.gql-operation-type.stories.mdx +0 -24
- package/src/__docs__/types.gql-operation.stories.mdx +0 -67
- package/src/__docs__/types.raw-scoped-cache.stories.mdx +0 -27
- package/src/__docs__/types.response-cache.stories.mdx +0 -33
- package/src/__docs__/types.result.stories.mdx +0 -39
- package/src/__docs__/types.scoped-cache.stories.mdx +0 -114
- package/src/__docs__/types.valid-cache-data.stories.mdx +0 -23
- package/src/util/gql-router-context.js +0 -6
- package/src/util/graphql-types.js +0 -30
- /package/src/hooks/__tests__/__snapshots__/{use-shared-cache.test.js.snap → use-shared-cache.test.ts.snap} +0 -0
- /package/src/util/__tests__/__snapshots__/{scoped-in-memory-cache.test.js.snap → scoped-in-memory-cache.test.ts.snap} +0 -0
- /package/src/util/__tests__/__snapshots__/{serializable-in-memory-cache.test.js.snap → serializable-in-memory-cache.test.ts.snap} +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import type {
|
|
3
2
|
DocumentNode,
|
|
4
3
|
DefinitionNode,
|
|
@@ -12,12 +11,12 @@ export const DocumentTypes = Object.freeze({
|
|
|
12
11
|
mutation: "mutation",
|
|
13
12
|
});
|
|
14
13
|
|
|
15
|
-
export type DocumentType =
|
|
14
|
+
export type DocumentType = typeof DocumentTypes[keyof typeof DocumentTypes];
|
|
16
15
|
|
|
17
16
|
export interface IDocumentDefinition {
|
|
18
17
|
type: DocumentType;
|
|
19
18
|
name: string;
|
|
20
|
-
variables:
|
|
19
|
+
variables: ReadonlyArray<VariableDefinitionNode>;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
const cache = new Map<DocumentNode, IDocumentDefinition>();
|
|
@@ -60,19 +59,19 @@ export function graphQLDocumentNodeParser(
|
|
|
60
59
|
|
|
61
60
|
const queries = document.definitions.filter(
|
|
62
61
|
(x: DefinitionNode) =>
|
|
63
|
-
//
|
|
62
|
+
// @ts-expect-error [FEI-5019] - TS2339 - Property 'operation' does not exist on type 'DefinitionNode'.
|
|
64
63
|
x.kind === "OperationDefinition" && x.operation === "query",
|
|
65
64
|
);
|
|
66
65
|
|
|
67
66
|
const mutations = document.definitions.filter(
|
|
68
67
|
(x: DefinitionNode) =>
|
|
69
|
-
//
|
|
68
|
+
// @ts-expect-error [FEI-5019] - TS2339 - Property 'operation' does not exist on type 'DefinitionNode'.
|
|
70
69
|
x.kind === "OperationDefinition" && x.operation === "mutation",
|
|
71
70
|
);
|
|
72
71
|
|
|
73
72
|
const subscriptions = document.definitions.filter(
|
|
74
73
|
(x: DefinitionNode) =>
|
|
75
|
-
//
|
|
74
|
+
// @ts-expect-error [FEI-5019] - TS2339 - Property 'operation' does not exist on type 'DefinitionNode'.
|
|
76
75
|
x.kind === "OperationDefinition" && x.operation === "subscription",
|
|
77
76
|
);
|
|
78
77
|
|
|
@@ -120,7 +119,7 @@ export function graphQLDocumentNodeParser(
|
|
|
120
119
|
const type = queries.length ? DocumentTypes.query : DocumentTypes.mutation;
|
|
121
120
|
const definitions = queries.length ? queries : mutations;
|
|
122
121
|
|
|
123
|
-
const definition: OperationDefinitionNode =
|
|
122
|
+
const definition: OperationDefinitionNode = definitions[0] as any;
|
|
124
123
|
const variables = definition.variableDefinitions || [];
|
|
125
124
|
|
|
126
125
|
// fallback to using data if no name
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// NOTE(somewhatabstract):
|
|
2
|
+
// These types are bare minimum to support document parsing. They're derived
|
|
3
|
+
// from graphql@14.5.8, the last version that provided TypeScript types.
|
|
4
|
+
// Doing this avoids us having to take a dependency on that library just for
|
|
5
|
+
// these types.
|
|
6
|
+
export interface DefinitionNode {
|
|
7
|
+
readonly kind: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type VariableDefinitionNode = {
|
|
11
|
+
readonly kind: "VariableDefinition";
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface OperationDefinitionNode extends DefinitionNode {
|
|
15
|
+
readonly kind: "OperationDefinition";
|
|
16
|
+
readonly operation: string;
|
|
17
|
+
readonly variableDefinitions: ReadonlyArray<VariableDefinitionNode>;
|
|
18
|
+
readonly name?: {
|
|
19
|
+
readonly kind: unknown;
|
|
20
|
+
readonly value: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type DocumentNode = {
|
|
25
|
+
readonly kind: "Document";
|
|
26
|
+
readonly definitions: ReadonlyArray<DefinitionNode>;
|
|
27
|
+
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import {SsrCache} from "./ssr-cache";
|
|
3
2
|
|
|
4
3
|
import type {ValidCacheData, CachedResponse, ResponseCache} from "./types";
|
|
@@ -23,6 +22,9 @@ export const initializeHydrationCache = (source: ResponseCache): void =>
|
|
|
23
22
|
export const purgeHydrationCache = (
|
|
24
23
|
predicate?: (
|
|
25
24
|
key: string,
|
|
26
|
-
cacheEntry
|
|
25
|
+
cacheEntry?:
|
|
26
|
+
| Readonly<CachedResponse<ValidCacheData>>
|
|
27
|
+
| null
|
|
28
|
+
| undefined,
|
|
27
29
|
) => boolean,
|
|
28
30
|
): void => SsrCache.Default.purgeData(predicate);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import type {GqlContext} from "./gql-types";
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -7,7 +6,7 @@ import type {GqlContext} from "./gql-types";
|
|
|
7
6
|
* Values in the partial context that are `undefined` will be ignored.
|
|
8
7
|
* Values in the partial context that are `null` will be deleted.
|
|
9
8
|
*/
|
|
10
|
-
export const mergeGqlContext = <TContext
|
|
9
|
+
export const mergeGqlContext = <TContext extends GqlContext>(
|
|
11
10
|
defaultContext: TContext,
|
|
12
11
|
overrides: Partial<TContext>,
|
|
13
12
|
): TContext => {
|
|
@@ -24,6 +23,7 @@ export const mergeGqlContext = <TContext: GqlContext>(
|
|
|
24
23
|
delete acc[key];
|
|
25
24
|
} else {
|
|
26
25
|
// Otherwise, we set it.
|
|
26
|
+
// @ts-expect-error [FEI-5019] - TS2536 - Type 'string' cannot be used to index type 'TContext'.
|
|
27
27
|
acc[key] = overrides[key];
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import type {Result, ValidCacheData} from "./types";
|
|
3
2
|
|
|
4
3
|
import {DataError, DataErrors} from "./data-error";
|
|
5
4
|
|
|
6
5
|
type RequestCache = {
|
|
7
|
-
[id: string]: Promise<Result<any
|
|
6
|
+
[id: string]: Promise<Result<any>>;
|
|
8
7
|
};
|
|
9
8
|
|
|
10
|
-
type FulfillOptions<TData
|
|
11
|
-
handler: () => Promise<TData
|
|
12
|
-
hydrate?: boolean
|
|
13
|
-
|
|
9
|
+
type FulfillOptions<TData extends ValidCacheData> = {
|
|
10
|
+
handler: () => Promise<TData>;
|
|
11
|
+
hydrate?: boolean;
|
|
12
|
+
};
|
|
14
13
|
|
|
15
14
|
let _default: RequestFulfillment;
|
|
16
15
|
|
|
@@ -33,10 +32,10 @@ export class RequestFulfillment {
|
|
|
33
32
|
* This will return an inflight request if one exists, otherwise it will
|
|
34
33
|
* make a new request. Inflight requests are deleted once they resolve.
|
|
35
34
|
*/
|
|
36
|
-
fulfill: <TData
|
|
35
|
+
fulfill: <TData extends ValidCacheData>(
|
|
37
36
|
id: string,
|
|
38
37
|
options: FulfillOptions<TData>,
|
|
39
|
-
) => Promise<Result<TData>> = <TData
|
|
38
|
+
) => Promise<Result<TData>> = <TData extends ValidCacheData>(
|
|
40
39
|
id: string,
|
|
41
40
|
{handler, hydrate = true}: FulfillOptions<TData>,
|
|
42
41
|
): Promise<Result<TData>> => {
|
|
@@ -52,10 +51,12 @@ export class RequestFulfillment {
|
|
|
52
51
|
* We don't have an inflight request, so let's set one up.
|
|
53
52
|
*/
|
|
54
53
|
const request = handler()
|
|
55
|
-
.then(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
.then(
|
|
55
|
+
(data: TData): Result<TData> => ({
|
|
56
|
+
status: "success",
|
|
57
|
+
data,
|
|
58
|
+
}),
|
|
59
|
+
)
|
|
59
60
|
.catch((error: string | Error): Result<TData> => {
|
|
60
61
|
const actualError =
|
|
61
62
|
typeof error === "string"
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import {SsrCache} from "./ssr-cache";
|
|
4
3
|
import {RequestFulfillment} from "./request-fulfillment";
|
|
5
4
|
|
|
6
5
|
import type {ResponseCache, ValidCacheData} from "./types";
|
|
7
6
|
|
|
8
|
-
type TrackerFn = <TData
|
|
7
|
+
type TrackerFn = <TData extends ValidCacheData>(
|
|
9
8
|
id: string,
|
|
10
9
|
handler: () => Promise<TData>,
|
|
11
10
|
hydrate: boolean,
|
|
12
11
|
) => void;
|
|
13
12
|
|
|
14
13
|
type RequestCache = {
|
|
15
|
-
[id: string]: {
|
|
16
|
-
hydrate: boolean
|
|
17
|
-
handler: () => Promise<any
|
|
18
|
-
|
|
19
|
-
...
|
|
14
|
+
[id: string]: {
|
|
15
|
+
hydrate: boolean;
|
|
16
|
+
handler: () => Promise<any>;
|
|
17
|
+
};
|
|
20
18
|
};
|
|
21
19
|
|
|
22
20
|
/**
|
|
@@ -24,8 +22,8 @@ type RequestCache = {
|
|
|
24
22
|
*
|
|
25
23
|
* INTERNAL USE ONLY
|
|
26
24
|
*/
|
|
27
|
-
export const TrackerContext: React.Context
|
|
28
|
-
React.createContext
|
|
25
|
+
export const TrackerContext: React.Context<TrackerFn | null | undefined> =
|
|
26
|
+
React.createContext<TrackerFn | null | undefined>(null);
|
|
29
27
|
|
|
30
28
|
/**
|
|
31
29
|
* The default instance is stored here.
|
|
@@ -53,7 +51,8 @@ export class RequestTracker {
|
|
|
53
51
|
_responseCache: SsrCache;
|
|
54
52
|
_requestFulfillment: RequestFulfillment;
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
// @ts-expect-error [FEI-5019] - TS2322 - Type 'undefined' is not assignable to type 'SsrCache | null'.
|
|
55
|
+
constructor(responseCache: SsrCache | null = undefined) {
|
|
57
56
|
this._responseCache = responseCache || SsrCache.Default;
|
|
58
57
|
this._requestFulfillment = new RequestFulfillment();
|
|
59
58
|
}
|
|
@@ -64,11 +63,11 @@ export class RequestTracker {
|
|
|
64
63
|
* This method caches a request and its handler for use during server-side
|
|
65
64
|
* rendering to allow us to fulfill requests before producing a final render.
|
|
66
65
|
*/
|
|
67
|
-
trackDataRequest: <TData
|
|
66
|
+
trackDataRequest: <TData extends ValidCacheData>(
|
|
68
67
|
id: string,
|
|
69
68
|
handler: () => Promise<TData>,
|
|
70
69
|
hydrate: boolean,
|
|
71
|
-
) => void = <TData
|
|
70
|
+
) => void = <TData extends ValidCacheData>(
|
|
72
71
|
id: string,
|
|
73
72
|
handler: () => Promise<TData>,
|
|
74
73
|
hydrate: boolean,
|
|
@@ -167,7 +166,7 @@ export class RequestTracker {
|
|
|
167
166
|
return;
|
|
168
167
|
}),
|
|
169
168
|
);
|
|
170
|
-
} catch (e) {
|
|
169
|
+
} catch (e: any) {
|
|
171
170
|
// This captures if there are problems in the code that
|
|
172
171
|
// begins the requests.
|
|
173
172
|
promises.push(
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import {Status} from "./status";
|
|
3
2
|
import {DataError, DataErrors} from "./data-error";
|
|
4
3
|
import type {ValidCacheData, CachedResponse, Result} from "./types";
|
|
@@ -6,9 +5,9 @@ import type {ValidCacheData, CachedResponse, Result} from "./types";
|
|
|
6
5
|
/**
|
|
7
6
|
* Turns a cache entry into a stateful result.
|
|
8
7
|
*/
|
|
9
|
-
export const resultFromCachedResponse = <TData
|
|
10
|
-
cacheEntry
|
|
11
|
-
):
|
|
8
|
+
export const resultFromCachedResponse = <TData extends ValidCacheData>(
|
|
9
|
+
cacheEntry?: CachedResponse<TData> | null,
|
|
10
|
+
): Result<TData> | null | undefined => {
|
|
12
11
|
// No cache entry means no result to be hydrated.
|
|
13
12
|
if (cacheEntry == null) {
|
|
14
13
|
return null;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import {DataError, DataErrors} from "./data-error";
|
|
3
2
|
import type {ScopedCache, RawScopedCache, ValidCacheData} from "./types";
|
|
4
3
|
|
|
@@ -53,7 +52,7 @@ export class ScopedInMemoryCache implements ScopedCache {
|
|
|
53
52
|
/**
|
|
54
53
|
* Retrieve a value from the cache.
|
|
55
54
|
*/
|
|
56
|
-
get(scope: string, id: string):
|
|
55
|
+
get(scope: string, id: string): ValidCacheData | null | undefined {
|
|
57
56
|
return this._cache[scope]?.[id] ?? null;
|
|
58
57
|
}
|
|
59
58
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import {clone} from "@khanacademy/wonder-stuff-core";
|
|
3
2
|
import {DataError, DataErrors} from "./data-error";
|
|
4
3
|
import {ScopedInMemoryCache} from "./scoped-in-memory-cache";
|
|
@@ -11,7 +10,7 @@ export class SerializableInMemoryCache extends ScopedInMemoryCache {
|
|
|
11
10
|
constructor(initialCache: RawScopedCache = {}) {
|
|
12
11
|
try {
|
|
13
12
|
super(clone(initialCache));
|
|
14
|
-
} catch (e) {
|
|
13
|
+
} catch (e: any) {
|
|
15
14
|
throw new DataError(
|
|
16
15
|
`An error occurred trying to initialize from a response cache snapshot: ${e}`,
|
|
17
16
|
DataErrors.InvalidInput,
|
|
@@ -32,7 +31,7 @@ export class SerializableInMemoryCache extends ScopedInMemoryCache {
|
|
|
32
31
|
clone(): RawScopedCache {
|
|
33
32
|
try {
|
|
34
33
|
return clone(this._cache);
|
|
35
|
-
} catch (e) {
|
|
34
|
+
} catch (e: any) {
|
|
36
35
|
throw new DataError(
|
|
37
36
|
"An error occurred while trying to clone the cache",
|
|
38
37
|
DataErrors.Internal,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import {Server} from "@khanacademy/wonder-blocks-core";
|
|
3
2
|
import {SerializableInMemoryCache} from "./serializable-in-memory-cache";
|
|
4
3
|
|
|
@@ -29,15 +28,15 @@ export class SsrCache {
|
|
|
29
28
|
_ssrOnlyCache: SerializableInMemoryCache;
|
|
30
29
|
|
|
31
30
|
constructor(
|
|
32
|
-
hydrationCache:
|
|
33
|
-
ssrOnlyCache:
|
|
31
|
+
hydrationCache: SerializableInMemoryCache | null = null,
|
|
32
|
+
ssrOnlyCache: SerializableInMemoryCache | null = null,
|
|
34
33
|
) {
|
|
35
34
|
this._ssrOnlyCache = ssrOnlyCache || new SerializableInMemoryCache();
|
|
36
35
|
this._hydrationCache =
|
|
37
36
|
hydrationCache || new SerializableInMemoryCache();
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
_setCachedResponse<TData
|
|
39
|
+
_setCachedResponse<TData extends ValidCacheData>(
|
|
41
40
|
id: string,
|
|
42
41
|
entry: CachedResponse<TData>,
|
|
43
42
|
hydrate: boolean,
|
|
@@ -70,7 +69,6 @@ export class SsrCache {
|
|
|
70
69
|
);
|
|
71
70
|
}
|
|
72
71
|
this._hydrationCache = new SerializableInMemoryCache({
|
|
73
|
-
// $FlowIgnore[incompatible-call]
|
|
74
72
|
[DefaultScope]: source,
|
|
75
73
|
});
|
|
76
74
|
};
|
|
@@ -80,11 +78,11 @@ export class SsrCache {
|
|
|
80
78
|
*
|
|
81
79
|
* This is a noop when client-side.
|
|
82
80
|
*/
|
|
83
|
-
cacheData: <TData
|
|
81
|
+
cacheData: <TData extends ValidCacheData>(
|
|
84
82
|
id: string,
|
|
85
83
|
data: TData,
|
|
86
84
|
hydrate: boolean,
|
|
87
|
-
) => CachedResponse<TData> = <TData
|
|
85
|
+
) => CachedResponse<TData> = <TData extends ValidCacheData>(
|
|
88
86
|
id: string,
|
|
89
87
|
data: TData,
|
|
90
88
|
hydrate: boolean,
|
|
@@ -95,11 +93,11 @@ export class SsrCache {
|
|
|
95
93
|
*
|
|
96
94
|
* This is a noop when client-side.
|
|
97
95
|
*/
|
|
98
|
-
cacheError: <TData
|
|
96
|
+
cacheError: <TData extends ValidCacheData>(
|
|
99
97
|
id: string,
|
|
100
98
|
error: Error | string,
|
|
101
99
|
hydrate: boolean,
|
|
102
|
-
) => CachedResponse<TData> = <TData
|
|
100
|
+
) => CachedResponse<TData> = <TData extends ValidCacheData>(
|
|
103
101
|
id: string,
|
|
104
102
|
error: Error | string,
|
|
105
103
|
hydrate: boolean,
|
|
@@ -111,11 +109,13 @@ export class SsrCache {
|
|
|
111
109
|
/**
|
|
112
110
|
* Retrieve data from our cache.
|
|
113
111
|
*/
|
|
114
|
-
getEntry: <TData
|
|
112
|
+
getEntry: <TData extends ValidCacheData>(
|
|
115
113
|
id: string,
|
|
116
|
-
) =>
|
|
114
|
+
) => Readonly<CachedResponse<TData>> | null | undefined = <
|
|
115
|
+
TData extends ValidCacheData,
|
|
116
|
+
>(
|
|
117
117
|
id: string,
|
|
118
|
-
):
|
|
118
|
+
): Readonly<CachedResponse<TData>> | null | undefined => {
|
|
119
119
|
// Get the cached entry for this value.
|
|
120
120
|
|
|
121
121
|
// We first look in the ssr cache, if we need to.
|
|
@@ -139,8 +139,8 @@ export class SsrCache {
|
|
|
139
139
|
this._hydrationCache.purge(DefaultScope, id);
|
|
140
140
|
}
|
|
141
141
|
// Getting the typing right between the in-memory cache and this
|
|
142
|
-
// is hard. Just telling
|
|
143
|
-
//
|
|
142
|
+
// is hard. Just telling TypeScript it's OK.
|
|
143
|
+
// @ts-expect-error [FEI-5019] - TS2322 - Type 'string | number | boolean | Record<any, any> | null | undefined' is not assignable to type 'Readonly<CachedResponse<TData>> | null | undefined'.
|
|
144
144
|
return internalEntry;
|
|
145
145
|
};
|
|
146
146
|
|
|
@@ -155,14 +155,15 @@ export class SsrCache {
|
|
|
155
155
|
purgeData: (
|
|
156
156
|
predicate?: (
|
|
157
157
|
key: string,
|
|
158
|
-
cachedEntry:
|
|
158
|
+
cachedEntry: Readonly<CachedResponse<ValidCacheData>>,
|
|
159
159
|
) => boolean,
|
|
160
160
|
) => void = (predicate) => {
|
|
161
161
|
const realPredicate = predicate
|
|
162
162
|
? // We know what we're putting into the cache so let's assume it
|
|
163
163
|
// conforms.
|
|
164
|
-
//
|
|
165
|
-
(_, key, cachedEntry) =>
|
|
164
|
+
// @ts-expect-error [FEI-5019] - TS7006 - Parameter 'cachedEntry' implicitly has an 'any' type.
|
|
165
|
+
(_: string, key: string, cachedEntry) =>
|
|
166
|
+
predicate(key, cachedEntry)
|
|
166
167
|
: undefined;
|
|
167
168
|
|
|
168
169
|
// Apply the predicate to what we have in our caches.
|
|
@@ -183,7 +184,7 @@ export class SsrCache {
|
|
|
183
184
|
// to an empty object.
|
|
184
185
|
// We only need the default scope out of our scoped in-memory cache.
|
|
185
186
|
// We know that it conforms to our expectations.
|
|
186
|
-
//
|
|
187
|
+
// @ts-expect-error [FEI-5019] - TS2322 - Type '{ [id: string]: ValidCacheData; }' is not assignable to type 'ResponseCache'.
|
|
187
188
|
return cache[DefaultScope] ?? {};
|
|
188
189
|
};
|
|
189
190
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import type {Result, ValidCacheData} from "./types";
|
|
3
2
|
|
|
4
3
|
const loadingStatus = Object.freeze({
|
|
@@ -13,15 +12,15 @@ const abortedStatus = Object.freeze({
|
|
|
13
12
|
* Create Result<TData> instances with specific statuses.
|
|
14
13
|
*/
|
|
15
14
|
export const Status = Object.freeze({
|
|
16
|
-
loading: <TData
|
|
15
|
+
loading: <TData extends ValidCacheData = ValidCacheData>(): Result<TData> =>
|
|
17
16
|
loadingStatus,
|
|
18
|
-
aborted: <TData
|
|
17
|
+
aborted: <TData extends ValidCacheData = ValidCacheData>(): Result<TData> =>
|
|
19
18
|
abortedStatus,
|
|
20
|
-
success: <TData
|
|
19
|
+
success: <TData extends ValidCacheData>(data: TData): Result<TData> => ({
|
|
21
20
|
status: "success",
|
|
22
21
|
data,
|
|
23
22
|
}),
|
|
24
|
-
error: <TData
|
|
23
|
+
error: <TData extends ValidCacheData = ValidCacheData>(
|
|
25
24
|
error: Error,
|
|
26
25
|
): Result<TData> => ({
|
|
27
26
|
status: "error",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import {graphQLDocumentNodeParser} from "./graphql-document-node-parser";
|
|
3
2
|
import type {GqlOperation} from "./gql-types";
|
|
4
3
|
import type {DocumentNode} from "./graphql-types";
|
|
@@ -32,7 +31,7 @@ import type {DocumentNode} from "./graphql-types";
|
|
|
32
31
|
* };
|
|
33
32
|
* ```
|
|
34
33
|
*/
|
|
35
|
-
export const toGqlOperation = <TData, TVariables
|
|
34
|
+
export const toGqlOperation = <TData, TVariables extends Record<any, any>>(
|
|
36
35
|
documentNode: DocumentNode,
|
|
37
36
|
): GqlOperation<TData, TVariables> => {
|
|
38
37
|
const definition = graphQLDocumentNodeParser(documentNode);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
1
|
import type {Metadata} from "@khanacademy/wonder-stuff-core";
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -10,24 +9,24 @@ export const FetchPolicy = {
|
|
|
10
9
|
* If the data is in the cache, return that; otherwise, fetch from the
|
|
11
10
|
* server.
|
|
12
11
|
*/
|
|
13
|
-
CacheBeforeNetwork:
|
|
12
|
+
CacheBeforeNetwork: "CacheBeforeNetwork" as const,
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* If the data is in the cache, return that; always fetch from the server
|
|
17
16
|
* regardless of cache.
|
|
18
17
|
*/
|
|
19
|
-
CacheAndNetwork:
|
|
18
|
+
CacheAndNetwork: "CacheAndNetwork" as const,
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
21
|
* If the data is in the cache, return that; otherwise, do nothing.
|
|
23
22
|
*/
|
|
24
|
-
CacheOnly:
|
|
23
|
+
CacheOnly: "CacheOnly" as const,
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
26
|
* Ignore any existing cached result; always fetch from the server.
|
|
28
27
|
*/
|
|
29
|
-
NetworkOnly:
|
|
30
|
-
};
|
|
28
|
+
NetworkOnly: "NetworkOnly" as const,
|
|
29
|
+
} as const;
|
|
31
30
|
|
|
32
31
|
/**
|
|
33
32
|
* Define what can be cached.
|
|
@@ -39,47 +38,46 @@ export type ValidCacheData =
|
|
|
39
38
|
| string
|
|
40
39
|
| boolean
|
|
41
40
|
| number
|
|
42
|
-
|
|
|
43
|
-
| Array
|
|
41
|
+
| Record<any, any>
|
|
42
|
+
| Array<ValidCacheData | null | undefined>;
|
|
44
43
|
|
|
45
44
|
/**
|
|
46
45
|
* The normalized result of a request.
|
|
47
46
|
*/
|
|
48
|
-
export type Result<TData
|
|
49
|
-
| {
|
|
50
|
-
status: "loading"
|
|
51
|
-
|
|
52
|
-
| {
|
|
53
|
-
status: "success"
|
|
54
|
-
data: TData
|
|
55
|
-
|
|
56
|
-
| {
|
|
57
|
-
status: "error"
|
|
58
|
-
error: Error
|
|
59
|
-
|
|
60
|
-
| {
|
|
61
|
-
status: "aborted"
|
|
62
|
-
|
|
47
|
+
export type Result<TData extends ValidCacheData> =
|
|
48
|
+
| {
|
|
49
|
+
status: "loading";
|
|
50
|
+
}
|
|
51
|
+
| {
|
|
52
|
+
status: "success";
|
|
53
|
+
data: TData;
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
status: "error";
|
|
57
|
+
error: Error;
|
|
58
|
+
}
|
|
59
|
+
| {
|
|
60
|
+
status: "aborted";
|
|
61
|
+
};
|
|
63
62
|
|
|
64
63
|
/**
|
|
65
64
|
* A cache entry for a fulfilled request response.
|
|
66
65
|
*/
|
|
67
|
-
export type CachedResponse<TData
|
|
68
|
-
| {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
| {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
export type CachedResponse<TData extends ValidCacheData> =
|
|
67
|
+
| {
|
|
68
|
+
readonly error: string;
|
|
69
|
+
readonly data?: undefined;
|
|
70
|
+
}
|
|
71
|
+
| {
|
|
72
|
+
readonly data: TData;
|
|
73
|
+
readonly error?: undefined;
|
|
74
|
+
};
|
|
76
75
|
|
|
77
76
|
/**
|
|
78
77
|
* A cache of fulfilled request responses.
|
|
79
78
|
*/
|
|
80
79
|
export type ResponseCache = {
|
|
81
|
-
[key: string]: CachedResponse<any
|
|
82
|
-
...
|
|
80
|
+
[key: string]: CachedResponse<any>;
|
|
83
81
|
};
|
|
84
82
|
|
|
85
83
|
/**
|
|
@@ -93,40 +91,34 @@ export type RawScopedCache = {
|
|
|
93
91
|
/**
|
|
94
92
|
* Each value in the cache is then identified within a given scope.
|
|
95
93
|
*/
|
|
96
|
-
[id: string]: ValidCacheData
|
|
97
|
-
|
|
98
|
-
},
|
|
99
|
-
...
|
|
94
|
+
[id: string]: ValidCacheData;
|
|
95
|
+
};
|
|
100
96
|
};
|
|
101
97
|
|
|
102
98
|
/**
|
|
103
99
|
* Options to pass to error construction.
|
|
104
100
|
*/
|
|
105
|
-
export type ErrorOptions = {
|
|
101
|
+
export type ErrorOptions = {
|
|
106
102
|
/**
|
|
107
103
|
* Metadata to attach to the error.
|
|
108
104
|
*/
|
|
109
|
-
metadata?:
|
|
110
|
-
|
|
105
|
+
metadata?: Metadata | null | undefined;
|
|
111
106
|
/**
|
|
112
107
|
* The error that caused the error being constructed.
|
|
113
108
|
*/
|
|
114
|
-
cause?:
|
|
115
|
-
|
|
109
|
+
cause?: Error | null | undefined;
|
|
110
|
+
};
|
|
116
111
|
|
|
117
112
|
export interface ScopedCache {
|
|
118
113
|
set(scope: string, id: string, value: ValidCacheData): void;
|
|
119
|
-
|
|
120
114
|
/**
|
|
121
115
|
* Retrieve a value from the cache.
|
|
122
116
|
*/
|
|
123
|
-
get(scope: string, id: string):
|
|
124
|
-
|
|
117
|
+
get(scope: string, id: string): ValidCacheData | null | undefined;
|
|
125
118
|
/**
|
|
126
119
|
* Purge an item from the cache.
|
|
127
120
|
*/
|
|
128
121
|
purge(scope: string, id: string): void;
|
|
129
|
-
|
|
130
122
|
/**
|
|
131
123
|
* Purge a scope of items that match the given predicate.
|
|
132
124
|
*
|
|
@@ -136,7 +128,6 @@ export interface ScopedCache {
|
|
|
136
128
|
scope: string,
|
|
137
129
|
predicate?: (id: string, value: ValidCacheData) => boolean,
|
|
138
130
|
): void;
|
|
139
|
-
|
|
140
131
|
/**
|
|
141
132
|
* Purge all items from the cache that match the given predicate.
|
|
142
133
|
*
|