@khanacademy/wonder-blocks-data 5.0.1 → 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.
Files changed (85) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/es/index.js +771 -372
  3. package/dist/index.js +1191 -550
  4. package/legacy-docs.md +3 -0
  5. package/package.json +2 -2
  6. package/src/__docs__/_overview_.stories.mdx +18 -0
  7. package/src/__docs__/_overview_graphql.stories.mdx +35 -0
  8. package/src/__docs__/_overview_ssr_.stories.mdx +185 -0
  9. package/src/__docs__/_overview_testing_.stories.mdx +123 -0
  10. package/src/__docs__/exports.clear-shared-cache.stories.mdx +20 -0
  11. package/src/__docs__/exports.data-error.stories.mdx +23 -0
  12. package/src/__docs__/exports.data-errors.stories.mdx +23 -0
  13. package/src/{components/data.md → __docs__/exports.data.stories.mdx} +15 -18
  14. package/src/__docs__/exports.fulfill-all-data-requests.stories.mdx +24 -0
  15. package/src/__docs__/exports.gql-error.stories.mdx +23 -0
  16. package/src/__docs__/exports.gql-errors.stories.mdx +20 -0
  17. package/src/__docs__/exports.gql-router.stories.mdx +29 -0
  18. package/src/__docs__/exports.has-unfulfilled-requests.stories.mdx +20 -0
  19. package/src/{components/intercept-requests.md → __docs__/exports.intercept-requests.stories.mdx} +16 -1
  20. package/src/__docs__/exports.intialize-cache.stories.mdx +29 -0
  21. package/src/__docs__/exports.remove-all-from-cache.stories.mdx +24 -0
  22. package/src/__docs__/exports.remove-from-cache.stories.mdx +25 -0
  23. package/src/__docs__/exports.request-fulfillment.stories.mdx +36 -0
  24. package/src/__docs__/exports.scoped-in-memory-cache.stories.mdx +92 -0
  25. package/src/__docs__/exports.serializable-in-memory-cache.stories.mdx +112 -0
  26. package/src/__docs__/exports.status.stories.mdx +31 -0
  27. package/src/{components/track-data.md → __docs__/exports.track-data.stories.mdx} +15 -0
  28. package/src/__docs__/exports.use-cached-effect.stories.mdx +41 -0
  29. package/src/__docs__/exports.use-gql.stories.mdx +73 -0
  30. package/src/__docs__/exports.use-hydratable-effect.stories.mdx +43 -0
  31. package/src/__docs__/exports.use-server-effect.stories.mdx +38 -0
  32. package/src/__docs__/exports.use-shared-cache.stories.mdx +30 -0
  33. package/src/__docs__/exports.when-client-side.stories.mdx +33 -0
  34. package/src/__docs__/types.cached-response.stories.mdx +29 -0
  35. package/src/__docs__/types.error-options.stories.mdx +21 -0
  36. package/src/__docs__/types.gql-context.stories.mdx +20 -0
  37. package/src/__docs__/types.gql-fetch-fn.stories.mdx +24 -0
  38. package/src/__docs__/types.gql-fetch-options.stories.mdx +24 -0
  39. package/src/__docs__/types.gql-operation-type.stories.mdx +24 -0
  40. package/src/__docs__/types.gql-operation.stories.mdx +67 -0
  41. package/src/__docs__/types.response-cache.stories.mdx +33 -0
  42. package/src/__docs__/types.result.stories.mdx +39 -0
  43. package/src/__docs__/types.scoped-cache.stories.mdx +27 -0
  44. package/src/__docs__/types.valid-cache-data.stories.mdx +23 -0
  45. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +0 -80
  46. package/src/__tests__/generated-snapshot.test.js +0 -24
  47. package/src/components/__tests__/data.test.js +149 -128
  48. package/src/components/data.js +22 -112
  49. package/src/components/intercept-requests.js +1 -1
  50. package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +8 -8
  51. package/src/hooks/__tests__/use-cached-effect.test.js +507 -0
  52. package/src/hooks/__tests__/use-gql-router-context.test.js +133 -0
  53. package/src/hooks/__tests__/use-gql.test.js +1 -30
  54. package/src/hooks/__tests__/use-hydratable-effect.test.js +708 -0
  55. package/src/hooks/__tests__/use-server-effect.test.js +39 -11
  56. package/src/hooks/use-cached-effect.js +225 -0
  57. package/src/hooks/use-gql-router-context.js +50 -0
  58. package/src/hooks/use-gql.js +22 -52
  59. package/src/hooks/use-hydratable-effect.js +206 -0
  60. package/src/hooks/use-request-interception.js +20 -23
  61. package/src/hooks/use-server-effect.js +12 -5
  62. package/src/hooks/use-shared-cache.js +13 -11
  63. package/src/index.js +53 -3
  64. package/src/util/__tests__/__snapshots__/serializable-in-memory-cache.test.js.snap +19 -0
  65. package/src/util/__tests__/merge-gql-context.test.js +74 -0
  66. package/src/util/__tests__/request-fulfillment.test.js +23 -42
  67. package/src/util/__tests__/request-tracking.test.js +26 -7
  68. package/src/util/__tests__/result-from-cache-response.test.js +19 -5
  69. package/src/util/__tests__/scoped-in-memory-cache.test.js +6 -85
  70. package/src/util/__tests__/serializable-in-memory-cache.test.js +398 -0
  71. package/src/util/__tests__/ssr-cache.test.js +52 -52
  72. package/src/util/abort-error.js +15 -0
  73. package/src/util/data-error.js +58 -0
  74. package/src/util/get-gql-data-from-response.js +3 -2
  75. package/src/util/gql-error.js +19 -11
  76. package/src/util/merge-gql-context.js +34 -0
  77. package/src/util/request-fulfillment.js +49 -46
  78. package/src/util/request-tracking.js +69 -15
  79. package/src/util/result-from-cache-response.js +12 -16
  80. package/src/util/scoped-in-memory-cache.js +24 -47
  81. package/src/util/serializable-in-memory-cache.js +49 -0
  82. package/src/util/ssr-cache.js +9 -8
  83. package/src/util/status.js +30 -0
  84. package/src/util/types.js +18 -1
  85. package/docs.md +0 -122
