@khanacademy/wonder-blocks-data 3.1.2 → 5.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 (51) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/es/index.js +408 -349
  3. package/dist/index.js +568 -467
  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 +60 -126
  8. package/src/components/__tests__/data.test.js +373 -313
  9. package/src/components/__tests__/intercept-requests.test.js +58 -0
  10. package/src/components/data.js +139 -21
  11. package/src/components/data.md +38 -69
  12. package/src/components/gql-router.js +1 -1
  13. package/src/components/intercept-context.js +6 -3
  14. package/src/components/intercept-requests.js +69 -0
  15. package/src/components/intercept-requests.md +54 -0
  16. package/src/components/track-data.md +9 -23
  17. package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +17 -0
  18. package/src/hooks/__tests__/use-gql.test.js +1 -0
  19. package/src/hooks/__tests__/use-request-interception.test.js +255 -0
  20. package/src/hooks/__tests__/use-server-effect.test.js +217 -0
  21. package/src/hooks/__tests__/use-shared-cache.test.js +307 -0
  22. package/src/hooks/use-gql.js +39 -31
  23. package/src/hooks/use-request-interception.js +54 -0
  24. package/src/hooks/use-server-effect.js +45 -0
  25. package/src/hooks/use-shared-cache.js +106 -0
  26. package/src/index.js +17 -20
  27. package/src/util/__tests__/__snapshots__/scoped-in-memory-cache.test.js.snap +19 -0
  28. package/src/util/__tests__/request-fulfillment.test.js +42 -85
  29. package/src/util/__tests__/request-tracking.test.js +72 -191
  30. package/src/util/__tests__/{result-from-cache-entry.test.js → result-from-cache-response.test.js} +9 -10
  31. package/src/util/__tests__/scoped-in-memory-cache.test.js +396 -0
  32. package/src/util/__tests__/ssr-cache.test.js +639 -0
  33. package/src/util/gql-types.js +5 -10
  34. package/src/util/request-fulfillment.js +36 -44
  35. package/src/util/request-tracking.js +62 -75
  36. package/src/util/{result-from-cache-entry.js → result-from-cache-response.js} +10 -13
  37. package/src/util/scoped-in-memory-cache.js +149 -0
  38. package/src/util/ssr-cache.js +206 -0
  39. package/src/util/types.js +43 -108
  40. package/src/components/__tests__/intercept-data.test.js +0 -87
  41. package/src/components/intercept-data.js +0 -77
  42. package/src/components/intercept-data.md +0 -65
  43. package/src/hooks/__tests__/use-data.test.js +0 -826
  44. package/src/hooks/use-data.js +0 -143
  45. package/src/util/__tests__/memory-cache.test.js +0 -446
  46. package/src/util/__tests__/request-handler.test.js +0 -121
  47. package/src/util/__tests__/response-cache.test.js +0 -879
  48. package/src/util/memory-cache.js +0 -187
  49. package/src/util/request-handler.js +0 -42
  50. package/src/util/request-handler.md +0 -51
  51. package/src/util/response-cache.js +0 -213
package/dist/index.js CHANGED
@@ -82,7 +82,7 @@ module.exports =
82
82
  /******/
83
83
  /******/
84
84
  /******/ // Load entry module and return exports
85
- /******/ return __webpack_require__(__webpack_require__.s = 19);
85
+ /******/ return __webpack_require__(__webpack_require__.s = 20);
86
86
  /******/ })
87
87
  /************************************************************************/
88
88
  /******/ ([
@@ -93,12 +93,18 @@ module.exports = require("react");
93
93
 
94
94
  /***/ }),
95
95
  /* 1 */
96
+ /***/ (function(module, exports) {
97
+
98
+ module.exports = require("@khanacademy/wonder-stuff-core");
99
+
100
+ /***/ }),
101
+ /* 2 */
96
102
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
97
103
 
98
104
  "use strict";
99
105
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return GqlErrors; });
100
106
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return GqlError; });
101
- /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(10);
107
+ /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
102
108
  /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__);
103
109
 
104
110
 
@@ -130,27 +136,28 @@ class GqlError extends _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0
130
136
  }
131
137
 
132
138
  /***/ }),
133
- /* 2 */
139
+ /* 3 */
134
140
  /***/ (function(module, exports) {
135
141
 
136
142
  module.exports = require("@khanacademy/wonder-blocks-core");
137
143
 
138
144
  /***/ }),
139
- /* 3 */
145
+ /* 4 */
140
146
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
141
147
 
142
148
  "use strict";
143
- /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return ResponseCache; });
144
- /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
149
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return SsrCache; });
150
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3);
145
151
  /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__);
146
- /* harmony import */ var _memory_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
147
-
152
+ /* harmony import */ var _scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
148
153
 
149
154
 
155
+ const DefaultScope = "default";
150
156
  /**
151
157
  * The default instance is stored here.
152
158
  * It's created below in the Default() static property.
153
159
  */
160
+
154
161
  let _default;
155
162
  /**
156
163
  * Implements the response cache.
@@ -159,10 +166,10 @@ let _default;
159
166
  */
160
167
 
161
168
 
