@khanacademy/wonder-blocks-data 4.0.0 → 6.0.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 +31 -0
- package/dist/es/index.js +793 -375
- package/dist/index.js +1203 -523
- package/legacy-docs.md +3 -0
- package/package.json +2 -2
- package/src/__docs__/_overview_.stories.mdx +18 -0
- package/src/__docs__/_overview_graphql.stories.mdx +35 -0
- package/src/__docs__/_overview_ssr_.stories.mdx +185 -0
- package/src/__docs__/_overview_testing_.stories.mdx +123 -0
- package/src/__docs__/exports.clear-shared-cache.stories.mdx +20 -0
- package/src/__docs__/exports.data-error.stories.mdx +23 -0
- package/src/__docs__/exports.data-errors.stories.mdx +23 -0
- package/src/{components/data.md → __docs__/exports.data.stories.mdx} +15 -18
- package/src/__docs__/exports.fulfill-all-data-requests.stories.mdx +24 -0
- package/src/__docs__/exports.gql-error.stories.mdx +23 -0
- package/src/__docs__/exports.gql-errors.stories.mdx +20 -0
- package/src/__docs__/exports.gql-router.stories.mdx +29 -0
- package/src/__docs__/exports.has-unfulfilled-requests.stories.mdx +20 -0
- package/src/__docs__/exports.intercept-requests.stories.mdx +69 -0
- package/src/__docs__/exports.intialize-cache.stories.mdx +29 -0
- package/src/__docs__/exports.remove-all-from-cache.stories.mdx +24 -0
- package/src/__docs__/exports.remove-from-cache.stories.mdx +25 -0
- package/src/__docs__/exports.request-fulfillment.stories.mdx +36 -0
- package/src/__docs__/exports.scoped-in-memory-cache.stories.mdx +92 -0
- package/src/__docs__/exports.serializable-in-memory-cache.stories.mdx +112 -0
- package/src/__docs__/exports.status.stories.mdx +31 -0
- package/src/{components/track-data.md → __docs__/exports.track-data.stories.mdx} +15 -0
- package/src/__docs__/exports.use-cached-effect.stories.mdx +41 -0
- package/src/__docs__/exports.use-gql.stories.mdx +73 -0
- package/src/__docs__/exports.use-hydratable-effect.stories.mdx +43 -0
- package/src/__docs__/exports.use-server-effect.stories.mdx +38 -0
- package/src/__docs__/exports.use-shared-cache.stories.mdx +30 -0
- package/src/__docs__/exports.when-client-side.stories.mdx +33 -0
- package/src/__docs__/types.cached-response.stories.mdx +29 -0
- package/src/__docs__/types.error-options.stories.mdx +21 -0
- package/src/__docs__/types.gql-context.stories.mdx +20 -0
- package/src/__docs__/types.gql-fetch-fn.stories.mdx +24 -0
- package/src/__docs__/types.gql-fetch-options.stories.mdx +24 -0
- package/src/__docs__/types.gql-operation-type.stories.mdx +24 -0
- package/src/__docs__/types.gql-operation.stories.mdx +67 -0
- package/src/__docs__/types.response-cache.stories.mdx +33 -0
- package/src/__docs__/types.result.stories.mdx +39 -0
- package/src/__docs__/types.scoped-cache.stories.mdx +27 -0
- package/src/__docs__/types.valid-cache-data.stories.mdx +23 -0
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +0 -80
- package/src/__tests__/generated-snapshot.test.js +7 -31
- package/src/components/__tests__/data.test.js +160 -154
- package/src/components/__tests__/intercept-requests.test.js +58 -0
- package/src/components/data.js +22 -126
- package/src/components/intercept-context.js +4 -5
- package/src/components/intercept-requests.js +69 -0
- package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +8 -8
- package/src/hooks/__tests__/use-cached-effect.test.js +507 -0
- package/src/hooks/__tests__/use-gql-router-context.test.js +133 -0
- package/src/hooks/__tests__/use-gql.test.js +1 -30
- package/src/hooks/__tests__/use-hydratable-effect.test.js +708 -0
- package/src/hooks/__tests__/use-request-interception.test.js +255 -0
- package/src/hooks/__tests__/use-server-effect.test.js +39 -11
- package/src/hooks/use-cached-effect.js +225 -0
- package/src/hooks/use-gql-router-context.js +50 -0
- package/src/hooks/use-gql.js +22 -52
- package/src/hooks/use-hydratable-effect.js +206 -0
- package/src/hooks/use-request-interception.js +51 -0
- package/src/hooks/use-server-effect.js +14 -7
- package/src/hooks/use-shared-cache.js +13 -11
- package/src/index.js +54 -2
- package/src/util/__tests__/__snapshots__/serializable-in-memory-cache.test.js.snap +19 -0
- package/src/util/__tests__/merge-gql-context.test.js +74 -0
- package/src/util/__tests__/request-fulfillment.test.js +23 -42
- package/src/util/__tests__/request-tracking.test.js +26 -7
- package/src/util/__tests__/result-from-cache-response.test.js +19 -5
- package/src/util/__tests__/scoped-in-memory-cache.test.js +6 -85
- package/src/util/__tests__/serializable-in-memory-cache.test.js +398 -0
- package/src/util/__tests__/ssr-cache.test.js +52 -52
- package/src/util/abort-error.js +15 -0
- package/src/util/data-error.js +58 -0
- package/src/util/get-gql-data-from-response.js +3 -2
- package/src/util/gql-error.js +19 -11
- package/src/util/merge-gql-context.js +34 -0
- package/src/util/request-fulfillment.js +49 -46
- package/src/util/request-tracking.js +69 -15
- package/src/util/result-from-cache-response.js +12 -16
- package/src/util/scoped-in-memory-cache.js +24 -47
- package/src/util/serializable-in-memory-cache.js +49 -0
- package/src/util/ssr-cache.js +9 -8
- package/src/util/status.js +30 -0
- package/src/util/types.js +18 -1
- package/docs.md +0 -122
- package/src/components/__tests__/intercept-data.test.js +0 -63
- package/src/components/intercept-data.js +0 -66
- package/src/components/intercept-data.md +0 -51
package/docs.md
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
## fulfillAllDataRequests
|
|
2
|
-
|
|
3
|
-
When performing server-side rendering (SSR), the data requests that are being
|
|
4
|
-
made via the `Data` component can be tracked by rendering the React tree
|
|
5
|
-
inside the `TrackData` component. After this has occurred, the tracked requests
|
|
6
|
-
can be fulfilled using `fulfillAllDataRequests`.
|
|
7
|
-
|
|
8
|
-
This method returns a promise that resolves to a copy of the data that was
|
|
9
|
-
cached by fulfilling the tracked requests. In the process, it clears the
|
|
10
|
-
record of tracked requests so that new requests can be tracked and fulfilled
|
|
11
|
-
if so required.
|
|
12
|
-
|
|
13
|
-
The returned copy of the data cache can be used with the `initializeCache`
|
|
14
|
-
method to prepare the data cache before a subsequent render. This is useful on
|
|
15
|
-
the server to then SSR a more complete result, and again on the client, to
|
|
16
|
-
rehydrate that result.
|
|
17
|
-
|
|
18
|
-
### Usage
|
|
19
|
-
|
|
20
|
-
```js static
|
|
21
|
-
fulfillAllDataRequests(): Promise<ResponseCache>;
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## initializeCache
|
|
25
|
-
|
|
26
|
-
Wonder Blocks Data caches data in its response cache for hydration. This cache
|
|
27
|
-
can be initialized with data using the `initializeCache` method.
|
|
28
|
-
The `initializeCache` method can only be called when the hydration cache is
|
|
29
|
-
empty.
|
|
30
|
-
|
|
31
|
-
Usually, the data to be passed to `initializeCache` will be obtained by
|
|
32
|
-
calling `fulfillAllDataRequests` after tracking data requests
|
|
33
|
-
(see [TrackData](#trackdata)) during server-side rendering.
|
|
34
|
-
|
|
35
|
-
Combine with `removeFromCache` or `removeAllFromCache` to support your testing
|
|
36
|
-
needs.
|
|
37
|
-
|
|
38
|
-
### Usage
|
|
39
|
-
|
|
40
|
-
```js static
|
|
41
|
-
initializeCache(sourceCache: ResponseCache): void;
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
#### Function arguments
|
|
45
|
-
|
|
46
|
-
| Argument | Flow Type | Default | Description |
|
|
47
|
-
| --- | --- | --- | --- |
|
|
48
|
-
| `sourceData` | `ResponseCache` | _Required_ | The source cache that will be used to initialize the response cache. |
|
|
49
|
-
|
|
50
|
-
## removeFromCache
|
|
51
|
-
|
|
52
|
-
Removes an entry from the cache. The given handler and options identify the entry to be removed.
|
|
53
|
-
|
|
54
|
-
If an item is removed, this returns `true`; otherwise, `false`.
|
|
55
|
-
|
|
56
|
-
This can be used after `initializeCache` to manipulate the cache prior to hydration.
|
|
57
|
-
This can be useful during testing.
|
|
58
|
-
|
|
59
|
-
### Usage
|
|
60
|
-
|
|
61
|
-
```js static
|
|
62
|
-
removeFromCache(id: string): boolean;
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
#### Function arguments
|
|
66
|
-
|
|
67
|
-
| Argument | Flow Type | Default | Description |
|
|
68
|
-
| --- | --- | --- | --- |
|
|
69
|
-
| `id` | `string` | _Required_ | The id of the item to be removed. |
|
|
70
|
-
|
|
71
|
-
## removeAllFromCache
|
|
72
|
-
|
|
73
|
-
Removes all entries that match a given predicate from the cache. If no predicate is given, all cached entries for the given handler are removed.
|
|
74
|
-
|
|
75
|
-
This returns the count of entries removed.
|
|
76
|
-
|
|
77
|
-
This can be used after `initializeCache` to manipulate the cache prior to hydration.
|
|
78
|
-
This can be useful during testing (especially to clear the cache so that it can be initialized again).
|
|
79
|
-
If the predicate is not given, all items are removed.
|
|
80
|
-
|
|
81
|
-
### Usage
|
|
82
|
-
|
|
83
|
-
```js static
|
|
84
|
-
removeAllFromCache(predicate?: (key: string, entry: $ReadOnly<CachedResponse<TData>>) => boolean): number;
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
#### Function arguments
|
|
88
|
-
|
|
89
|
-
| Argument | Flow Type | Default | Description |
|
|
90
|
-
| --- | --- | --- | --- |
|
|
91
|
-
| `predicate` | `(key: string, entry: $ReadOnly<CachedResponse<TData>>) => boolean)` | _Optional_ | A predicate to identify which entries to remove. If absent, all data is removed; if present, any entries for which the predicate returns `true` will be returned. |
|
|
92
|
-
|
|
93
|
-
## Types
|
|
94
|
-
|
|
95
|
-
### ResponseCache
|
|
96
|
-
|
|
97
|
-
```js static
|
|
98
|
-
type CachedResponse =
|
|
99
|
-
| {|
|
|
100
|
-
data: any,
|
|
101
|
-
|}
|
|
102
|
-
| {|
|
|
103
|
-
error: string,
|
|
104
|
-
|};
|
|
105
|
-
|
|
106
|
-
type ResponseCache = {
|
|
107
|
-
[id: string]: CachedResponse,
|
|
108
|
-
...,
|
|
109
|
-
};
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
An example is of the response cache is shown below.
|
|
113
|
-
|
|
114
|
-
```js static
|
|
115
|
-
const responseCache = {
|
|
116
|
-
DATA_ID_1: {error: "It go 💥boom 😢"},
|
|
117
|
-
DATA_ID_2: {data: ["array", "of", "data"]},
|
|
118
|
-
DATA_ID_3: {data: {some: "data"}},
|
|
119
|
-
};
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
In this example, the cache contains data retrieved for three different requests.
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import {render} from "@testing-library/react";
|
|
4
|
-
|
|
5
|
-
import InterceptContext from "../intercept-context.js";
|
|
6
|
-
import InterceptData from "../intercept-data.js";
|
|
7
|
-
|
|
8
|
-
describe("InterceptData", () => {
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
jest.resetAllMocks();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("should update context with fulfillRequest method", () => {
|
|
14
|
-
// Arrange
|
|
15
|
-
const fakeHandler = () => Promise.resolve("data");
|
|
16
|
-
const props = {
|
|
17
|
-
handler: fakeHandler,
|
|
18
|
-
requestId: "ID",
|
|
19
|
-
};
|
|
20
|
-
const captureContextFn = jest.fn();
|
|
21
|
-
|
|
22
|
-
// Act
|
|
23
|
-
render(
|
|
24
|
-
<InterceptData {...props}>
|
|
25
|
-
<InterceptContext.Consumer>
|
|
26
|
-
{captureContextFn}
|
|
27
|
-
</InterceptContext.Consumer>
|
|
28
|
-
</InterceptData>,
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
// Assert
|
|
32
|
-
expect(captureContextFn).toHaveBeenCalledWith(
|
|
33
|
-
expect.objectContaining({
|
|
34
|
-
ID: props.handler,
|
|
35
|
-
}),
|
|
36
|
-
);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("should override parent InterceptData", () => {
|
|
40
|
-
// Arrange
|
|
41
|
-
const fulfillRequest1Fn = jest.fn();
|
|
42
|
-
const fulfillRequest2Fn = jest.fn();
|
|
43
|
-
const captureContextFn = jest.fn();
|
|
44
|
-
|
|
45
|
-
// Act
|
|
46
|
-
render(
|
|
47
|
-
<InterceptData handler={fulfillRequest1Fn} requestId="ID">
|
|
48
|
-
<InterceptData handler={fulfillRequest2Fn} requestId="ID">
|
|
49
|
-
<InterceptContext.Consumer>
|
|
50
|
-
{captureContextFn}
|
|
51
|
-
</InterceptContext.Consumer>
|
|
52
|
-
</InterceptData>
|
|
53
|
-
</InterceptData>,
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
// Assert
|
|
57
|
-
expect(captureContextFn).toHaveBeenCalledWith(
|
|
58
|
-
expect.objectContaining({
|
|
59
|
-
ID: fulfillRequest2Fn,
|
|
60
|
-
}),
|
|
61
|
-
);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
|
|
4
|
-
import InterceptContext from "./intercept-context.js";
|
|
5
|
-
|
|
6
|
-
import type {ValidCacheData} from "../util/types.js";
|
|
7
|
-
|
|
8
|
-
type Props<TData: ValidCacheData> = {|
|
|
9
|
-
/**
|
|
10
|
-
* The ID of the request to intercept.
|
|
11
|
-
*/
|
|
12
|
-
requestId: string,
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Called to intercept and fulfill the request.
|
|
16
|
-
* If this returns null, the request will be fulfilled by the
|
|
17
|
-
* handler of the original request being intercepted.
|
|
18
|
-
*/
|
|
19
|
-
handler: () => ?Promise<?TData>,
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* The children to render within this component. Any requests by `Data`
|
|
23
|
-
* components that use same ID as this component will be intercepted.
|
|
24
|
-
* (unless another `InterceptData` component overrides this one).
|
|
25
|
-
*/
|
|
26
|
-
children: React.Node,
|
|
27
|
-
|};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* This component provides a mechanism to intercept data requests.
|
|
31
|
-
* This is for use in testing.
|
|
32
|
-
*
|
|
33
|
-
* This component is not recommended for use in production code as it
|
|
34
|
-
* can prevent predictable functioning of the Wonder Blocks Data framework.
|
|
35
|
-
* One possible side-effect is that inflight requests from the interceptor could
|
|
36
|
-
* be picked up by `Data` component requests from outside the children of this
|
|
37
|
-
* component.
|
|
38
|
-
*
|
|
39
|
-
* These components do not chain. If a different `InterceptData` instance is
|
|
40
|
-
* rendered within this one that intercepts the same id, then that
|
|
41
|
-
* new instance will replace this interceptor for its children. All methods
|
|
42
|
-
* will be replaced.
|
|
43
|
-
*/
|
|
44
|
-
const InterceptData = <TData: ValidCacheData>({
|
|
45
|
-
requestId,
|
|
46
|
-
handler,
|
|
47
|
-
children,
|
|
48
|
-
}: Props<TData>): React.Node => {
|
|
49
|
-
const interceptMap = React.useContext(InterceptContext);
|
|
50
|
-
|
|
51
|
-
const updatedInterceptMap = React.useMemo(
|
|
52
|
-
() => ({
|
|
53
|
-
...interceptMap,
|
|
54
|
-
[requestId]: handler,
|
|
55
|
-
}),
|
|
56
|
-
[interceptMap, requestId, handler],
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<InterceptContext.Provider value={updatedInterceptMap}>
|
|
61
|
-
{children}
|
|
62
|
-
</InterceptContext.Provider>
|
|
63
|
-
);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export default InterceptData;
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
When you want to generate tests that check the loading state and
|
|
2
|
-
subsequent loaded state are working correctly for your uses of `Data` you can
|
|
3
|
-
use the `InterceptData` component.
|
|
4
|
-
|
|
5
|
-
This component takes three props; children to be rendered, the handler of the
|
|
6
|
-
to fulfill the request, and the id of the request that is being intercepted.
|
|
7
|
-
|
|
8
|
-
Note that this component is expected to be used only within test cases and
|
|
9
|
-
usually only as a single instance. In flight requests for a given handler
|
|
10
|
-
type can be shared and as such, using `InterceptData` alongside non-intercepted
|
|
11
|
-
`Data` components with the same id can have indeterminate outcomes.
|
|
12
|
-
|
|
13
|
-
The `handler` intercept function has the form:
|
|
14
|
-
|
|
15
|
-
```js static
|
|
16
|
-
() => ?Promise<?TData>;
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
If this method returns `null`, the default behavior occurs. This
|
|
20
|
-
means that a request will be made for data via the handler assigned to the
|
|
21
|
-
`Data` component being intercepted.
|
|
22
|
-
|
|
23
|
-
```jsx
|
|
24
|
-
import {Body, BodyMonospace} from "@khanacademy/wonder-blocks-typography";
|
|
25
|
-
import {View} from "@khanacademy/wonder-blocks-core";
|
|
26
|
-
import {InterceptData, Data} from "@khanacademy/wonder-blocks-data";
|
|
27
|
-
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
28
|
-
import Color from "@khanacademy/wonder-blocks-color";
|
|
29
|
-
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
30
|
-
|
|
31
|
-
const myHandler = () => Promise.reject(new Error("You should not see this!"));
|
|
32
|
-
|
|
33
|
-
const interceptHandler = () => Promise.resolve("INTERCEPTED DATA!");
|
|
34
|
-
|
|
35
|
-
<InterceptData handler={interceptHandler} requestId="INTERCEPT_EXAMPLE">
|
|
36
|
-
<View>
|
|
37
|
-
<Body>This received intercepted data!</Body>
|
|
38
|
-
<Data handler={myHandler} requestId="INTERCEPT_EXAMPLE">
|
|
39
|
-
{(result) => {
|
|
40
|
-
if (result.status !== "success") {
|
|
41
|
-
return "If you see this, the example is broken!";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<BodyMonospace>{result.data}</BodyMonospace>
|
|
46
|
-
);
|
|
47
|
-
}}
|
|
48
|
-
</Data>
|
|
49
|
-
</View>
|
|
50
|
-
</InterceptData>
|
|
51
|
-
```
|