@@ -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&nbsp;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&nbsp;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&nbsp;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.
@@ -0,0 +1,33 @@
1
+ import {Meta} from "@storybook/addon-docs";
2
+
3
+ <Meta
4
+ title="Data / Exports / WhenClientSide"
5
+ parameters={{
6
+ chromatic: {
7
+ disableSnapshot: true,
8
+ },
9
+ }}
10
+ />
11
+
12
+ # WhenClientSide
13
+
14
+ This enumeration is used with [`useHydratableEffect`](/docs/data-exports-usehydratableeffect--page). It defines how the hook should behave when rendering on the client.
15
+
16
+ ## WhenClientSide.DoNotHydrate
17
+
18
+ The effect will not be hydrated and as such the effect will always be executed on initial render in the client. This is an advanced use-case that you should avoid unless you are certain of what you are doing.
19
+
20
+ Without hydration support to ensure the data is available for hydration on the client, your server and client rendered pages may differ and the hydration will fail. This option is useful if something else is responsible for data capture and hydration of the action that gets executed. For example, if the action uses Apollo Client to perform the asynchronous action executed by this effect, then that may be also performing hydration responsibilities. However, be cautious; the code that calls `useHydratableEffect` will have to have access to that data on hydration as `useHydratableEffect` will return a "loading" state on initial render, which is not what you will want.
21
+
22
+ ## WhenClientSide.ExecuteWhenNoResult
23
+
24
+ On initial render in the client, the effect is hydrated from the server-side rendered result. However, it is only executed if there was no server-side render result to hydrate (this can happen if the server-side rendered request was aborted, or if the component is rendering for the first time on the client and was never part of the server-side rendered content).
25
+
26
+ ## WhenClientSide.ExecuteWhenNoSuccessResult
27
+
28
+ This behavior will hydrate the server-side result, but it will only execute the effect on the client if the hydrated result is not a success result.
29
+
30
+ ## WhenClientSide.AlwaysExecute
31
+
32
+ When the effect is executed with this behavior, the server-side result will be hydrated and the effect will be executed on the initial client-side render, regardless of the hydrated result status.
33
+
@@ -0,0 +1,29 @@
1
+ import {Meta} from "@storybook/addon-docs";
2
+
3
+ <Meta
4
+ title="Data / Types / CachedResponse<>"
5
+ parameters={{
6
+ chromatic: {
7
+ disableSnapshot: true,
8
+ },
9
+ }}
10
+ />
11
+
12
+ # CachedResponse&lt;&gt;
13
+
14
+ ```ts
15
+ type CachedResponse<TData: ValidCacheData> =
16
+ | {|
17
+ +error: string,
18
+ +data?: void,
19
+ |}
20
+ | {|
21
+ +data: TData,
22
+ +error?: void,
23
+ |};
24
+ ```
25
+
26
+ `CachedResponse<>` is a special union type that is used to represent the serialized result of a request, which can be used by Wonder Blocks Data to
27
+ hydrate the response.
28
+
29
+ See the section on [server-side rendering](/docs/data-server-side-rendering-and-hydration--page) for more information.