162
- class ResponseCache {
169
+ class SsrCache {
163
170
  static get Default() {
164
171
  if (!_default) {
165
- _default = new ResponseCache();
172
+ _default = new SsrCache();
166
173
  }
167
174
 
168
175
  return _default;
@@ -174,31 +181,29 @@ class ResponseCache {
174
181
  throw new Error("Cannot initialize data response cache more than once");
175
182
  }
176
183
 
177
- try {
178
- this._hydrationCache = new _memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"](source);
179
- } catch (e) {
180
- throw new Error(`An error occurred trying to initialize the data response cache: ${e}`);
181
- }
184
+ this._hydrationCache = new _scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ScopedInMemoryCache */ "a"]({
185
+ // $FlowIgnore[incompatible-call]
186
+ [DefaultScope]: source
187
+ });
182
188
  };
183
189
 
184
- this.cacheData = (handler, options, data) => this._setCacheEntry(handler, options, {
190
+ this.cacheData = (id, data, hydrate) => this._setCachedResponse(id, {
185
191
  data
186
- });
192
+ }, hydrate);
187
193
 
188
- this.cacheError = (handler, options, error) => {
194
+ this.cacheError = (id, error, hydrate) => {
189
195
  const errorMessage = typeof error === "string" ? error : error.message;
190
- return this._setCacheEntry(handler, options, {
196
+ return this._setCachedResponse(id, {
191
197
  error: errorMessage
192
- });
198
+ }, hydrate);
193
199
  };
194
200
 
195
- this.getEntry = (handler, options) => {
201
+ this.getEntry = id => {
202
+ var _this$_ssrOnlyCache$g, _this$_ssrOnlyCache;
203
+
196
204
  // Get the cached entry for this value.
197
- // If the handler wants WB Data to hydrate (i.e. handler.hydrate is
198
- // true), we use our hydration cache. Otherwise, if we're server-side
199
- // we use our SSR-only cache. Otherwise, there's no entry to return.
200
- const cache = handler.hydrate ? this._hydrationCache : _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide() ? this._ssrOnlyCache : undefined;
201
- const internalEntry = cache == null ? void 0 : cache.retrieve(handler, options); // If we are not server-side and we hydrated something, let's clear
205
+ // We first look in the ssr cache and then the hydration cache.
206
+ const internalEntry = (_this$_ssrOnlyCache$g = (_this$_ssrOnlyCache = this._ssrOnlyCache) == null ? void 0 : _this$_ssrOnlyCache.get(DefaultScope, id)) != null ? _this$_ssrOnlyCache$g : this._hydrationCache.get(DefaultScope, id); // If we are not server-side and we hydrated something, let's clear
202
207
  // that from the hydration cache to save memory.
203
208
 
204
209
  if (this._ssrOnlyCache == null && internalEntry != null) {
@@ -208,48 +213,71 @@ class ResponseCache {
208
213
  // that's not an expected use-case. If two different places use the
209
214
  // same handler and options (i.e. the same request), then the
210
215
  // handler should cater to that to ensure they share the result.
211
- this._hydrationCache.remove(handler, options);
212
- }
216
+ this._hydrationCache.purge(DefaultScope, id);
217
+ } // Getting the typing right between the in-memory cache and this
218
+ // is hard. Just telling flow it's OK.
219
+ // $FlowIgnore[incompatible-return]
220
+
213
221
 
214
222
  return internalEntry;
215
223
  };
216
224
 
217
- this.remove = (handler, options) => {
218
- var _this$_ssrOnlyCache$r, _this$_ssrOnlyCache;
225
+ this.remove = id => {
226
+ var _this$_ssrOnlyCache$p, _this$_ssrOnlyCache2;
219
227
 
220
228
  // NOTE(somewhatabstract): We could invoke removeAll with a predicate
221
229
  // to match the key of the entry we're removing, but that's an
222
230
  // inefficient way to remove a single item, so let's not do that.
223
231
  // Delete the entry from the appropriate cache.
224
- return handler.hydrate ? this._hydrationCache.remove(handler, options) : (_this$_ssrOnlyCache$r = (_this$_ssrOnlyCache = this._ssrOnlyCache) == null ? void 0 : _this$_ssrOnlyCache.remove(handler, options)) != null ? _this$_ssrOnlyCache$r : false;
232
+ return this._hydrationCache.purge(DefaultScope, id) || ((_this$_ssrOnlyCache$p = (_this$_ssrOnlyCache2 = this._ssrOnlyCache) == null ? void 0 : _this$_ssrOnlyCache2.purge(DefaultScope, id)) != null ? _this$_ssrOnlyCache$p : false);
225
233
  };
226
234
 
227
- this.removeAll = (handler, predicate) => {
228
- var _this$_ssrOnlyCache$r2, _this$_ssrOnlyCache2;
235
+ this.removeAll = predicate => {
236
+ var _this$_ssrOnlyCache3;
229
237
 
230
- // Apply the predicate to what we have in the appropriate cache.
231
- return handler.hydrate ? this._hydrationCache.removeAll(handler, predicate) : (_this$_ssrOnlyCache$r2 = (_this$_ssrOnlyCache2 = this._ssrOnlyCache) == null ? void 0 : _this$_ssrOnlyCache2.removeAll(handler, predicate)) != null ? _this$_ssrOnlyCache$r2 : 0;
238
+ const realPredicate = predicate ? // We know what we're putting into the cache so let's assume it
239
+ // conforms.
240
+ // $FlowIgnore[incompatible-call]
241
+ (_, key, cachedEntry) => predicate(key, cachedEntry) : undefined; // Apply the predicate to what we have in our caches.
242
+
243
+ this._hydrationCache.purgeAll(realPredicate);
244
+
245
+ (_this$_ssrOnlyCache3 = this._ssrOnlyCache) == null ? void 0 : _this$_ssrOnlyCache3.purgeAll(realPredicate);
232
246
  };
233
247
 
234
248
  this.cloneHydratableData = () => {
249
+ var _cache$DefaultScope;
250
+
235
251
  // We return our hydration cache only.
236
- return this._hydrationCache.cloneData();
252
+ const cache = this._hydrationCache.clone(); // If we're empty, we still want to return an object, so we default
253
+ // to an empty object.
254
+ // We only need the default scope out of our scoped in-memory cache.
255
+ // We know that it conforms to our expectations.
256
+ // $FlowIgnore[incompatible-return]
257
+
258
+
259
+ return (_cache$DefaultScope = cache[DefaultScope]) != null ? _cache$DefaultScope : {};
237
260
  };
238
261
 
239
- this._ssrOnlyCache = _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide() ? ssrOnlyCache || new _memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"]() : undefined;
240
- this._hydrationCache = hydrationCache || new _memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"]();
262
+ this._ssrOnlyCache = _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide() ? ssrOnlyCache || new _scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ScopedInMemoryCache */ "a"]() : undefined;
263
+ this._hydrationCache = hydrationCache || new _scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ScopedInMemoryCache */ "a"]();
241
264
  }
242
265
 
243
- _setCacheEntry(handler, options, entry) {
266
+ _setCachedResponse(id, entry, hydrate) {
244
267
  const frozenEntry = Object.freeze(entry);
245
268
 
246
- if (this._ssrOnlyCache != null) {
269
+ if (_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
247
270
  // We are server-side.
248
271
  // We need to store this value.
249
- if (handler.hydrate) {
250
- this._hydrationCache.store(handler, options, frozenEntry);
272
+ if (hydrate) {
273
+ this._hydrationCache.set(DefaultScope, id, frozenEntry);
251
274
  } else {
252
- this._ssrOnlyCache.store(handler, options, frozenEntry);
275
+ var _this$_ssrOnlyCache4;
276
+
277
+ // Usually, when server-side, this cache will always be present.
278
+ // We do fake server-side in our doc example though, when it
279
+ // won't be.
280
+ (_this$_ssrOnlyCache4 = this._ssrOnlyCache) == null ? void 0 : _this$_ssrOnlyCache4.set(DefaultScope, id, frozenEntry);
253
281
  }
254
282
  }
255
283
 
@@ -265,7 +293,124 @@ class ResponseCache {
265
293
  }
266
294
 
267
295
  /***/ }),
268
- /* 4 */
296
+ /* 5 */
297
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
298
+
299
+ "use strict";
300
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return ScopedInMemoryCache; });
301
+ /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
302
+ /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__);
303
+
304
+
305
+ /**
306
+ * Describe an in-memory cache.
307
+ */
308
+ class ScopedInMemoryCache {
309
+ constructor(initialCache = Object.freeze({})) {
310
+ this.set = (scope, id, value) => {
311
+ var _this$_cache$scope;
312
+
313
+ if (!id || typeof id !== "string") {
314
+ throw new _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["KindError"]("id must be non-empty string", _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["Errors"].InvalidInput);
315
+ }
316
+
317
+ if (!scope || typeof scope !== "string") {
318
+ throw new _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["KindError"]("scope must be non-empty string", _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["Errors"].InvalidInput);
319
+ }
320
+
321
+ if (typeof value === "function") {
322
+ throw new _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["KindError"]("value must be a non-function value", _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["Errors"].InvalidInput);
323
+ }
324
+
325
+ this._cache[scope] = (_this$_cache$scope = this._cache[scope]) != null ? _this$_cache$scope : {};
326
+ this._cache[scope][id] = Object.freeze(Object(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["clone"])(value));
327
+ };
328
+
329
+ this.get = (scope, id) => {
330
+ var _this$_cache$scope$id, _this$_cache$scope2;
331
+
332
+ return (_this$_cache$scope$id = (_this$_cache$scope2 = this._cache[scope]) == null ? void 0 : _this$_cache$scope2[id]) != null ? _this$_cache$scope$id : null;
333
+ };
334
+
335
+ this.purge = (scope, id) => {
336
+ var _this$_cache$scope3;
337
+
338
+ if (!((_this$_cache$scope3 = this._cache[scope]) != null && _this$_cache$scope3[id])) {
339
+ return;
340
+ }
341
+
342
+ delete this._cache[scope][id];
343
+
344
+ if (Object.keys(this._cache[scope]).length === 0) {
345
+ delete this._cache[scope];
346
+ }
347
+ };
348
+
349
+ this.purgeScope = (scope, predicate) => {
350
+ if (!this._cache[scope]) {
351
+ return;
352
+ }
353
+
354
+ if (predicate == null) {
355
+ delete this._cache[scope];
356
+ return;
357
+ }
358
+
359
+ for (const key of Object.keys(this._cache[scope])) {
360
+ if (predicate(key, this._cache[scope][key])) {
361
+ delete this._cache[scope][key];
362
+ }
363
+ }
364
+
365
+ if (Object.keys(this._cache[scope]).length === 0) {
366
+ delete this._cache[scope];
367
+ }
368
+ };
369
+
370
+ this.purgeAll = predicate => {
371
+ if (predicate == null) {
372
+ this._cache = {};
373
+ return;
374
+ }
375
+
376
+ for (const scope of Object.keys(this._cache)) {
377
+ this.purgeScope(scope, (id, value) => predicate(scope, id, value));
378
+ }
379
+ };
380
+
381
+ this.clone = () => {
382
+ try {
383
+ return Object(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["clone"])(this._cache);
384
+ } catch (e) {
385
+ throw new Error(`An error occurred while trying to clone the cache: ${e}`);
386
+ }
387
+ };
388
+
389
+ try {
390
+ this._cache = Object(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["clone"])(initialCache);
391
+ } catch (e) {
392
+ throw new _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["KindError"](`An error occurred trying to initialize from a response cache snapshot: ${e}`, _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["Errors"].InvalidInput);
393
+ }
394
+ }
395
+ /**
396
+ * Indicate if this cache is being used or not.
397
+ *
398
+ * When the cache has entries, returns `true`; otherwise, returns `false`.
399
+ */
400
+
401
+
402
+ get inUse() {
403
+ return Object.keys(this._cache).length > 0;
404
+ }
405
+ /**
406
+ * Set a value in the cache.
407
+ */
408
+
409
+
410
+ }
411
+
412
+ /***/ }),
413
+ /* 6 */
269
414
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
270
415
 
