@khanacademy/wonder-blocks-data 3.2.0 → 4.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 (42) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/es/index.js +356 -332
  3. package/dist/index.js +507 -456
  4. package/docs.md +17 -35
  5. package/package.json +1 -1
  6. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +7 -46
  7. package/src/__tests__/generated-snapshot.test.js +56 -122
  8. package/src/components/__tests__/data.test.js +372 -297
  9. package/src/components/__tests__/intercept-data.test.js +6 -30
  10. package/src/components/data.js +153 -21
  11. package/src/components/data.md +38 -69
  12. package/src/components/intercept-context.js +6 -2
  13. package/src/components/intercept-data.js +40 -51
  14. package/src/components/intercept-data.md +13 -27
  15. package/src/components/track-data.md +9 -23
  16. package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +17 -0
  17. package/src/hooks/__tests__/use-server-effect.test.js +217 -0
  18. package/src/hooks/__tests__/use-shared-cache.test.js +307 -0
  19. package/src/hooks/use-server-effect.js +45 -0
  20. package/src/hooks/use-shared-cache.js +106 -0
  21. package/src/index.js +15 -19
  22. package/src/util/__tests__/__snapshots__/scoped-in-memory-cache.test.js.snap +19 -0
  23. package/src/util/__tests__/request-fulfillment.test.js +42 -85
  24. package/src/util/__tests__/request-tracking.test.js +72 -191
  25. package/src/util/__tests__/{result-from-cache-entry.test.js → result-from-cache-response.test.js} +9 -10
  26. package/src/util/__tests__/scoped-in-memory-cache.test.js +396 -0
  27. package/src/util/__tests__/ssr-cache.test.js +639 -0
  28. package/src/util/request-fulfillment.js +36 -44
  29. package/src/util/request-tracking.js +62 -75
  30. package/src/util/{result-from-cache-entry.js → result-from-cache-response.js} +10 -13
  31. package/src/util/scoped-in-memory-cache.js +149 -0
  32. package/src/util/ssr-cache.js +206 -0
  33. package/src/util/types.js +43 -108
  34. package/src/hooks/__tests__/use-data.test.js +0 -826
  35. package/src/hooks/use-data.js +0 -143
  36. package/src/util/__tests__/memory-cache.test.js +0 -446
  37. package/src/util/__tests__/request-handler.test.js +0 -121
  38. package/src/util/__tests__/response-cache.test.js +0 -879
  39. package/src/util/memory-cache.js +0 -187
  40. package/src/util/request-handler.js +0 -42
  41. package/src/util/request-handler.md +0 -51
  42. package/src/util/response-cache.js +0 -213
package/docs.md CHANGED
@@ -18,7 +18,7 @@ rehydrate that result.
18
18
  ### Usage
19
19
 
20
20
  ```js static
21
- fulfillAllDataRequests(): Promise<$ReadOnly<ResponseCache>>;
21
+ fulfillAllDataRequests(): Promise<ResponseCache>;
22
22
  ```
23
23
 
24
24
  ## initializeCache
@@ -38,18 +38,18 @@ needs.
38
38
  ### Usage
39
39
 
40
40
  ```js static
41
- initializeCache(sourceCache: $ReadOnly<ResponseCache>): void;
41
+ initializeCache(sourceCache: ResponseCache): void;
42
42
  ```
43
43
 
44
44
  #### Function arguments
45
45
 
46
46
  | Argument | Flow&nbsp;Type | Default | Description |
47
47
  | --- | --- | --- | --- |
48
- | `sourceData` | `$ReadOnly<ResponseCache>` | _Required_ | The source cache that will be used to initialize the response cache. |
48
+ | `sourceData` | `ResponseCache` | _Required_ | The source cache that will be used to initialize the response cache. |
49
49
 
50
50
  ## removeFromCache
51
51
 
52
- Removes an entry associated with the given handler from the hydration cache. The given handler and options identify the entry to be removed.
52
+ Removes an entry from the cache. The given handler and options identify the entry to be removed.
53
53
 
54
54
  If an item is removed, this returns `true`; otherwise, `false`.
55
55
 
@@ -59,44 +59,43 @@ This can be useful during testing.
59
59
  ### Usage
60
60
 
