@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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
import {InterceptRequests} from "../index.js";
|
|
3
|
+
|
|
4
|
+
<Meta
|
|
5
|
+
title="Data / Exports / InterceptRequests"
|
|
6
|
+
component={InterceptRequests}
|
|
7
|
+
parameters={{
|
|
8
|
+
chromatic: {
|
|
9
|
+
disableSnapshot: true,
|
|
10
|
+
},
|
|
11
|
+
}}
|
|
12
|
+
/>
|
|
13
|
+
|
|
14
|
+
# InterceptRequests
|
|
15
|
+
|
|
16
|
+
When you want to generate tests that check the loading state and
|
|
17
|
+
subsequent loaded state are working correctly for your uses of `Data` you can
|
|
18
|
+
use the `InterceptRequests` component. You can also use this component to
|
|
19
|
+
register request interceptors for any code that uses the `useRequestInterception`
|
|
20
|
+
hook.
|
|
21
|
+
|
|
22
|
+
This component takes the children to be rendered, and an interceptor function.
|
|
23
|
+
|
|
24
|
+
Note that this component is expected to be used only within test cases or
|
|
25
|
+
stories. Be careful want request IDs are matched to avoid intercepting the
|
|
26
|
+
wrong requests and remember that in-flight requests for a given request ID
|
|
27
|
+
can be shared - which means a bad request ID match could share requests across
|
|
28
|
+
different request IDs..
|
|
29
|
+
|
|
30
|
+
The `interceptor` intercept function has the form:
|
|
31
|
+
|
|
32
|
+
```js static
|
|
33
|
+
(requestId: string) => ?Promise<TData>;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If this method returns `null`, then the next interceptor in the chain is
|
|
37
|
+
invoked, ultimately ending with the original handler. This
|
|
38
|
+
means that a request will be made for data via the handler assigned to the
|
|
39
|
+
`Data` component being intercepted if no interceptor handles the request first.
|
|
40
|
+
|
|
41
|
+
```jsx
|
|
42
|
+
import {Body, BodyMonospace} from "@khanacademy/wonder-blocks-typography";
|
|
43
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
44
|
+
import {InterceptRequests, Data} from "@khanacademy/wonder-blocks-data";
|
|
45
|
+
import {Strut} from "@khanacademy/wonder-blocks-layout";
|
|
46
|
+
import Color from "@khanacademy/wonder-blocks-color";
|
|
47
|
+
import Spacing from "@khanacademy/wonder-blocks-spacing";
|
|
48
|
+
|
|
49
|
+
const myHandler = () => Promise.reject(new Error("You should not see this!"));
|
|
50
|
+
|
|
51
|
+
const interceptor = (requestId) => requestId === "INTERCEPT_EXAMPLE" ? Promise.resolve("INTERCEPTED DATA!") : null;
|
|
52
|
+
|
|
53
|
+
<InterceptRequests interceptor={interceptor}>
|
|
54
|
+
<View>
|
|
55
|
+
<Body>This received intercepted data!</Body>
|
|
56
|
+
<Data handler={myHandler} requestId="INTERCEPT_EXAMPLE">
|
|
57
|
+
{(result) => {
|
|
58
|
+
if (result.status !== "success") {
|
|
59
|
+
return "If you see this, the example is broken!";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<BodyMonospace>{result.data}</BodyMonospace>
|
|
64
|
+
);
|
|
65
|
+
}}
|
|
66
|
+
</Data>
|
|
67
|
+
</View>
|
|
68
|
+
</InterceptRequests>
|
|
69
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / initializeCache()"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# initializeCache()
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
initializeCache(sourceCache: ResponseCache): void;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
| Argument | Flow Type | Default | Description |
|
|
19
|
+
| --- | --- | --- | --- |
|
|
20
|
+
| `sourceData` | `ResponseCache` | _Required_ | The source cache that will be used to initialize the response cache. |
|
|
21
|
+
|
|
22
|
+
Wonder Blocks Data caches data in its response cache for hydration. This cache can be initialized with data using the `initializeCache` method.
|
|
23
|
+
The `initializeCache` method can only be called when the hydration cache is empty.
|
|
24
|
+
|
|
25
|
+
Usually, the data to be passed to `initializeCache` will be obtained by calling [`fulfillAllDataRequests`](/docs/data-exports-fulfillalldatarequests--page) after tracking data requests (see [`TrackData`](/docs/data-exports-trackdata--page)) during server-side rendering.
|
|
26
|
+
|
|
27
|
+
Combine with [`removeFromCache`](/docs/data-exports-removefromcache--page) or [`removeAllFromCache`](/docs/data-exports-removeallfromcache--page) to support your testing needs.
|
|
28
|
+
|
|
29
|
+
More details about server-side rendering with Wonder Blocks Data can be found in the [relevant overview section](/docs/data-server-side-rendering-and-hydration--page).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / removeAllFromCache()"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# removeAllFromCache()
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
removeAllFromCache(predicate?: (key: string, cacheEntry: $ReadOnly<CachedResponse<ValidCacheData>>) => boolean): void;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
| Argument | Flow Type | Default | Description |
|
|
19
|
+
| --- | --- | --- | --- |
|
|
20
|
+
| `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. |
|
|
21
|
+
|
|
22
|
+
Removes all entries that match a given predicate from the cache. If no predicate is given, all cached entries.
|
|
23
|
+
|
|
24
|
+
This can be used after [`initializeCache`](/docs/data-exports-initializecache--page) to manipulate the cache prior to hydration wich can be useful during testing (especially to clear the cache so that it can be initialized again).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / removeFromCache()"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# removeFromCache()
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
removeFromCache(id: string): void;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
| Argument | Flow Type | Default | Description |
|
|
19
|
+
| --- | --- | --- | --- |
|
|
20
|
+
| `id` | `string` | _Required_ | The id of the item to be removed. |
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
Removes an entry from the cache.
|
|
24
|
+
|
|
25
|
+
This can be used after [`initializeCache`](/docs/data-exports-initializecache--page) to manipulate the cache prior to hydration. This can be useful during testing.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / RequestFulfillment"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# RequestFulfillment
|
|
13
|
+
|
|
14
|
+
The `RequestFulfillment` class encapsulates tracking of inflight asynchronous actions keyed by an identifier. Using this API, callers can request the result of the same asynchronous action multiple times without invoking the underlying action more than is necessary. Instead, any pending request will be shared by all requests for the same identifier.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
fulfill: <TData: ValidCacheData>(
|
|
20
|
+
id: string,
|
|
21
|
+
options: {|
|
|
22
|
+
handler: () => Promise<TData>,
|
|
23
|
+
hydrate?: boolean,
|
|
24
|
+
|},
|
|
25
|
+
) => Promise<Result<TData>>;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
There is a single function on the `RequestFulfillment` class, called `fulfill`.
|
|
29
|
+
|
|
30
|
+
The `fulfill` method takes the request identifier (used to deduplicate requests) and an options object. The options object contains the following properties:
|
|
31
|
+
|
|
32
|
+
* `handler`: A function that returns a promise resolving to the result of the request. This is the asynchronous work that will be tracked by the given identifier.
|
|
33
|
+
* `hydrate`: A boolean indicating whether the data should be hydrated. This is used during server-side rendering to determine if the response data should be included in the hydration cache. This defaults to `true` and should only be set to `false` if you are performing server-side rendering of the request and you know that the data will not be needed for hydration to succeed.
|
|
34
|
+
|
|
35
|
+
## RequestFulfillment.Default
|
|
36
|
+
The `RequestFulfillment` class provides a static instance, `RequestFulfillment.Default`, which is used by the Wonder Blocks Data framework. However, a custom instance can be constructed should your specific use case need to be isolated from others.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / ScopedInMemoryCache"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# ScopedInMemoryCache
|
|
13
|
+
|
|
14
|
+
This class implements an in-memory cache that can contain different scopes of cached data. This allows for quick removal of entire classes of data as identified by their scopes without having to iterate each cached item to find them.
|
|
15
|
+
|
|
16
|
+
## constructor()
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
new ScopedInMemoryCache(initialCache?: ScopedCache)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Creates a new instance. An initial state for the cache can be provided.
|
|
23
|
+
|
|
24
|
+
## inUse
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
if (cache.inUse) {
|
|
28
|
+
// Cache is in use
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Is `true` if the cache contains any data; otherwise, `false`.
|
|
33
|
+
|
|
34
|
+
## set()
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
set<TValue: ValidCacheData>(
|
|
38
|
+
scope: string,
|
|
39
|
+
id: string,
|
|
40
|
+
value: TValue,
|
|
41
|
+
): void;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Sets a value in the cache within a given scope.
|
|
45
|
+
|
|
46
|
+
### Throws
|
|
47
|
+
|
|
48
|
+
| Error Type | Error Name | Reason |
|
|
49
|
+
| ------ | ------ | ------ |
|
|
50
|
+
| [`DataError`](/docs/data-exports-dataerror--page) | `InvalidInputDataError` | `id` and `scope` must be non-empty strings |
|
|
51
|
+
| [`DataError`](/docs/data-exports-dataerror--page) | `InvalidInputDataError` | `value` must be a non-function value |
|
|
52
|
+
|
|
53
|
+
## get()
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
get(scope: string, id: string): ?ValidCacheData;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Gets a value from the cache. If a value with the given identifier (`id`) is not found within the given scope (`scope`) of the cache, `null` is returned.
|
|
60
|
+
|
|
61
|
+
## purge()
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
purge(scope: string, id: string): void;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Purges the value from the cache. If a value with the given identifier (`id`) is not found within the given scope (`scope`) of the cache, nothing happens.
|
|
68
|
+
|
|
69
|
+
## purgeScope()
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
purgeScope(
|
|
73
|
+
scope: string,
|
|
74
|
+
predicate?: (id: string, value: ValidCacheData) => boolean,
|
|
75
|
+
): void;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Purges items within a given scope (`scope`) of the cache from that scope. If a predicate is provided, only items for which the predicate returns `true` will be purged; otherwise, the entire scope will be purged.
|
|
79
|
+
|
|
80
|
+
## purgeAll()
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
purgeAll(
|
|
84
|
+
predicate?: (
|
|
85
|
+
scope: string,
|
|
86
|
+
id: string,
|
|
87
|
+
value: ValidCacheData,
|
|
88
|
+
) => boolean,
|
|
89
|
+
): void;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Purges all items from the cache. If a predicate is provided, only items for which the predicate returns `true` will be purged; otherwise, the entire cache will be purged.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / SerializableInMemoryCache"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# SerializableInMemoryCache
|
|
13
|
+
|
|
14
|
+
This class is a specialization of [`ScopedInMemoryCache`](/docs/data-exports-scopedinmemorycache--page). This specialization requires that values added can be serialized to and from strings.
|
|
15
|
+
|
|
16
|
+
## constructor()
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
new SerializableInMemoryCache(initialCache?: ScopedCache)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Creates a new instance. The `initialCache`, if provided, will be cloned and used as the initial state of the cache.
|
|
23
|
+
|
|
24
|
+
### Throws
|
|
25
|
+
|
|
26
|
+
| Error Type | Error Name | Reason |
|
|
27
|
+
| ------ | ------ | ------ |
|
|
28
|
+
| [`DataError`](/docs/data-exports-dataerror--page) | `InvalidInputDataError` | Could not clone the initial cache. |
|
|
29
|
+
|
|
30
|
+
## inUse
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
if (cache.inUse) {
|
|
34
|
+
// Cache is in use
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Is `true` if the cache contains any data; otherwise, `false`.
|
|
39
|
+
|
|
40
|
+
## set()
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
set<TValue: ValidCacheData>(
|
|
44
|
+
scope: string,
|
|
45
|
+
id: string,
|
|
46
|
+
value: TValue,
|
|
47
|
+
): void;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Sets a value in the cache within a given scope. The value is cloned and the clone is frozen before being added to the cache.
|
|
51
|
+
|
|
52
|
+
### Throws
|
|
53
|
+
|
|
54
|
+
| Error Type | Error Name | Reason |
|
|
55
|
+
| ------ | ------ | ------ |
|
|
56
|
+
| [`DataError`](/docs/data-exports-dataerror--page) | `InvalidInputDataError` | `id` and `scope` must be non-empty strings |
|
|
57
|
+
| [`DataError`](/docs/data-exports-dataerror--page) | `InvalidInputDataError` | `value` must be a non-function value |
|
|
58
|
+
|
|
59
|
+
## get()
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
get(scope: string, id: string): ?ValidCacheData;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Gets a value from the cache. If a value with the given identifier (`id`) is not found within the given scope (`scope`) of the cache, `null` is returned.
|
|
66
|
+
|
|
67
|
+
## clone()
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
clone(): ScopedCache;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Returns a clone of the current cache.
|
|
74
|
+
|
|
75
|
+
### Throws
|
|
76
|
+
|
|
77
|
+
| Error Type | Error Name | Reason |
|
|
78
|
+
| ------ | ------ | ------ |
|
|
79
|
+
| [`DataError`](/docs/data-exports-dataerror--page) | `InternalDataError` | Could not clone the cache. |
|
|
80
|
+
|
|
81
|
+
## purge()
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
purge(scope: string, id: string): void;
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Purges the value from the cache. If a value with the given identifier (`id`) is not found within the given scope (`scope`) of the cache, nothing happens.
|
|
88
|
+
|
|
89
|
+
## purgeScope()
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
purgeScope(
|
|
93
|
+
scope: string,
|
|
94
|
+
predicate?: (id: string, value: ValidCacheData) => boolean,
|
|
95
|
+
): void;
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Purges items within a given scope (`scope`) of the cache from that scope. If a predicate is provided, only items for which the predicate returns `true` will be purged; otherwise, the entire scope will be purged.
|
|
99
|
+
|
|
100
|
+
## purgeAll()
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
purgeAll(
|
|
104
|
+
predicate?: (
|
|
105
|
+
scope: string,
|
|
106
|
+
id: string,
|
|
107
|
+
value: ValidCacheData,
|
|
108
|
+
) => boolean,
|
|
109
|
+
): void;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Purges all items from the cache. If a predicate is provided, only items for which the predicate returns `true` will be purged; otherwise, the entire cache will be purged.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / Status"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# Status
|
|
13
|
+
|
|
14
|
+
Provides a helper API for creating [`Result<TData>`](/docs/data-types-result--page) instances with specific statuses.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## loading()
|
|
18
|
+
|
|
19
|
+
`Status.loading()` creates a result with the `"loading"` status.
|
|
20
|
+
|
|
21
|
+
## aborted()
|
|
22
|
+
|
|
23
|
+
`Status.aborted()` creates a result with the `"aborted"` status.
|
|
24
|
+
|
|
25
|
+
## success()
|
|
26
|
+
|
|
27
|
+
`Status.success()` creates a result with the `"success"` status and the given data.
|
|
28
|
+
|
|
29
|
+
## error()
|
|
30
|
+
|
|
31
|
+
`Status.error()` creates a result with the `"error"` status and the given error.
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
import {TrackData} from "../index.js";
|
|
3
|
+
|
|
4
|
+
<Meta
|
|
5
|
+
title="Data / Exports / TrackData"
|
|
6
|
+
component={TrackData}
|
|
7
|
+
parameters={{
|
|
8
|
+
chromatic: {
|
|
9
|
+
disableSnapshot: true,
|
|
10
|
+
},
|
|
11
|
+
}}
|
|
12
|
+
/>
|
|
13
|
+
|
|
14
|
+
# TrackData
|
|
15
|
+
|
|
1
16
|
The `TrackData` component is a server-side only component. It should be used as
|
|
2
17
|
a parent to the components whose data requests you want to fulfill during
|
|
3
18
|
server-side rendering.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / useCachedEffect()"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# useCachedEffect()
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
function useCachedEffect<TData: ValidCacheData>(
|
|
16
|
+
requestId: string,
|
|
17
|
+
handler: () => Promise<TData>,
|
|
18
|
+
options?: CachedEffectOptions<TData>,
|
|
19
|
+
): Result<TData>;
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This hook is a wrapper around `useEffect`. Internally it uses the [`useSharedCache`](/docs/data-exports-usesharedcache--page) hook to store the result of the effect and, if that result is available, returns it without rerunning the effect. The precise behavior of the
|
|
23
|
+
hook can be modified using the options.
|
|
24
|
+
|
|
25
|
+
There is currently no way to reinvoke the effect without changing the `requestId`.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
type CachedEffectOptions<TData: ValidCacheData> = {|
|
|
29
|
+
skip?: boolean,
|
|
30
|
+
retainResultOnChange?: boolean,
|
|
31
|
+
onResultChanged?: (result: Result<TData>) => void,
|
|
32
|
+
scope?: string,
|
|
33
|
+
|};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Option | Default | Description |
|
|
37
|
+
| ------ | ------- | ----------- |
|
|
38
|
+
| `skip` | `false` | When `true`, the effect will not be executed; otherwise, the effect will be executed. If this is set to `true` while the effect is still pending, the pending effect will be cancelled. |
|
|
39
|
+
| `retainResultOnChange` | `false` | When `true`, the effect will not reset the result to the loading status while executing if the requestId changes, instead, returning the existing result from before the change; otherwise, the result will be set to loading status. If the status is loading when the changes are made, it will remain as loading; old pending effects are discarded on changes and as such this value has no effect in that case.|
|
|
40
|
+
| `onResultChanged` | `undefined` | Callback that is invoked if the result for the given hook has changed. When defined, the hook will invoke this callback whenever it has reason to change the result and will not otherwise affect component rendering directly. When not defined, the hook will ensure the component re-renders to pick up the latest result. |
|
|
41
|
+
| `scope` | `"useCachedEffect"` | Scope to use with the shared cache. When specified, the given scope will be used to isolate this hook's cached results. Otherwise, the default scope will be used. Changing this value after the first call is not supported. |
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / useGql()"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# useGql()
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
type FetchFn = <TData, TVariables: {...}>(
|
|
16
|
+
operation: GqlOperation<TData, TVariables>,
|
|
17
|
+
options?: GqlFetchOptions<TVariables, TContext>,
|
|
18
|
+
) => Promise<TData>;
|
|
19
|
+
|
|
20
|
+
function useGql<TContext: GqlContext>(
|
|
21
|
+
context: Partial<TContext> = ({}: $Shape<TContext>),
|
|
22
|
+
): FetchFn;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The `useGql` hook requires that the calling component has been rendered with a [`GqlRouter`](/docs/data-exports-gqlrouter--page) as an ancestor component since it relies on the default context and fetch operation that is specified therein.
|
|
26
|
+
|
|
27
|
+
The `useGql` hook can take a partial context value which will be combined with the default context to create the context used for a specific request.
|
|
28
|
+
|
|
29
|
+
The return value of `useGql` is a fetch function that can be used to invoke a GraphQL request. It takes as arguments the [`GqlOperation`](/docs/data-types-gqloperation--page) operation to be performed and some options (which, by their nature, are optional). These options can be used to provide variables for the operation as well as additional customization of the context.
|
|
30
|
+
|
|
31
|
+
The result of calling the function returned by `useGql` is a promise of the data that the request will return. This is compatible with the [`useServerEffect`](/docs/data-exports-useservereffect--page), [`useCachedEffect`](/docs/data-exports-usecachedeffect--page), and [`useHydratableEffect`](/docs/data-exports-usehydratableeffect--page) hooks, allowing a variety of scenarios to be easily constructed.
|
|
32
|
+
|
|
33
|
+
Below is an example request identifier generation function that could be used to generate request identifiers (we hope to include a base implementation for this purpose in a future release of Wonder Blocks Data):
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const getGqlRequestId = <TData, TVariables: {...}>(
|
|
37
|
+
operation: GqlOperation<TData, TVariables>,
|
|
38
|
+
variables: ?TVariables,
|
|
39
|
+
context: GqlContext,
|
|
40
|
+
): string => {
|
|
41
|
+
// We add all the bits for this into an array and then join them with
|
|
42
|
+
// a chosen separator.
|
|
43
|
+
const parts = [];
|
|
44
|
+
parts.push(operation.id);
|
|
45
|
+
if (context != null) {
|
|
46
|
+
// Turn the context into a string of the form,
|
|
47
|
+
// "key1=;key2=value"
|
|
48
|
+
const sortedContext = Object.keys(context || {})
|
|
49
|
+
.sort()
|
|
50
|
+
.map((key) => `${key}=${JSON.stringify(context?.[key]) || ""}`)
|
|
51
|
+
.join(";");
|
|
52
|
+
parts.push(sortedContext);
|
|
53
|
+
}
|
|
54
|
+
if (variables != null) {
|
|
55
|
+
// Turn the variables into a string of the form,
|
|
56
|
+
// "key1=;key2=value"
|
|
57
|
+
const sortedVariables = Object.keys(variables || {})
|
|
58
|
+
.sort()
|
|
59
|
+
.map((key) => `${key}=${JSON.stringify(variables?.[key]) || ""}`)
|
|
60
|
+
.join(";");
|
|
61
|
+
parts.push(sortedVariables);
|
|
62
|
+
}
|
|
63
|
+
return parts.join("|");
|
|
64
|
+
};
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Context Merging
|
|
68
|
+
|
|
69
|
+
Context overrides are combined such that any values that are explicitly or implicitly `undefined` on the partial context will be ignored. Any values that are explicitly `null` on the partial context will be removed from the merged context. The order of precedence is as follows:
|
|
70
|
+
|
|
71
|
+
1. Values from the fetch partial context, then,
|
|
72
|
+
2. Values from the `useGql` partial context, then,
|
|
73
|
+
3. Values from the default context.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / useHydratableEffect()"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# useHydratableEffect()
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
function useHydratableEffect<TData: ValidCacheData>(
|
|
16
|
+
requestId: string,
|
|
17
|
+
handler: () => Promise<TData>,
|
|
18
|
+
options?: HydratableEffectOptions<TData>,
|
|
19
|
+
): Result<TData>;
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This hook combines [`useServerEffect`](/docs/data-exports-useservereffect--page) and [`useCachedEffect`](/docs/data-exports-usecachedeffect--page) to form an effect that can execute on the server and hydrate on the client.
|
|
23
|
+
|
|
24
|
+
More details about server-side rendering with Wonder Blocks Data can be found in the [relevant overview section](/docs/data-server-side-rendering-and-hydration--page).
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
type HydratableEffectOptions<TData: ValidCacheData> = {|
|
|
29
|
+
clientBehavior?: WhenClientSide,
|
|
30
|
+
skip?: boolean,
|
|
31
|
+
retainResultOnChange?: boolean,
|
|
32
|
+
onResultChanged?: (result: Result<TData>) => void,
|
|
33
|
+
scope?: string,
|
|
34
|
+
|};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Option | Default | Description |
|
|
38
|
+
| ------ | ------- | ----------- |
|
|
39
|
+
| `clientBehavior` | [`WhenClientSide.ExecuteWhenNoSuccessResult`](/docs/data-exports-whenclientside--page#whenclientsideexecutewhennosuccessresult) | How the hook should behave when rendering client-side for the first time. This controls the hydration and execution of the effect on the client. Changing this value after the initial render is inert. For more information on other behaviors, see [`WhenClientSide`](/docs/data-exports-whenclientside--page). |
|
|
40
|
+
| `skip` | `false` | When `true`, the effect will not be executed; otherwise, the effect will be executed. If this is set to `true` while the effect is still pending, the pending effect will be cancelled. |
|
|
41
|
+
| `retainResultOnChange` | `false` | When `true`, the effect will not reset the result to the loading status while executing if the requestId changes, instead, returning the existing result from before the change; otherwise, the result will be set to loading status. If the status is loading when the changes are made, it will remain as loading; old pending effects are discarded on changes and as such this value has no effect in that case.|
|
|
42
|
+
| `onResultChanged` | `undefined` | Callback that is invoked if the result for the given hook has changed. When defined, the hook will invoke this callback whenever it has reason to change the result and will not otherwise affect component rendering directly. When not defined, the hook will ensure the component re-renders to pick up the latest result. |
|
|
43
|
+
| `scope` | `"useCachedEffect"` | Scope to use with the shared cache. When specified, the given scope will be used to isolate this hook's cached results. Otherwise, the default scope will be used. Changing this value after the first call is not supported. |
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / useServerEffect()"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# useServerEffect()
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
function useServerEffect<TData: ValidCacheData>(
|
|
16
|
+
requestId: string,
|
|
17
|
+
handler: () => Promise<TData>,
|
|
18
|
+
hydrate: boolean = true,
|
|
19
|
+
): ?Result<TData>;
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The `useServerEffect` hook is an integral part of server-side rendering. It has different behavior depending on whether it is running on the server (and in what context) or the client.
|
|
23
|
+
|
|
24
|
+
## Server-side behavior
|
|
25
|
+
|
|
26
|
+
First, this hook checks the server-side rendering cache for the request identifier; if it finds a cached value, it will return that.
|
|
27
|
+
|
|
28
|
+
If there is no cached value, it will return a "loading" state. In addition, if the current rendering component has a [`TrackData`](/docs/data-exports-trackdata--page) ancestor, `useServerEffect` will register the request for fulfillment.
|
|
29
|
+
|
|
30
|
+
This then allows that pending request to be fulfilled with [`fulfillAllDataRequests`](/docs/data-exports-fulfillalldatarequests--page), the response to be placed into the cache, and the render to be reexecuted, at which point, this hook will be able to provide that result instead of "loading.
|
|
31
|
+
|
|
32
|
+
More details about server-side rendering with Wonder Blocks Data can be found in the [relevant overview section](/docs/data-server-side-rendering-and-hydration--page).
|
|
33
|
+
|
|
34
|
+
## Client-side behavior
|
|
35
|
+
|
|
36
|
+
On initial render in the client, this hook will look for a corresponding value in the Wonder Blocks Data hydration cache. If there is one, it will delete it from the hydration cache and return that value.
|
|
37
|
+
|
|
38
|
+
Otherwise, it will return `null`.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {Meta} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
<Meta
|
|
4
|
+
title="Data / Exports / useSharedCache()"
|
|
5
|
+
parameters={{
|
|
6
|
+
chromatic: {
|
|
7
|
+
disableSnapshot: true,
|
|
8
|
+
},
|
|
9
|
+
}}
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
# useSharedCache()
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
function useSharedCache<TValue: ValidCacheData>(
|
|
16
|
+
id: string,
|
|
17
|
+
scope: string,
|
|
18
|
+
initialValue?: ?TValue | (() => ?TValue),
|
|
19
|
+
): [?TValue, CacheValueFn<TValue>];
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The `useSharedCache` hook provides access to a shared in-memory cache. This cache is not part of the cache hydrated by Wonder Blocks Data, so [`clearSharedCache`](/docs/data-exports-clearsharedcache--page) must be called between server-side render cycles.
|
|
23
|
+
|
|
24
|
+
The hook returns a tuple of the currently cached value, or `null` if none is cached, and a function that can be used to set the cached value.
|
|
25
|
+
|
|
26
|
+
The shared cache is passive and as such does not notify of changes to its contents.
|
|
27
|
+
|
|
28
|
+
Each cached item is identified by an id and a scope. The scope is used to group items. Whole scopes can be cleared by specifying the specific scope when calling [`clearSharedCache`](/docs/data-exports-clearsharedcache--page).
|
|
29
|
+
|
|
30
|
+
An optional argument, `initialValue` can be given. This can be either the value to be cached itself or a function that returns the value to be cached (functions themselves are not valid cachable values). This allows for expensive initialization to only occur when it is necessary.
|