271
416
  "use strict";
@@ -273,8 +418,8 @@ class ResponseCache {
273
418
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RequestTracker; });
274
419
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
275
420
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
276
- /* harmony import */ var _response_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
277
- /* harmony import */ var _request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7);
421
+ /* harmony import */ var _ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
422
+ /* harmony import */ var _request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10);
278
423
 
279
424
 
280
425
 
@@ -312,48 +457,31 @@ class RequestTracker {
312
457
 
313
458
 
314
459
  constructor(responseCache = undefined) {
315
- this._trackedHandlers = {};
316
460
  this._trackedRequests = {};
317
461
 
318
- this.trackDataRequest = (handler, options) => {
319
- const key = handler.getKey(options);
320
- const type = handler.type;
321
- /**
322
- * Make sure we have stored the handler for use when fulfilling requests.
323
- */
324
-
325
- if (this._trackedHandlers[type] == null) {
326
- this._trackedHandlers[type] = handler;
327
- this._trackedRequests[type] = {};
328
- }
462
+ this.trackDataRequest = (id, handler, hydrate) => {
329
463
  /**
330
464
  * If we don't already have this tracked, then let's track it.
331
465
  */
332
-
333
-
334
- if (this._trackedRequests[type][key] == null) {
335
- this._trackedRequests[type][key] = options;
466
+ if (this._trackedRequests[id] == null) {
467
+ this._trackedRequests[id] = {
468
+ handler,
469
+ hydrate
470
+ };
336
471
  }
337
472
  };
338
473
 
339
474
  this.reset = () => {
340
- this._trackedHandlers = {};
341
475
  this._trackedRequests = {};
342
476
  };
343
477
 
344
478
  this.fulfillTrackedRequests = () => {
345
479
  const promises = [];
346
480
 
347
- for (const handlerType of Object.keys(this._trackedHandlers)) {
348
- const handler = this._trackedHandlers[handlerType]; // For each handler, we will perform the request fulfillments!
349
-
350
- const requests = this._trackedRequests[handlerType];
481
+ for (const requestKey of Object.keys(this._trackedRequests)) {
482
+ const promise = this._requestFulfillment.fulfill(requestKey, this._trackedRequests[requestKey]);
351
483
 
352
- for (const requestKey of Object.keys(requests)) {
353
- const promise = this._requestFulfillment.fulfill(handler, requests[requestKey]);
354
-
355
- promises.push(promise);
356
- }
484
+ promises.push(promise);
357
485
  }
358
486
  /**
359
487
  * Clear out our tracked info.
@@ -382,7 +510,7 @@ class RequestTracker {
382
510
  return Promise.all(promises).then(() => this._responseCache.cloneHydratableData());
383
511
  };
384
512
 
385
- this._responseCache = responseCache || _response_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ResponseCache */ "a"].Default;
513
+ this._responseCache = responseCache || _ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* SsrCache */ "a"].Default;
386
514
  this._requestFulfillment = new _request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestFulfillment */ "a"](responseCache);
387
515
  }
388
516
  /**
@@ -409,15 +537,15 @@ class RequestTracker {
409
537
  * Calling this method marks tracked requests as fulfilled; requests are
410
538
  * removed from the list of tracked requests by calling this method.
411
539
  *
412
- * @returns {Promise<Cache>} A frozen cache of the data that was cached
413
- * as a result of fulfilling the tracked requests.
540
+ * @returns {Promise<ResponseCache>} The promise of the data that was
541
+ * cached as a result of fulfilling the tracked requests.
414
542
  */
415
543
 
416
544
 
417
545
  }
418
546
 
419
547
  /***/ }),
420
- /* 5 */
548
+ /* 7 */
421
549
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
422
550
 
423
551
  "use strict";
@@ -426,158 +554,122 @@ class RequestTracker {
426
554
 
427
555
 
428
556
  /**
429
- * InterceptContext defines a map from handler type to interception methods.
557
+ * InterceptContext defines a map from request ID to interception methods.
430
558
  *
431
559
  * INTERNAL USE ONLY
432
560
  */
433
- const InterceptContext = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createContext"]({});
561
+ const InterceptContext = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createContext"]([]);
434
562
  /* harmony default export */ __webpack_exports__["a"] = (InterceptContext);
435
563
 
436
564
  /***/ }),
437
- /* 6 */
565
+ /* 8 */
438
566
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
439
567
 
440
568
  "use strict";
441
- /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useData; });
442
- /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
569
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useServerEffect; });
570
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3);
443
571
  /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__);
444
572
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(0);
445
573
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
446
- /* harmony import */ var _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7);
447
- /* harmony import */ var _components_intercept_context_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5);
448
- /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(4);
449
- /* harmony import */ var _util_result_from_cache_entry_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(17);
450
- /* harmony import */ var _util_response_cache_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3);
574
+ /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);
575
+ /* harmony import */ var _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4);
451
576
 
452
577
 
453
578
 
454
579
 
455
580
 