61
61
  ```js static
62
- removeFromCache(handler: IRequestHandler<TOptions, TData>, options: TOptions): boolean;
62
+ removeFromCache(id: string): boolean;
63
63
  ```
64
64
 
65
65
  #### Function arguments
66
66
 
67
67
  | Argument | Flow&nbsp;Type | Default | Description |
68
68
  | --- | --- | --- | --- |
69
- | `handler` | `IRequestHandler<TOptions, TData>` | _Required_ | The handler type for the data to be removed. |
70
- | `options` | `TOptions` | _Required_ | The options that identify the cached request data to be removed. |
69
+ | `id` | `string` | _Required_ | The id of the item to be removed. |
71
70
 
72
71
  ## removeAllFromCache
73
72
 
74
- Removes all entries associated to the given handler that match a given predicate from the hydration cache. If no predicate is given, all cached entries for the given handler are removed.
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.
75
74
 
76
75
  This returns the count of entries removed.
77
76
 
78
77
  This can be used after `initializeCache` to manipulate the cache prior to hydration.
79
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
80
 
81
81
  ### Usage
82
82
 
83
83
  ```js static
84
- removeAllFromCache(handler: IRequestHandler<TOptions, TData>, predicate: (key: string, entry: $ReadOnly<CacheEntry<TData>>) => boolean): number;
84
+ removeAllFromCache(predicate?: (key: string, entry: $ReadOnly<CachedResponse<TData>>) => boolean): number;
85
85
  ```
86
86
 
87
87
  #### Function arguments
88
88
 
89
89
  | Argument | Flow&nbsp;Type | Default | Description |
90
90
  | --- | --- | --- | --- |
91
- | `handler` | `IRequestHandler<TOptions, TData>` | _Required_ | The handler type for the data to be removed. |
92
- | `predicate` | `(key: string, entry: $ReadOnly<CacheEntry<TData>>) => boolean)` | _Optional_ | A predicate to identify which entries to remove. If absent, all data for the handler type is removed; if present, any entries for which the predicate returns `true` will be returned. |
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. |
93
92
 
94
93
  ## Types
95
94
 
96
95
  ### ResponseCache
97
96
 
98
97
  ```js static
99
- type CacheEntry =
98
+ type CachedResponse =
100
99
  | {|
101
100
  data: any,
102
101
  |}
@@ -104,37 +103,20 @@ type CacheEntry =
104
103
  error: string,
105
104
  |};
106
105
 
107
- type HandlerSubcache = {
108
- [key: string]: CacheEntry,
109
- ...,
110
- };
111
-
112
106
  type ResponseCache = {
113
- [handlerType: string]: HandlerSubcache,
107
+ [id: string]: CachedResponse,
114
108
  ...,
115
109
  };
116
110
  ```
117
111
 
118
- A response cache is divided into subcaches by handler type. An example is
119
- shown below.
112
+ An example is of the response cache is shown below.
120
113
 
121
114
  ```js static
122
115
  const responseCache = {
123
- HANDLER_TYPE_A: {
124
- DATA_ID_1: {error: "It go 💥boom 😢"},
125
- DATA_ID_2: {data: ["array", "of", "data"]},
126
- },
127
- HANDLER_TYPE_B: {
128
- DATA_ID_3: {
129
- data: {
130
- some: "data",
131
- },
132
- },
133
- },
116
+ DATA_ID_1: {error: "It go 💥boom 😢"},
117
+ DATA_ID_2: {data: ["array", "of", "data"]},
118
+ DATA_ID_3: {data: {some: "data"}},
134
119
  };
135
120
  ```
136
121
 