456
-
457
-
458
- const useData = (handler, options) => {
581
+ /**
582
+ * Hook to perform an asynchronous action during server-side rendering.
583
+ *
584
+ * This hook registers an asynchronous action to be performed during
585
+ * server-side rendering. The action is performed only once, and the result
586
+ * is cached against the given identifier so that subsequent calls return that
587
+ * cached result allowing components to render more of the component.
588
+ *
589
+ * This hook requires the Wonder Blocks Data functionality for resolving
590
+ * pending requests, as well as support for the hydration cache to be
591
+ * embedded into a page so that the result can by hydrated (if that is a
592
+ * requirement).
593
+ *
594
+ * The asynchronous action is never invoked on the client-side.
595
+ */
596
+ const useServerEffect = (requestId, handler, hydrate = true) => {
459
597
  // If we're server-side or hydrating, we'll have a cached entry to use.
460
598
  // So we get that and use it to initialize our state.
461
599
  // This works in both hydration and SSR because the very first call to
462
600
  // this will have cached data in those cases as it will be present on the
463
601
  // initial render - and subsequent renders on the client it will be null.
464
- const cachedResult = _util_response_cache_js__WEBPACK_IMPORTED_MODULE_6__[/* ResponseCache */ "a"].Default.getEntry(handler, options);
465
- const [result, setResult] = Object(react__WEBPACK_IMPORTED_MODULE_1__["useState"])(cachedResult); // Lookup to see if there's an interceptor for the handler.
466
- // If we have one, we need to replace the handler with one that
467
- // uses the interceptor.
468
-
469
- const interceptorMap = Object(react__WEBPACK_IMPORTED_MODULE_1__["useContext"])(_components_intercept_context_js__WEBPACK_IMPORTED_MODULE_3__[/* default */ "a"]);
470
- const interceptor = interceptorMap[handler.type]; // If we have an interceptor, we need to replace the handler with one that
471
- // uses the interceptor. This helper function generates a new handler.
472
- // We need this before we track the request as we want the interceptor
473
- // to also work for tracked requests to simplify testing the server-side
474
- // request fulfillment.
475
-
476
- const getMaybeInterceptedHandler = () => {
477
- if (interceptor == null) {
478
- return handler;
479
- }
480
-
481
- const fulfillRequestFn = options => {
482
- var _interceptor$fulfillR;
483
-
484
- return (_interceptor$fulfillR = interceptor.fulfillRequest(options)) != null ? _interceptor$fulfillR : handler.fulfillRequest(options);
485
- };
486
-
487
- return {
488
- fulfillRequest: fulfillRequestFn,
489
- getKey: options => handler.getKey(options),
490
- type: handler.type,
491
- hydrate: handler.hydrate
492
- };
493
- }; // We only track data requests when we are server-side and we don't
602
+ const cachedResult = _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_3__[/* SsrCache */ "a"].Default.getEntry(requestId); // We only track data requests when we are server-side and we don't
494
603
  // already have a result, as given by the cachedData (which is also the
495
604
  // initial value for the result state).
496
605
 
606
+ const maybeTrack = Object(react__WEBPACK_IMPORTED_MODULE_1__["useContext"])(_util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__[/* TrackerContext */ "b"]);
497
607
 
498
- const maybeTrack = Object(react__WEBPACK_IMPORTED_MODULE_1__["useContext"])(_util_request_tracking_js__WEBPACK_IMPORTED_MODULE_4__[/* TrackerContext */ "b"]);
499
-
500
- if (result == null && _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
501
- maybeTrack == null ? void 0 : maybeTrack(getMaybeInterceptedHandler(), options);
502
- } // We need to update our request when the handler changes or the key
503
- // to the options change, so we keep track of those.
504
- // However, even if we are hydrating from cache, we still need to make the
505
- // request at least once, so we do not initialize these references.
506
-
507
-
508
- const handlerRef = Object(react__WEBPACK_IMPORTED_MODULE_1__["useRef"])();
509
- const keyRef = Object(react__WEBPACK_IMPORTED_MODULE_1__["useRef"])();
510
- const interceptorRef = Object(react__WEBPACK_IMPORTED_MODULE_1__["useRef"])(); // This effect will ensure that we fulfill the request as desired.
511
-
512
- Object(react__WEBPACK_IMPORTED_MODULE_1__["useEffect"])(() => {
513
- // If we are server-side, then just skip the effect. We track requests
514
- // during SSR and fulfill them outside of the React render cycle.
515
- // NOTE: This shouldn't happen since effects would not run on the server
516
- // but let's be defensive - I think it makes the code clearer.
517
-
518
- /* istanbul ignore next */
519
- if (_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
520
- return;
521
- } // Update our refs to the current handler and key.
608
+ if (cachedResult == null && _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
609
+ maybeTrack == null ? void 0 : maybeTrack(requestId, handler, hydrate);
610
+ }
522
611
 
612
+ return cachedResult;
613
+ };
523
614
 
524
- handlerRef.current = handler;
525
- keyRef.current = handler.getKey(options);
526
- interceptorRef.current = interceptor; // If we're not hydrating a result, we want to make sure we set our
527
- // result to null so that we're in the loading state.
615
+ /***/ }),
616
+ /* 9 */
617
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
528
618
 
529
- if (cachedResult == null) {
530
- // Mark ourselves as loading.
531
- setResult(null);
532
- } // We aren't server-side, so let's make the request.
533
- // The request handler is in control of whether that request actually
534
- // happens or not.
619
+ "use strict";
620
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useRequestInterception; });
621
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
622
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
623
+ /* harmony import */ var _components_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7);
535
624
 
536
625
 
537
- let cancel = false;
538
- _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestFulfillment */ "a"].Default.fulfill(getMaybeInterceptedHandler(), options).then(updateEntry => {
539
- if (cancel) {
540
- return;
541
- }
542
626
 
543
- setResult(updateEntry);
544
- return;
545
- }).catch(e => {
546
- if (cancel) {
547
- return;
627
+ /**
628
+ * Allow request handling to be intercepted.
629
+ *
630
+ * Hook to take a uniquely identified request handler and return a
631
+ * method that will support request interception from the InterceptRequest
632
+ * component.
633
+ *
634
+ * If you want request interception to be supported with `useServerEffect` or
635
+ * any client-side effect that uses the handler, call this first to generate
636
+ * an intercepted handler, and then invoke `useServerEffect` (or other things)
637
+ * with that intercepted handler.
638
+ */
639
+ const useRequestInterception = (requestId, handler) => {
640
+ // Get the interceptors that have been registered.
641
+ const interceptors = react__WEBPACK_IMPORTED_MODULE_0__["useContext"](_components_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"]); // Now, we need to create a new handler that will check if the
642
+ // request is intercepted before ultimately calling the original handler
643
+ // if nothing intercepted it.
644
+ // We memoize this so that it only changes if something related to it
645
+ // changes.
646
+
647
+ const interceptedHandler = react__WEBPACK_IMPORTED_MODULE_0__["useMemo"](() => () => {
648
+ // Call the interceptors from closest to furthest.
649
+ // If one returns a non-null result, then we keep that.
650
+ const interceptResponse = interceptors.reduceRight((prev, interceptor) => {
651
+ if (prev != null) {
652
+ return prev;
548
653
  }
549
- /**
550
- * We should never get here as errors in fulfillment are part
551
- * of the `then`, but if we do.
552
- */
553
- // eslint-disable-next-line no-console
554
654
 
655
+ return interceptor(requestId);
656
+ }, null); // If nothing intercepted this request, invoke the original handler.
657
+ // NOTE: We can't guarantee all interceptors return the same type
658
+ // as our handler, so how can flow know? Let's just suppress that.
659
+ // $FlowFixMe[incompatible-return]
555
660
 
556
- console.error(`Unexpected error occurred during data fulfillment: ${e}`);
557
- setResult({
558
- data: null,
559
- error: typeof e === "string" ? e : e.message
560
- });
561
- return;
562
- });
563
- return () => {
564
- cancel = true;
565
- }; // - handler.getKey is a proxy for options
566
- // - We don't want to trigger on cachedResult changing, we're
567
- // just using that as a flag for render state if the other things
568
- // trigger this effect.
569
- // eslint-disable-next-line react-hooks/exhaustive-deps
570
- }, [handler, handler.getKey(options), interceptor]);
571
- return Object(_util_result_from_cache_entry_js__WEBPACK_IMPORTED_MODULE_5__[/* resultFromCacheEntry */ "a"])(result);
661
+ return interceptResponse != null ? interceptResponse : handler();
662
+ }, [handler, interceptors, requestId]);
663
+ return interceptedHandler;
572
664
  };
573
665
 
574
666
  /***/ }),
575
- /* 7 */
667
+ /* 10 */
576
668
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
577
669
 
578
670
  "use strict";
579
671
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RequestFulfillment; });
580
- /* harmony import */ var _response_cache_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3);
672
+ /* harmony import */ var _ssr_cache_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
581
673
 
582
674
 
583
675
  let _default;
@@ -594,23 +686,14 @@ class RequestFulfillment {
594
686
  constructor(responseCache = undefined) {
595
687
  this._requests = {};
596
688
 
597
- this._getHandlerSubcache = handler => {
598
- if (!this._requests[handler.type]) {
599
- this._requests[handler.type] = {};
600
- }
601
-
602
- return this._requests[handler.type];
603
- };
604
-
605
- this.fulfill = (handler, options) => {
606
- const handlerRequests = this._getHandlerSubcache(handler);
607
-
608
- const key = handler.getKey(options);
689
+ this.fulfill = (id, {
690
+ handler,
691
+ hydrate = true
692
+ }) => {
609
693
  /**
610
694
  * If we have an inflight request, we'll provide that.
611
695
  */
612
-
613
- const inflight = handlerRequests[key];
696
+ const inflight = this._requests[id];
614
697
 
615
698
  if (inflight) {
616
699
  return inflight;
@@ -626,43 +709,56 @@ class RequestFulfillment {
626
709
  } = this._responseCache;
627
710
 
628
711
  try {
629
- const request = handler.fulfillRequest(options).then(data => {
630
- delete handlerRequests[key];
712
+ const request = handler().then(data => {
713
+ delete this._requests[id];
714
+
715
+ if (data == null) {
716
+ // Request aborted. We won't cache this.
717
+ return null;
718
+ }
631
719
  /**
632
720
  * Let's cache the data!
633
721
  *
634
722
  * NOTE: This only caches when we're server side.
635
723
  */
636
724
 
637
- return cacheData(handler, options, data);
725
+
726
+ return cacheData(id, data, hydrate);
638
727
  }).catch(error => {
639
- delete handlerRequests[key];
728
+ delete this._requests[id];
640
729
  /**
641
730
  * Let's cache the error!
642
731
  *
643
732
  * NOTE: This only caches when we're server side.
644
733
  */
645
734
 
646
- return cacheError(handler, options, error);
735
+ return cacheError(id, error, hydrate);
647
736
  });
648
- handlerRequests[key] = request;
737
+ this._requests[id] = request;
649
738
  return request;
650
739
  } catch (e) {
651
740
  /**
652
741
  * In this case, we don't cache an inflight request, because there
653
742
  * really isn't one.
654
743
  */
655
- return Promise.resolve(cacheError(handler, options, e));
744
+ return Promise.resolve(cacheError(id, e, hydrate));
656
745
  }
657
746
  };
658
747
 
659
- this._responseCache = responseCache || _response_cache_js__WEBPACK_IMPORTED_MODULE_0__[/* ResponseCache */ "a"].Default;
748
+ this._responseCache = responseCache || _ssr_cache_js__WEBPACK_IMPORTED_MODULE_0__[/* SsrCache */ "a"].Default;
660
749
  }
750
+ /**
751
+ * Get a promise of a request for a given handler and options.
752
+ *
753
+ * This will return an inflight request if one exists, otherwise it will
754
+ * make a new request. Inflight requests are deleted once they resolve.
755
+ */
756
+
661
757
 
662
758
  }
663
759
 
664
760
  /***/ }),
665
- /* 8 */
761
+ /* 11 */
666
762
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
667
763
 
668
764
  "use strict";
@@ -673,211 +769,107 @@ class RequestFulfillment {
673
769
  const GqlRouterContext = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createContext"](null);
674
770
 
675
771
  /***/ }),
676
- /* 9 */
772
+ /* 12 */
677
773
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
678
774
 
679
775
  "use strict";
680
- /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return MemoryCache; });
681
- function deepClone(source) {
682
- /**
683
- * We want to deep clone the source cache to dodge mutations by external
684
- * references. So we serialize the source cache to JSON and parse it
685
- * back into a new object.
686
- *
687
- * NOTE: This doesn't work for get/set property accessors.
688
- */
689
- const serializedInitCache = JSON.stringify(source);
690
- const cloneInitCache = JSON.parse(serializedInitCache);
691
- return Object.freeze(cloneInitCache);
692
- }
693
- /**
694
- * INTERNAL USE ONLY
695
- *
696
- * Special case cache implementation for the memory cache.
697
- *
698
- * This is only used within our framework for SSR (see ./response-cache.js).
699
- */
700
-
701
-
702
- class MemoryCache {
703
- constructor(source = null) {
704
- this.store = (handler, options, entry) => {
705
- const requestType = handler.type;
706
- const frozenEntry = Object.freeze(entry); // Ensure we have a cache location for this handler type.
707
-
708
- this._cache[requestType] = this._cache[requestType] || {}; // Cache the data.
709
-
710
- const key = handler.getKey(options);
711
- this._cache[requestType][key] = frozenEntry;
712
- };
713
-
714
- this.retrieve = (handler, options) => {
715
- const requestType = handler.type; // Get the internal subcache for the handler.
716
-
717
- const handlerCache = this._cache[requestType];
718
-
719
- if (!handlerCache) {
720
- return null;
721
- } // Get the response.
722
-
723
-
724
- const key = handler.getKey(options);
725
- const internalEntry = handlerCache[key];
726
-
727
- if (internalEntry == null) {
728
- return null;
729
- }
730
-
731
- return internalEntry;
732
- };
733
-
734
- this.remove = (handler, options) => {
735
- const requestType = handler.type; // NOTE(somewhatabstract): We could invoke removeAll with a predicate
736
- // to match the key of the entry we're removing, but that's an
737
- // inefficient way to remove a single item, so let's not do that.
738
- // Get the internal subcache for the handler.
739
-
740
- const handlerCache = this._cache[requestType];
741
-
742
- if (!handlerCache) {
743
- return false;
744
- } // Get the entry.
745
-
746
-
747
- const key = handler.getKey(options);
748
- const internalEntry = handlerCache[key];
749
-
750
- if (internalEntry == null) {
751
- return false;
752
- } // Delete the entry.
753
-
754
-
755
- delete handlerCache[key];
756
- return true;
757
- };
758
-
759
- this.removeAll = (handler, predicate) => {
760
- const requestType = handler.type; // Get the internal subcache for the handler.
761
-
762
- const handlerCache = this._cache[requestType];
763
-
764
- if (!handlerCache) {
765
- return 0;
766
- }
767
-
768
- let removedCount = 0;
776
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return clearSharedCache; });
777
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return useSharedCache; });
778
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
779
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
780
+ /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1);
781
+ /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_1__);
782
+ /* harmony import */ var _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);
769
783
 
770
- if (typeof predicate === "function") {
771
- // Apply the predicate to what we have cached.
772
- for (const [key, entry] of Object.entries(handlerCache)) {
773
- if (predicate(key, entry)) {
774
- removedCount++;
775
- delete handlerCache[key];
776
- }
777
- }
778
- } else {
779
- // We're removing everything so delete the entire subcache.
780
- removedCount = Object.keys(handlerCache).length;
781
- delete this._cache[requestType];
782
- }
783
784
 
784
- return removedCount;
785
- };
786
785
 
787
- this.cloneData = () => {
788
- try {
789
- return deepClone(this._cache);
790
- } catch (e) {
791
- throw new Error(`An error occurred while trying to clone the cache: ${e}`);
792
- }
793
- };
794
786
 
795
- this._cache = {};
787
+ /**
788
+ * This is the cache.
789
+ * It's incredibly complex.
790
+ * Very in-memory. So cache. Such complex. Wow.
791
+ */
792
+ const cache = new _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_2__[/* ScopedInMemoryCache */ "a"]();
793
+ /**
794
+ * Clear the in-memory cache or a single scope within it.
795
+ */
796
796
 