137
- In this example, the cache contains data retrieved by two handlers; one with
138
- type `HANDLER_TYPE_A` and one with type `HANDLER_TYPE_B`. Within each subcache,
139
- the data responses are keyed by the returned value of the respective [request
140
- handler](#requesthandler)'s `getKey` method for the relevant request.
122
+ In this example, the cache contains data retrieved for three different requests.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-data",
3
- "version": "3.2.0",
3
+ "version": "4.0.0",
4
4
  "design": "v1",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -55,20 +55,7 @@ exports[`wonder-blocks-data example 1 1`] = `
55
55
  >
56
56
  This request will succeed and give us data!
57
57
  </span>
58
- <span
59
- className=""
60
- style={
61
- Object {
62
- "MozOsxFontSmoothing": "grayscale",
63
- "WebkitFontSmoothing": "antialiased",
64
- "display": "block",
65
- "fontFamily": "Inconsolata, monospace",
66
- "fontSize": 17,
67
- "fontWeight": 400,
68
- "lineHeight": "22px",
69
- }
70
- }
71
- />
58
+ Loading...
72
59
  </div>
73
60
  <div
74
61
  aria-hidden="true"
@@ -131,23 +118,7 @@ exports[`wonder-blocks-data example 1 1`] = `
131
118
  >
132
119
  This request will go boom and give us an error!
133
120
  </span>
134
- <span
135
- className=""
136
- style={
137
- Object {
138
- "MozOsxFontSmoothing": "grayscale",
139
- "WebkitFontSmoothing": "antialiased",
140
- "color": "#d92916",
141
- "display": "block",
142
- "fontFamily": "Inconsolata, monospace",
143
- "fontSize": 17,
144
- "fontWeight": 400,
145
- "lineHeight": "22px",
146
- }
147
- }
148
- >
149
- ERROR:
150
- </span>
121
+ Loading...
151
122
  </div>
152
123
  </div>
153
124
  `;
@@ -220,7 +191,9 @@ exports[`wonder-blocks-data example 2 1`] = `
220
191
  "lineHeight": "22px",
221
192
  }
222
193
  }
223
- />
194
+ >
195
+ I'm DATA from the hydration cache
196
+ </span>
224
197
  </div>
225
198
  <div
226
199
  aria-hidden="true"
@@ -299,6 +272,7 @@ exports[`wonder-blocks-data example 2 1`] = `
299
272
  }
300
273
  >
301
274
  ERROR:
275
+ I'm an ERROR from hydration cache
302
276
  </span>
303
277
  </div>
304
278
  </div>
@@ -340,20 +314,7 @@ exports[`wonder-blocks-data example 3 1`] = `
340
314
  >
341
315
  This received intercepted data!
342
316
  </span>
343
- <span
344
- className=""
345
- style={
346
- Object {
347
- "MozOsxFontSmoothing": "grayscale",
348
- "WebkitFontSmoothing": "antialiased",
349
- "display": "block",
350
- "fontFamily": "Inconsolata, monospace",
351
- "fontSize": 17,
352
- "fontWeight": 400,
353
- "lineHeight": "22px",
354
- }
355
- }
356
- />
317
+ If you see this, the example is broken!
357
318
  </div>