797
- if (source != null) {
798
- try {
799
- /**
800
- * Object.assign only performs a shallow clone.
801
- * So we deep clone it and then assign the clone values to our
802
- * internal cache.
803
- */
804
- const cloneInitCache = deepClone(source);
805
- Object.assign(this._cache, cloneInitCache);
806
- } catch (e) {
807
- throw new Error(`An error occurred trying to initialize from a response cache snapshot: ${e}`);
808
- }
809
- }
797
+ const clearSharedCache = (scope = "") => {
798
+ // If we have a valid scope (empty string is falsy), then clear that scope.
799
+ if (scope && typeof scope === "string") {
800
+ cache.purgeScope(scope);
801
+ } else {
802
+ // Just reset the object. This should be sufficient.
803
+ cache.purgeAll();
810
804
  }
811
- /**
812
- * Indicate if this cache is being used or now.
813
- *
814
- * When the cache has entries, returns `true`; otherwise, returns `false`.
815
- */
816
-
805
+ };
806
+ /**
807
+ * Hook to retrieve data from and store data in an in-memory cache.
808
+ *
809
+ * @returns {[?ReadOnlyCacheValue, CacheValueFn]}
810
+ * Returns an array containing the current cache entry (or undefined), a
811
+ * function to set the cache entry (passing null or undefined to this function
812
+ * will delete the entry).
813
+ *
814
+ * To clear a single scope within the cache or the entire cache,
815
+ * the `clearScopedCache` export is available.
816
+ *
817
+ * NOTE: Unlike useState or useReducer, we don't automatically update folks
818
+ * if the value they reference changes. We might add it later (if we need to),
819
+ * but the likelihood here is that things won't be changing in this cache in a
820
+ * way where we would need that. If we do (and likely only in specific
821
+ * circumstances), we should consider adding a simple boolean useState that can
822
+ * be toggled to cause a rerender whenever the referenced cached data changes
823
+ * so that callers can re-render on cache changes. However, we should make
824
+ * sure this toggling is optional - or we could use a callback argument, to
825
+ * achieve this on an as-needed basis.
826
+ */
817
827
 
818
- get inUse() {
819
- return Object.keys(this._cache).length > 0;
828
+ const useSharedCache = (id, scope, initialValue) => {
829
+ // Verify arguments.
830
+ if (!id || typeof id !== "string") {
831
+ throw new _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_1__["KindError"]("id must be a non-empty string", _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_1__["Errors"].InvalidInput);
820
832
  }
821
833
 
822
- }
834
+ if (!scope || typeof scope !== "string") {
835
+ throw new _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_1__["KindError"]("scope must be a non-empty string", _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_1__["Errors"].InvalidInput);
836
+ } // Memoize our APIs.
837
+ // This one allows callers to set or replace the cached value.
823
838
 
824
- /***/ }),
825
- /* 10 */
826
- /***/ (function(module, exports) {
827
839
 
828
- module.exports = require("@khanacademy/wonder-stuff-core");
840
+ const cacheValue = react__WEBPACK_IMPORTED_MODULE_0__["useMemo"](() => value => value == null ? cache.purge(scope, id) : cache.set(scope, id, value), [id, scope]); // We don't memo-ize the current value, just in case the cache was updated
841
+ // since our last run through. Also, our cache does not know what type it
842
+ // stores, so we have to cast it to the type we're exporting. This is a
843
+ // dev time courtesy, rather than a runtime thing.
844
+ // $FlowIgnore[incompatible-type]
829
845
 
830
- /***/ }),
831
- /* 11 */
832
- /***/ (function(module, __webpack_exports__, __webpack_require__) {
846
+ let currentValue = cache.get(scope, id); // If we have an initial value, we need to add it to the cache
847
+ // and use it as our current value.
833
848
 
834
- "use strict";
835
- /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RequestHandler; });
836
- /**
837
- * Base implementation for creating a request handler.
838
- *
839
- * Provides a base implementation of the `IRequestHandler` base class for
840
- * use with the Wonder Blocks Data framework.
841
- */
842
- class RequestHandler {
843
- constructor(type, hydrate = true) {
844
- this._type = type;
845
- this._hydrate = !!hydrate;
846
- }
849
+ if (currentValue == null && initialValue !== undefined) {
850
+ // Get the initial value.
851
+ const value = typeof initialValue === "function" ? initialValue() : initialValue; // Update the cache.
847
852
 
848
- get type() {
849
- return this._type;
850
- }
851
-
852
- get hydrate() {
853
- return this._hydrate;
854
- }
853
+ cacheValue(value); // Make sure we return this value as our current value.
855
854
 
856
- getKey(options) {
857
- try {
858
- return options === undefined ? "undefined" : JSON.stringify(options);
859
- } catch (e) {
860
- throw new Error(`Failed to auto-generate key: ${e}`);
861
- }
862
- }
855
+ currentValue = value;
856
+ } // Now we have everything, let's return it.
863
857
 
864
- fulfillRequest(options) {
865
- throw new Error("Not implemented");
866
- }
867
858
 
868
- }
859
+ return [currentValue, cacheValue];
860
+ };
869
861
 
870
862
  /***/ }),
871
- /* 12 */
863
+ /* 13 */
872
864
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
873
865
 
874
866
  "use strict";
875
867
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return TrackData; });
876
868
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
877
869
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
878
- /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
870
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
879
871
  /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__);
880
- /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
872
+ /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);
881
873
 
882
874
 
883
875
 
@@ -899,13 +891,22 @@ class TrackData extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
899
891
  }
900
892
 
901
893
  /***/ }),
902
- /* 13 */
894
+ /* 14 */
903
895
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
904
896
 
905
897
  "use strict";
906
898
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
907
899
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
908
- /* harmony import */ var _hooks_use_data_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6);
900
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
901
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__);
902
+ /* harmony import */ var _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10);
903
+ /* harmony import */ var _hooks_use_server_effect_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8);
904
+ /* harmony import */ var _hooks_use_request_interception_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9);
905
+ /* harmony import */ var _util_result_from_cache_response_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(18);
906
+
907
+
908
+
909
+
909
910
 
910
911
 
911
912
 
@@ -914,68 +915,145 @@ class TrackData extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
914
915
  * requirements can be placed in a React application in a manner that will
915
916
  * support server-side rendering and efficient caching.
916
917
  */
917
- const Data = props => {
918
- const data = Object(_hooks_use_data_js__WEBPACK_IMPORTED_MODULE_1__[/* useData */ "a"])(props.handler, props.options);
919
- return props.children(data);
918
+ const Data = ({
919
+ requestId,
920
+ handler,
921
+ children,
922
+ hydrate,
923
+ showOldDataWhileLoading,
924
+ alwaysRequestOnHydration
925
+ }) => {
926
+ const interceptedHandler = Object(_hooks_use_request_interception_js__WEBPACK_IMPORTED_MODULE_4__[/* useRequestInterception */ "a"])(requestId, handler);
927
+ const hydrateResult = Object(_hooks_use_server_effect_js__WEBPACK_IMPORTED_MODULE_3__[/* useServerEffect */ "a"])(requestId, interceptedHandler, hydrate);
928
+ const [currentResult, setResult] = react__WEBPACK_IMPORTED_MODULE_0__["useState"](hydrateResult); // Here we make sure the request still occurs client-side as needed.
929
+ // This is for legacy usage that expects this. Eventually we will want
930
+ // to deprecate.
931
+
932
+ react__WEBPACK_IMPORTED_MODULE_0__["useEffect"](() => {
933
+ // This is here until I can do a better documentation example for
934
+ // the TrackData docs.
935
+ // istanbul ignore next
936
+ if (_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__["Server"].isServerSide()) {
937
+ return;
938
+ } // We don't bother with this if we have hydration data and we're not
939
+ // forcing a request on hydration.
940
+ // We don't care if these things change after the first render,
941
+ // so we don't want them in the inputs array.
942
+
943
+
944
+ if (!alwaysRequestOnHydration && (hydrateResult == null ? void 0 : hydrateResult.data) != null) {
945
+ return;
946
+ } // If we're not hydrating a result and we're not going to render
947
+ // with old data until we're loaded, we want to make sure we set our
948
+ // result to null so that we're in the loading state.
949
+
950
+
951
+ if (!showOldDataWhileLoading) {
952
+ // Mark ourselves as loading.
953
+ setResult(null);
954
+ } // We aren't server-side, so let's make the request.
955
+ // We don't need to use our built-in request fulfillment here if we
956
+ // don't want, but it does mean we'll share inflight requests for the
957
+ // same ID and the result will be in the same format as the
958
+ // hydrated value.
959
+
960
+
961
+ let cancel = false;
962
+ _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestFulfillment */ "a"].Default.fulfill(requestId, {
963
+ handler: interceptedHandler
964
+ }).then(result => {
965
+ if (cancel) {
966
+ return;
967
+ }
968
+
969
+ setResult(result);
970
+ return;
971
+ }).catch(e => {
972
+ if (cancel) {
973
+ return;
974
+ }
975
+ /**
976
+ * We should never get here as errors in fulfillment are part
977
+ * of the `then`, but if we do.
978
+ */
979
+ // eslint-disable-next-line no-console
980
+
981
+
982
+ console.error(`Unexpected error occurred during data fulfillment: ${e}`);
983
+ setResult({
984
+ error: typeof e === "string" ? e : e.message
985
+ });
986
+ return;
987
+ });
988
+ return () => {
989
+ cancel = true;
990
+ }; // If the handler changes, we don't care. The ID is what indicates
991
+ // the request that should be made and folks shouldn't be changing the
992
+ // handler without changing the ID as well.
993
+ // In addition, we don't want to include hydrateResult nor
994
+ // alwaysRequestOnHydration as them changinng after the first pass
995
+ // is irrelevant.
996
+ // Finally, we don't want to include showOldDataWhileLoading as that
997
+ // changing on its own is also not relevant. It only matters if the
998
+ // request itself changes. All of which is to say that we only
999
+ // run this effect for the ID changing.
1000
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1001
+ }, [requestId]);
1002
+ return children(Object(_util_result_from_cache_response_js__WEBPACK_IMPORTED_MODULE_5__[/* resultFromCachedResponse */ "a"])(currentResult));
920
1003
  };
921
1004
 
922
1005
  /* harmony default export */ __webpack_exports__["a"] = (Data);
923
1006
 
924
1007
  /***/ }),
925
- /* 14 */
1008
+ /* 15 */
926
1009
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
927
1010
 
928
1011
  "use strict";
929
- /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return InterceptData; });
930
1012
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
931
1013
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
932
- /* harmony import */ var _intercept_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
1014
+ /* harmony import */ var _intercept_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7);
933
1015
 
934
1016
 
935
1017
 
936
1018
  /**
937
- * This component provides a mechanism to intercept the data requests for the
938
- * type of a given handler and provide alternative results. This is mostly
939
- * useful for testing.
1019
+ * This component provides a mechanism to intercept data requests.
1020
+ * This is for use in testing.
940
1021
  *
941
1022
  * This component is not recommended for use in production code as it
942
1023
  * can prevent predictable functioning of the Wonder Blocks Data framework.
943
1024
  * One possible side-effect is that inflight requests from the interceptor could
944
- * be picked up by `Data` component requests of the same handler type from
945
- * outside the children of this component.
1025
+ * be picked up by `Data` component requests from outside the children of this
1026
+ * component.
946
1027
  *
947
- * These components do not chain. If a different `InterceptData` instance is
948
- * rendered within this one that intercepts the same handler type, then that
949
- * new instance will replace this interceptor for its children. All methods
950
- * will be replaced.
1028
+ * Interceptions within the same component tree are chained such that the
1029
+ * interceptor closest to the intercepted request is called first, and the
1030
+ * furthest interceptor is called last.
951
1031
  */
952
- class InterceptData extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
953
- render() {
954
- return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"].Consumer, null, value => {
955
- const handlerType = this.props.handler.type;
956
- const interceptor = { ...value[handlerType],
957
- fulfillRequest: this.props.fulfillRequest
958
- };
959
- const newValue = { ...value,
960
- [handlerType]: interceptor
961
- };
962
- return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"].Provider, {
963
- value: newValue
964
- }, this.props.children);
965
- });
966
- }
1032
+ const InterceptRequests = ({
1033
+ interceptor,
1034
+ children
1035
+ }) => {
1036
+ const interceptors = react__WEBPACK_IMPORTED_MODULE_0__["useContext"](_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"]);
1037
+ const updatedInterceptors = react__WEBPACK_IMPORTED_MODULE_0__["useMemo"]( // We could build this in reverse order so that our hook that does
1038
+ // the interception didn't have to use reduceRight, but I think it
1039
+ // is easier to think about if we do this in component tree order.
1040
+ () => [].concat(interceptors, [interceptor]), [interceptors, interceptor]);
1041
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"].Provider, {
1042
+ value: updatedInterceptors
1043
+ }, children);
1044
+ };
967
1045
 
968
- }
1046
+ /* harmony default export */ __webpack_exports__["a"] = (InterceptRequests);
969
1047
 
970
1048
  /***/ }),
971
- /* 15 */
1049
+ /* 16 */
972
1050
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
973
1051
 
974
1052
  "use strict";
975
1053
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return GqlRouter; });
976
1054
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
977
1055
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
978
- /* harmony import */ var _util_gql_router_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8);
1056
+ /* harmony import */ var _util_gql_router_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11);
979
1057
 
980
1058
 
981
1059
 
@@ -1012,16 +1090,16 @@ const GqlRouter = ({
1012
1090
  };
1013
1091
 
1014
1092
  /***/ }),
1015
- /* 16 */
1093
+ /* 17 */
1016
1094
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1017
1095
 
1018
1096
  "use strict";
1019
1097
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useGql; });
1020
1098
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
1021
1099
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
1022
- /* harmony import */ var _util_gql_router_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8);
1023
- /* harmony import */ var _util_get_gql_data_from_response_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(18);
1024
- /* harmony import */ var _util_gql_error_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1);
1100
+ /* harmony import */ var _util_gql_router_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11);
1101
+ /* harmony import */ var _util_get_gql_data_from_response_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(19);
1102
+ /* harmony import */ var _util_gql_error_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2);
1025
1103
 
1026
1104
 
1027
1105
 