358
319
  `;
359
320
 
@@ -12,7 +12,6 @@ import {Body, BodyMonospace} from "@khanacademy/wonder-blocks-typography";
12
12
  import {View, Server} from "@khanacademy/wonder-blocks-core";
13
13
  import {
14
14
  Data,
15
- RequestHandler,
16
15
  initializeCache,
17
16
  InterceptData,
18
17
  TrackData,
@@ -25,65 +24,36 @@ import Button from "@khanacademy/wonder-blocks-button";
25
24
 
26
25
  describe("wonder-blocks-data", () => {
27
26
  it("example 1", () => {
28
- class MyValidHandler extends RequestHandler {
29
- constructor() {
30
- super("CACHE_MISS_HANDLER_VALID");
31
- }
27
+ const myValidHandler = () =>
28
+ new Promise((resolve, reject) =>
29
+ setTimeout(() => resolve("I'm DATA from a request"), 3000),
30
+ );
32
31
 
33
- fulfillRequest(options) {
34
- return new Promise((resolve, reject) =>
35
- setTimeout(() => resolve("I'm DATA from a request"), 3000),
36
- );
37
- }
38
- }
32
+ const myInvalidHandler = () =>
33
+ new Promise((resolve, reject) =>
34
+ setTimeout(() => reject("I'm an ERROR from a request"), 3000),
35
+ );
39
36
 
40
- class MyInvalidHandler extends RequestHandler {
41
- constructor() {
42
- super("CACHE_MISS_HANDLER_ERROR");
43
- }
44
-
45
- fulfillRequest(options) {
46
- return new Promise((resolve, reject) =>
47
- setTimeout(
48
- () => reject("I'm an ERROR from a request"),
49
- 3000,
50
- ),
51
- );
52
- }
53
- }
54
-
55
- const valid = new MyValidHandler();
56
- const invalid = new MyInvalidHandler();
57
37
  const example = (
58
38
  <View>
59
39
  <View>
60
40
  <Body>This request will succeed and give us data!</Body>
61
- <Data
62
- handler={valid}
63
- options={{
64
- some: "options",
65
- }}
66
- >
67
- {({loading, data}) => {
68
- if (loading) {
41
+ <Data handler={myValidHandler} requestId="VALID">
42
+ {(result) => {
43
+ if (result.status === "loading") {
69
44
  return "Loading...";
70
45
  }
71
46
 
72
- return <BodyMonospace>{data}</BodyMonospace>;
47
+ return <BodyMonospace>{result.data}</BodyMonospace>;
73
48
  }}
74
49
  </Data>
75
50
  </View>
76
51
  <Strut size={Spacing.small_12} />
77
52
  <View>
78
53
  <Body>This request will go boom and give us an error!</Body>
79
- <Data
80
- handler={invalid}
81
- options={{
82
- some: "options",
83
- }}
84
- >
85
- {({loading, error}) => {
86
- if (loading) {
54
+ <Data handler={myInvalidHandler} requestId="INVALID">
55
+ {(result) => {
56
+ if (result.status === "loading") {
87
57
  return "Loading...";
88
58
  }
89
59
 
@@ -93,7 +63,7 @@ describe("wonder-blocks-data", () => {
93
63
  color: Color.red,
94
64
  }}
95
65
  >
96
- ERROR: {error}
66
+ ERROR: {result.error}
97
67
  </BodyMonospace>
98
68
  );
99
69
  }}
@@ -106,52 +76,40 @@ describe("wonder-blocks-data", () => {
106
76
  });
107
77
 
108
78
  it("example 2", () => {
109
- class MyHandler extends RequestHandler {
110
- constructor() {
111
- super("CACHE_HIT_HANDLER");
112
- }
113
- /**
114
- * fulfillRequest should not get called as we already have data cached.
115
- */
116
-
117
- fulfillRequest(options) {
118
- throw new Error(
119
- "If you're seeing this error, the examples are broken and data isn't in the cache that should be.",
120
- );
121
- }
122
- }
79
+ const myHandler = () => {
80
+ throw new Error(
81
+ "If you're seeing this error, the examples are broken and data isn't in the cache that should be.",
82
+ );
83
+ };
123
84
 
124
- const handler = new MyHandler();
125
85
  initializeCache({
126
- CACHE_HIT_HANDLER: {
127
- DATA: {
128
- data: "I'm DATA from the hydration cache",
129
- },
130
- ERROR: {
131
- error: "I'm an ERROR from hydration cache",
132
- },
86
+ DATA: {
87
+ data: "I'm DATA from the hydration cache",
88
+ },
89
+ ERROR: {
90
+ error: "I'm an ERROR from hydration cache",
133
91
  },
134
92
  });
135
93
  const example = (
136
94
  <View>
137
95
  <View>
138
96
  <Body>This cache has data!</Body>
139
- <Data handler={handler} options={"DATA"}>
140
- {({loading, data}) => {
141
- if (loading) {
97
+ <Data handler={myHandler} requestId="DATA">
98
+ {(result) => {
99
+ if (result.status !== "success") {
142
100
  return "If you see this, the example is broken!";
143
101
  }
144
102
 
145
- return <BodyMonospace>{data}</BodyMonospace>;
103
+ return <BodyMonospace>{result.data}</BodyMonospace>;
146
104
  }}
147
105
  </Data>
148
106
  </View>
149
107
  <Strut size={Spacing.small_12} />
150
108
  <View>
151
109
  <Body>This cache has error!</Body>
152
- <Data handler={handler} options={"ERROR"}>
153
- {({loading, error}) => {
154
- if (loading) {
110
+ <Data handler={myHandler} requestId="ERROR">
111
+ {(result) => {
112
+ if (result.status !== "error") {
155
113
  return "If you see this, the example is broken!";
156
114
  }
157
115
 
@@ -161,7 +119,7 @@ describe("wonder-blocks-data", () => {
161
119
  color: Color.red,
162
120
  }}
163
121
  >
164
- ERROR: {error}
122
+ ERROR: {result.error}
165
123
  </BodyMonospace>
166
124
  );
167
125
  }}
@@ -174,40 +132,25 @@ describe("wonder-blocks-data", () => {
174
132
  });
175
133
 
176
134
  it("example 3", () => {
177
- class MyHandler extends RequestHandler {
178
- constructor() {
179
- super("INTERCEPT_DATA_HANDLER1");
180
- }
181
-
182
- fulfillRequest(options) {
183
- return Promise.reject(new Error("You should not see this!"));
184
- }
185
- }
186
-
187
- const handler = new MyHandler();
135
+ const myHandler = () =>
136
+ Promise.reject(new Error("You should not see this!"));
188
137
 
189
- const fulfillRequestInterceptor = function (options) {
190
- if (options === "DATA") {
191
- return Promise.resolve("INTERCEPTED DATA!");
192
- }
193
-
194
- return null;
195
- };
138
+ const interceptHandler = () => Promise.resolve("INTERCEPTED DATA!");
196
139
 
197
140
  const example = (
198
141
  <InterceptData
199
- handler={handler}
200
- fulfillRequest={fulfillRequestInterceptor}
142
+ handler={interceptHandler}
143
+ requestId="INTERCEPT_EXAMPLE"
201
144
  >
202
145
  <View>
203
146
  <Body>This received intercepted data!</Body>
204
- <Data handler={handler} options={"DATA"}>
205
- {({loading, data}) => {
206
- if (loading) {
147
+ <Data handler={myHandler} requestId="INTERCEPT_EXAMPLE">
148
+ {(result) => {
149
+ if (result.status !== "success") {
207
150
  return "If you see this, the example is broken!";
208
151
  }
209
152
 
210
- return <BodyMonospace>{data}</BodyMonospace>;
153
+ return <BodyMonospace>{result.data}</BodyMonospace>;
211
154
  }}
212
155
  </Data>
213
156
  </View>
@@ -265,17 +208,10 @@ describe("wonder-blocks-data", () => {
265
208
  });
266
209
 
267
210
  it("example 5", () => {
268
- class MyPretendHandler extends RequestHandler {
269
- constructor() {
270
- super("MY_PRETEND_HANDLER");
271
- }
272
-
273
- fulfillRequest(options) {
274
- return new Promise((resolve, reject) =>
275
- setTimeout(() => resolve("DATA!"), 3000),
276
- );
277
- }
278
- }
211
+ const myPretendHandler = () =>
212
+ new Promise((resolve, reject) =>
213
+ setTimeout(() => resolve("DATA!"), 3000),
214
+ );
279
215
 
280
216
  class Example extends React.Component {
281
217
  constructor() {
@@ -286,7 +222,6 @@ describe("wonder-blocks-data", () => {
286
222
  */
287
223
 
288
224
  this.state = {};
289
- this._handler = new MyPretendHandler();
290
225
  }
291
226
 
292
227
  static getDerivedStateFromError(error) {
@@ -344,12 +279,17 @@ describe("wonder-blocks-data", () => {
344
279
  <React.Fragment>
345
280
  <Strut size={Spacing.small_12} />
346
281
  <TrackData>
347
- <Data handler={this._handler} options={{}}>
348
- {({loading, data, error}) => (
282
+ <Data
283
+ handler={myPretendHandler}
284
+ requestId="TRACK_DATA_EXAMPLE"
285
+ >
286
+ {(result) => (
349
287
  <View>
350
- <BodyMonospace>{`Loading: ${loading}`}</BodyMonospace>
288
+ <BodyMonospace>{`Loading: ${
289
+ result.status === "loading"
290
+ }`}</BodyMonospace>
351
291
  <BodyMonospace>{`Data: ${JSON.stringify(
352
- data,
292
+ result.data,
353
293
  )}`}</BodyMonospace>
354
294
  </View>
355
295
  )}
@@ -372,12 +312,6 @@ describe("wonder-blocks-data", () => {
372
312
  not updated in the rendered tree.
373
313
  </Body>
374
314
  <Strut size={Spacing.small_12} />
375
- <Body>
376
- If you click to remount after the data appears,
377
- we'll rerender with the now cached data, and
378
- above should update accordingly.
379
- </Body>
380
- <Strut size={Spacing.small_12} />
381
315
  <BodyMonospace>{data}</BodyMonospace>
382
316
  </View>
383
317
  </React.Fragment>