@@ -1032,6 +1110,10 @@ const GqlRouter = ({
1032
1110
  *
1033
1111
  * The fetch function will resolve null if the request was aborted, otherwise
1034
1112
  * it will resolve the data returned by the GraphQL server.
1113
+ *
1114
+ * Context is merged with the default context provided to the GqlRouter.
1115
+ * Values in the partial context given to the returned fetch function will
1116
+ * only be included if they have a value other than undefined.
1035
1117
  */
1036
1118
  const useGql = () => {
1037
1119
  // This hook only works if the `GqlRouter` has been used to setup context.
@@ -1053,12 +1135,23 @@ const useGql = () => {
1053
1135
  const gqlFetch = Object(react__WEBPACK_IMPORTED_MODULE_0__["useMemo"])(() => (operation, options = Object.freeze({})) => {
1054
1136
  const {
1055
1137
  variables,
1056
- context
1057
- } = options; // Invoke the fetch and extract the data.
1138
+ context = {}
1139
+ } = options; // Let's merge the partial context of the fetch with the
1140
+ // default context. We deliberately don't spread because
1141
+ // spreading would overwrite default context values with
1142
+ // undefined if the partial context includes a value explicitly
1143
+ // set to undefined. Instead, we use a map/reduce of keys.
1144
+
1145
+ const mergedContext = Object.keys(context).reduce((acc, key) => {
1146
+ if (context[key] !== undefined) {
1147
+ acc[key] = context[key];
1148
+ }
1058
1149
 
1059
- return fetch(operation, variables, { ...defaultContext,
1060
- ...context
1061
- }).then(_util_get_gql_data_from_response_js__WEBPACK_IMPORTED_MODULE_2__[/* getGqlDataFromResponse */ "a"], error => {
1150
+ return acc;
1151
+ }, { ...defaultContext
1152
+ }); // Invoke the fetch and extract the data.
1153
+
1154
+ return fetch(operation, variables, mergedContext).then(_util_get_gql_data_from_response_js__WEBPACK_IMPORTED_MODULE_2__[/* getGqlDataFromResponse */ "a"], error => {
1062
1155
  // Return null if the request was aborted.
1063
1156
  // The only way to detect this reliably, it seems, is to
1064
1157
  // check the error name and see if it's "AbortError" (this
@@ -1076,15 +1169,15 @@ const useGql = () => {
1076
1169
  };
1077
1170
 
1078
1171
  /***/ }),
1079
- /* 17 */
1172
+ /* 18 */
1080
1173
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1081
1174
 
1082
1175
  "use strict";
1083
- /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return resultFromCacheEntry; });
1176
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return resultFromCachedResponse; });
1084
1177
  /**
1085
1178
  * Turns a cache entry into a stateful result.
1086
1179
  */
1087
- const resultFromCacheEntry = cacheEntry => {
1180
+ const resultFromCachedResponse = cacheEntry => {
1088
1181
  // No cache entry means we didn't load one yet.
1089
1182
  if (cacheEntry == null) {
1090
1183
  return {
@@ -1097,34 +1190,32 @@ const resultFromCacheEntry = cacheEntry => {
1097
1190
  error
1098
1191
  } = cacheEntry;
1099
1192
 
1100
- if (data != null) {
1193
+ if (error != null) {
1101
1194
  return {
1102
- status: "success",
1103
- data
1195
+ status: "error",
1196
+ error
1104
1197
  };
1105
1198
  }
1106
1199
 
1107
- if (error == null) {
1108
- // We should never get here ever.
1200
+ if (data != null) {
1109
1201
  return {
1110
- status: "error",
1111
- error: "Loaded result has invalid state where data and error are missing"
1202
+ status: "success",
1203
+ data
1112
1204
  };
1113
1205
  }
1114
1206
 
1115
1207
  return {
1116
- status: "error",
1117
- error
1208
+ status: "aborted"
1118
1209
  };
1119
1210
  };
1120
1211
 
1121
1212
  /***/ }),
1122
- /* 18 */
1213
+ /* 19 */
1123
1214
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1124
1215
 
1125
1216
  "use strict";
1126
1217
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return getGqlDataFromResponse; });
1127
- /* harmony import */ var _gql_error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
1218
+ /* harmony import */ var _gql_error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
1128
1219
 
1129
1220
  /**
1130
1221
  * Validate a GQL operation response and extract the data.
@@ -1187,7 +1278,7 @@ const getGqlDataFromResponse = async response => {
1187
1278
  };
1188
1279
 
1189
1280
  /***/ }),
1190
- /* 19 */
1281
+ /* 20 */
1191
1282
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1192
1283
 
1193
1284
  "use strict";
@@ -1197,40 +1288,48 @@ __webpack_require__.r(__webpack_exports__);
1197
1288
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "hasUnfulfilledRequests", function() { return hasUnfulfilledRequests; });
1198
1289
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "removeFromCache", function() { return removeFromCache; });
1199
1290
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "removeAllFromCache", function() { return removeAllFromCache; });
1200
- /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
1291
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3);
1201
1292
  /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__);
1202
- /* harmony import */ var _util_response_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
1203
- /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
1204
- /* harmony import */ var _util_request_handler_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11);
1205
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "RequestHandler", function() { return _util_request_handler_js__WEBPACK_IMPORTED_MODULE_3__["a"]; });
1293
+ /* harmony import */ var _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
1294
+ /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);
1295
+ /* harmony import */ var _components_track_data_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(13);
1296
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "TrackData", function() { return _components_track_data_js__WEBPACK_IMPORTED_MODULE_3__["a"]; });
1206
1297
 
1207
- /* harmony import */ var _components_track_data_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(12);
1208
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "TrackData", function() { return _components_track_data_js__WEBPACK_IMPORTED_MODULE_4__["a"]; });
1298
+ /* harmony import */ var _components_data_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(14);
1299
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Data", function() { return _components_data_js__WEBPACK_IMPORTED_MODULE_4__["a"]; });
1209
1300
 
1210
- /* harmony import */ var _components_data_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(13);
1211
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Data", function() { return _components_data_js__WEBPACK_IMPORTED_MODULE_5__["a"]; });
1301
+ /* harmony import */ var _components_intercept_requests_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(15);
1302
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "InterceptRequests", function() { return _components_intercept_requests_js__WEBPACK_IMPORTED_MODULE_5__["a"]; });
1212
1303
 
1213
- /* harmony import */ var _components_intercept_data_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(14);
1214
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "InterceptData", function() { return _components_intercept_data_js__WEBPACK_IMPORTED_MODULE_6__["a"]; });
1304
+ /* harmony import */ var _hooks_use_server_effect_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8);
1305
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useServerEffect", function() { return _hooks_use_server_effect_js__WEBPACK_IMPORTED_MODULE_6__["a"]; });
1215
1306
 
1216
- /* harmony import */ var _hooks_use_data_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(6);
1217
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useData", function() { return _hooks_use_data_js__WEBPACK_IMPORTED_MODULE_7__["a"]; });
1307
+ /* harmony import */ var _hooks_use_request_interception_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(9);
1308
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useRequestInterception", function() { return _hooks_use_request_interception_js__WEBPACK_IMPORTED_MODULE_7__["a"]; });
1218
1309
 
1219
- /* harmony import */ var _components_gql_router_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(15);
1220
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlRouter", function() { return _components_gql_router_js__WEBPACK_IMPORTED_MODULE_8__["a"]; });
1310
+ /* harmony import */ var _hooks_use_shared_cache_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(12);
1311
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useSharedCache", function() { return _hooks_use_shared_cache_js__WEBPACK_IMPORTED_MODULE_8__["b"]; });
1221
1312
 
1222
- /* harmony import */ var _hooks_use_gql_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(16);
1223
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useGql", function() { return _hooks_use_gql_js__WEBPACK_IMPORTED_MODULE_9__["a"]; });
1313
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "clearSharedCache", function() { return _hooks_use_shared_cache_js__WEBPACK_IMPORTED_MODULE_8__["a"]; });
1224
1314
 
1225
- /* harmony import */ var _util_gql_error_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(1);
1226
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlErrors", function() { return _util_gql_error_js__WEBPACK_IMPORTED_MODULE_10__["b"]; });
1315
+ /* harmony import */ var _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(5);
1316
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ScopedInMemoryCache", function() { return _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_9__["a"]; });
1227
1317
 
1228
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlError", function() { return _util_gql_error_js__WEBPACK_IMPORTED_MODULE_10__["a"]; });
1318
+ /* harmony import */ var _components_gql_router_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(16);
1319
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlRouter", function() { return _components_gql_router_js__WEBPACK_IMPORTED_MODULE_10__["a"]; });
1229
1320
 
1321
+ /* harmony import */ var _hooks_use_gql_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(17);
1322
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useGql", function() { return _hooks_use_gql_js__WEBPACK_IMPORTED_MODULE_11__["a"]; });
1230
1323
 
1324
+ /* harmony import */ var _util_gql_error_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(2);
1325
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlErrors", function() { return _util_gql_error_js__WEBPACK_IMPORTED_MODULE_12__["b"]; });
1231
1326
 
1327
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlError", function() { return _util_gql_error_js__WEBPACK_IMPORTED_MODULE_12__["a"]; });
1232
1328
 
1233
- const initializeCache = source => _util_response_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ResponseCache */ "a"].Default.initialize(source);
1329
+
1330
+
1331
+
1332
+ const initializeCache = source => _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* SsrCache */ "a"].Default.initialize(source);
1234
1333
  const fulfillAllDataRequests = () => {
1235
1334
  if (!_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
1236
1335
  return Promise.reject(new Error("Data requests are not tracked when client-side"));
@@ -1245,8 +1344,10 @@ const hasUnfulfilledRequests = () => {
1245
1344
 
1246
1345
  return _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestTracker */ "a"].Default.hasUnfulfilledRequests;
1247
1346
  };
1248
- const removeFromCache = (handler, options) => _util_response_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ResponseCache */ "a"].Default.remove(handler, options);
1249
- const removeAllFromCache = (handler, predicate) => _util_response_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ResponseCache */ "a"].Default.removeAll(handler, predicate);
1347
+ const removeFromCache = id => _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* SsrCache */ "a"].Default.remove(id);
1348
+ const removeAllFromCache = predicate => _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* SsrCache */ "a"].Default.removeAll(predicate);
1349
+
1350
+
1250
1351
 
1251
1352
 
1252
1353