@khanacademy/wonder-blocks-data 2.3.1 → 3.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 (41) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/es/index.js +212 -446
  3. package/dist/index.js +553 -729
  4. package/docs.md +19 -13
  5. package/package.json +6 -7
  6. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +40 -160
  7. package/src/__tests__/generated-snapshot.test.js +15 -195
  8. package/src/components/__tests__/data.test.js +159 -965
  9. package/src/components/__tests__/intercept-data.test.js +9 -66
  10. package/src/components/__tests__/track-data.test.js +6 -5
  11. package/src/components/data.js +9 -119
  12. package/src/components/data.md +38 -60
  13. package/src/components/intercept-context.js +2 -3
  14. package/src/components/intercept-data.js +2 -34
  15. package/src/components/intercept-data.md +7 -105
  16. package/src/hooks/__tests__/use-data.test.js +790 -0
  17. package/src/hooks/use-data.js +138 -0
  18. package/src/index.js +1 -3
  19. package/src/util/__tests__/memory-cache.test.js +134 -35
  20. package/src/util/__tests__/request-fulfillment.test.js +21 -36
  21. package/src/util/__tests__/request-handler.test.js +30 -30
  22. package/src/util/__tests__/request-tracking.test.js +29 -30
  23. package/src/util/__tests__/response-cache.test.js +521 -561
  24. package/src/util/__tests__/result-from-cache-entry.test.js +68 -0
  25. package/src/util/memory-cache.js +20 -15
  26. package/src/util/request-fulfillment.js +4 -0
  27. package/src/util/request-handler.js +4 -28
  28. package/src/util/request-handler.md +0 -32
  29. package/src/util/request-tracking.js +2 -3
  30. package/src/util/response-cache.js +50 -110
  31. package/src/util/result-from-cache-entry.js +38 -0
  32. package/src/util/types.js +14 -35
  33. package/LICENSE +0 -21
  34. package/src/components/__tests__/intercept-cache.test.js +0 -124
  35. package/src/components/__tests__/internal-data.test.js +0 -1030
  36. package/src/components/intercept-cache.js +0 -79
  37. package/src/components/intercept-cache.md +0 -103
  38. package/src/components/internal-data.js +0 -219
  39. package/src/util/__tests__/no-cache.test.js +0 -112
  40. package/src/util/no-cache.js +0 -66
  41. package/src/util/no-cache.md +0 -66
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 = 3);
85
+ /******/ return __webpack_require__(__webpack_require__.s = 13);
86
86
  /******/ })
87
87
  /************************************************************************/
88
88
  /******/ ([
@@ -99,225 +99,13 @@ module.exports = require("@khanacademy/wonder-blocks-core");
99
99
 
100
100
  /***/ }),
101
101
  /* 2 */
102
- /***/ (function(module, exports) {
103
-
104
- function _extends() {
105
- module.exports = _extends = Object.assign || function (target) {
106
- for (var i = 1; i < arguments.length; i++) {
107
- var source = arguments[i];
108
-
109
- for (var key in source) {
110
- if (Object.prototype.hasOwnProperty.call(source, key)) {
111
- target[key] = source[key];
112
- }
113
- }
114
- }
115
-
116
- return target;
117
- };
118
-
119
- module.exports["default"] = module.exports, module.exports.__esModule = true;
120
- return _extends.apply(this, arguments);
121
- }
122
-
123
- module.exports = _extends;
124
- module.exports["default"] = module.exports, module.exports.__esModule = true;
125
-
126
- /***/ }),
127
- /* 3 */
128
102
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
129
103
 
130
104
  "use strict";
131
- // ESM COMPAT FLAG
132
- __webpack_require__.r(__webpack_exports__);
133
-
134
- // EXPORTS
135
- __webpack_require__.d(__webpack_exports__, "initializeCache", function() { return /* binding */ initializeCache; });
136
- __webpack_require__.d(__webpack_exports__, "fulfillAllDataRequests", function() { return /* binding */ fulfillAllDataRequests; });
137
- __webpack_require__.d(__webpack_exports__, "hasUnfulfilledRequests", function() { return /* binding */ hasUnfulfilledRequests; });
138
- __webpack_require__.d(__webpack_exports__, "removeFromCache", function() { return /* binding */ removeFromCache; });
139
- __webpack_require__.d(__webpack_exports__, "removeAllFromCache", function() { return /* binding */ removeAllFromCache; });
140
- __webpack_require__.d(__webpack_exports__, "RequestHandler", function() { return /* reexport */ RequestHandler; });
141
- __webpack_require__.d(__webpack_exports__, "TrackData", function() { return /* reexport */ track_data_TrackData; });
142
- __webpack_require__.d(__webpack_exports__, "Data", function() { return /* reexport */ data_Data; });
143
- __webpack_require__.d(__webpack_exports__, "InterceptData", function() { return /* reexport */ intercept_data_InterceptData; });
144
- __webpack_require__.d(__webpack_exports__, "InterceptCache", function() { return /* reexport */ intercept_cache_InterceptCache; });
145
- __webpack_require__.d(__webpack_exports__, "NoCache", function() { return /* reexport */ NoCache; });
146
-
147
- // EXTERNAL MODULE: external "@khanacademy/wonder-blocks-core"
148
- var wonder_blocks_core_ = __webpack_require__(1);
149
-
150
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/util/memory-cache.js
151
- function deepClone(source) {
152
- /**
153
- * We want to deep clone the source cache to dodge mutations by external
154
- * references. So we serialize the source cache to JSON and parse it
155
- * back into a new object.
156
- *
157
- * NOTE: This doesn't work for get/set property accessors.
158
- */
159
- const serializedInitCache = JSON.stringify(source);
160
- const cloneInitCache = JSON.parse(serializedInitCache);
161
- return Object.freeze(cloneInitCache);
162
- }
163
- /**
164
- * INTERNAL USE ONLY
165
- *
166
- * Special case cache implementation for the memory cache.
167
- *
168
- * This is only used within our framework. Handlers don't need to
169
- * provide this as a custom cache as the framework will default to this in the
170
- * absence of a custom cache. We use this for SSR too (see ./response-cache.js).
171
- */
172
-
173
-
174
- class MemoryCache {
175
- constructor(source = null) {
176
- this.store = (handler, options, entry) => {
177
- const requestType = handler.type;
178
- const frozenEntry = Object.isFrozen(entry) ? entry : Object.freeze(entry); // Ensure we have a cache location for this handler type.
179
-
180
- this._cache[requestType] = this._cache[requestType] || {}; // Cache the data.
181
-
182
- const key = handler.getKey(options);
183
- this._cache[requestType][key] = frozenEntry;
184
- };
185
-
186
- this.retrieve = (handler, options) => {
187
- const requestType = handler.type; // Get the internal subcache for the handler.
188
-
189
- const handlerCache = this._cache[requestType];
190
-
191
- if (!handlerCache) {
192
- return null;
193
- } // Get the response.
194
-
195
-
196
- const key = handler.getKey(options);
197
- const internalEntry = handlerCache[key];
198
-
199
- if (internalEntry == null) {
200
- return null;
201
- }
202
-
203
- return internalEntry;
204
- };
205
-
206
- this.remove = (handler, options) => {
207
- const requestType = handler.type; // NOTE(somewhatabstract): We could invoke removeAll with a predicate
208
- // to match the key of the entry we're removing, but that's an
209
- // inefficient way to remove a single item, so let's not do that.
210
- // Get the internal subcache for the handler.
211
-
212
- const handlerCache = this._cache[requestType];
213
-
214
- if (!handlerCache) {
215
- return false;
216
- } // Get the entry.
217
-
218
-
219
- const key = handler.getKey(options);
220
- const internalEntry = handlerCache[key];
221
-
222
- if (internalEntry == null) {
223
- return false;
224
- } // Delete the entry.
225
-
226
-
227
- delete handlerCache[key];
228
- return true;
229
- };
230
-
231
- this.removeAll = (handler, predicate) => {
232
- const requestType = handler.type; // Get the internal subcache for the handler.
233
-
234
- const handlerCache = this._cache[requestType];
235
-
236
- if (!handlerCache) {
237
- return 0;
238
- } // Apply the predicate to what we have cached.
239
-
240
-
241
- let removedCount = 0;
242
-
243
- for (const [key, entry] of Object.entries(handlerCache)) {
244
- if (typeof predicate !== "function" || predicate(key, entry)) {
245
- removedCount++;
246
- delete handlerCache[key];
247
- }
248
- }
249
-
250
- return removedCount;
251
- };
252
-
253
- this.cloneData = () => {
254
- try {
255
- return deepClone(this._cache);
256
- } catch (e) {
257
- throw new Error(`An error occurred while trying to clone the cache: ${e}`);
258
- }
259
- };
260
-
261
- this._cache = {};
262
-
263
- if (source != null) {
264
- try {
265
- /**
266
- * Object.assign only performs a shallow clone.
267
- * So we deep clone it and then assign the clone values to our
268
- * internal cache.
269
- */
270
- const cloneInitCache = deepClone(source);
271
- Object.assign(this._cache, cloneInitCache);
272
- } catch (e) {
273
- throw new Error(`An error occurred trying to initialize from a response cache snapshot: ${e}`);
274
- }
275
- }
276
- }
277
-
278
- get inUse() {
279
- return Object.keys(this._cache).length > 0;
280
- }
281
-
282
- }
283
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/util/no-cache.js
284
- let defaultInstance = null;
285
- /**
286
- * This is a cache implementation to use when no caching is wanted.
287
- *
288
- * Use this with your request handler if you want to support server-side
289
- * rendering of your data requests, but want to ensure data is never cached
290
- * on the client-side.
291
- *
292
- * This is better than having `shouldRefreshCache` always return `true` in the
293
- * handler as this ensures that cache space and memory are never used for the
294
- * requested data after hydration has finished.
295
- */
296
-
297
- class NoCache {
298
- constructor() {
299
- this.store = (handler, options, entry) => {
300
- /* empty */
301
- };
302
-
303
- this.retrieve = (handler, options) => null;
304
-
305
- this.remove = (handler, options) => false;
306
-
307
- this.removeAll = (handler, predicate) => 0;
308
- }
309
-
310
- static get Default() {
311
- if (defaultInstance == null) {
312
- defaultInstance = new NoCache();
313
- }
314
-
315
- return defaultInstance;
316
- }
317
-
318
- }
319
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/util/response-cache.js
320
-
105
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return ResponseCache; });
106
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
107
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__);
108
+ /* harmony import */ var _memory_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7);
321
109
 
322
110
 
323
111
 
@@ -333,33 +121,31 @@ let _default;
333
121
  */
334
122
 
335
123
 
336
- class response_cache_ResponseCache {
124
+ class ResponseCache {
337
125
  static get Default() {
338
126
  if (!_default) {
339
- _default = new response_cache_ResponseCache();
127
+ _default = new ResponseCache();
340
128
  }
341
129
 
342
130
  return _default;
343
131
  }
344
132
 
345
- constructor(memoryCache = null, ssrOnlyCache = null) {
133
+ constructor(hydrationCache = null, ssrOnlyCache = null) {
346
134
  this.initialize = source => {
347
- if (this._hydrationAndDefaultCache.inUse) {
135
+ if (this._hydrationCache.inUse) {
348
136
  throw new Error("Cannot initialize data response cache more than once");
349
137
  }
350
138
 
351
139
  try {
352
- this._hydrationAndDefaultCache = new MemoryCache(source);
140
+ this._hydrationCache = new _memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"](source);
353
141
  } catch (e) {
354
142
  throw new Error(`An error occurred trying to initialize the data response cache: ${e}`);
355
143
  }
356
144
  };
357
145
 
358
- this.cacheData = (handler, options, data) => {
359
- return this._setCacheEntry(handler, options, {
360
- data
361
- });
362
- };
146
+ this.cacheData = (handler, options, data) => this._setCacheEntry(handler, options, {
147
+ data
148
+ });
363
149
 
364
150
  this.cacheError = (handler, options, error) => {
365
151
  const errorMessage = typeof error === "string" ? error : error.message;
@@ -369,110 +155,64 @@ class response_cache_ResponseCache {
369
155
  };
370
156
 
371
157
  this.getEntry = (handler, options) => {
372
- // If we're not server-side, and the handler has a custom cache
373
- // let's try to use it.
374
- if (this._ssrOnlyCache == null && handler.cache != null) {
375
- const entry = handler.cache.retrieve(handler, options);
376
-
377
- if (entry != null) {
378
- // Custom cache has an entry, so use it.
379
- return entry;
380
- }
381
- } // Get the internal entry for the handler.
382
- // This allows us to use our hydrated cache during hydration.
383
- // If we just returned null when the custom cache didn't have it,
384
- // we would never hydrate properly.
385
-
386
-
387
- const internalEntry = this._defaultCache(handler).retrieve(handler, options); // If we are not server-side and we hydrated something that the custom
388
- // cache didn't have, we need to make sure the custom cache contains
389
- // that value.
390
-
391
-
392
- if (this._ssrOnlyCache == null && handler.cache != null && internalEntry != null) {
393
- // Yes, if this throws, we will have a problem. We want that.
394
- // Bad cache implementations should be overt.
395
- handler.cache.store(handler, options, internalEntry); // We now delete this from our in-memory cache as we don't need it.
158
+ // Get the cached entry for this value.
159
+ // If the handler wants WB Data to hydrate (i.e. handler.hydrate is
160
+ // true), we use our hydration cache. Otherwise, if we're server-side
161
+ // we use our SSR-only cache. Otherwise, there's no entry to return.
162
+ const cache = handler.hydrate ? this._hydrationCache : _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide() ? this._ssrOnlyCache : undefined;
163
+ const internalEntry = cache == null ? void 0 : cache.retrieve(handler, options); // If we are not server-side and we hydrated something, let's clear
164
+ // that from the hydration cache to save memory.
165
+
166
+ if (this._ssrOnlyCache == null && internalEntry != null) {
167
+ // We now delete this from our hydration cache as we don't need it.
396
168
  // This does mean that if another handler of the same type but
397
- // without a custom cache won't get the value, but that's not an
398
- // expected valid usage of this framework - two handlers with
399
- // different caching options shouldn't be using the same type name.
400
-
401
- this._hydrationAndDefaultCache.remove(handler, options);
169
+ // without some sort of linked cache won't get the value, but
170
+ // that's not an expected use-case. If two different places use the
171
+ // same handler and options (i.e. the same request), then the
172
+ // handler should cater to that to ensure they share the result.
173
+ this._hydrationCache.remove(handler, options);
402
174
  }
403
175
 
404
176
  return internalEntry;
405
177
  };
406
178
 
407
179
  this.remove = (handler, options) => {
180
+ var _this$_ssrOnlyCache$r, _this$_ssrOnlyCache;
181
+
408
182
  // NOTE(somewhatabstract): We could invoke removeAll with a predicate
409
183
  // to match the key of the entry we're removing, but that's an
410
184
  // inefficient way to remove a single item, so let's not do that.
411
- // If we're not server-side, and the handler has a custom cache
412
- // let's try to use it.
413
- const customCache = this._ssrOnlyCache == null ? handler.cache : null;
414
- const removedCustom = !!(customCache != null && customCache.remove(handler, options)); // Delete the entry from our internal cache.
415
- // Even if we have a custom cache, we want to make sure we still
416
- // removed the same value from internal cache since this could be
417
- // getting called before hydration for some complex advanced usage
418
- // reason.
419
-
420
- return this._defaultCache(handler).remove(handler, options) || removedCustom;
185
+ // Delete the entry from the appropriate cache.
186
+ 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;
421
187
  };
422
188
 
423
189
  this.removeAll = (handler, predicate) => {
424
- // If we're not server-side, and the handler has a custom cache
425
- // let's try to use it.
426
- const customCache = this._ssrOnlyCache == null ? handler.cache : null;
427
- const removedCountCustom = (customCache == null ? void 0 : customCache.removeAll(handler, predicate)) || 0; // Apply the predicate to what we have in our internal cached.
428
- // Even if we have a custom cache, we want to make sure we still
429
- // removed the same value from internal cache since this could be
430
- // getting called before hydration for some complex advanced usage
431
- // reason.
432
-
433
- const removedCount = this._defaultCache(handler).removeAll(handler, predicate); // We have no idea which keys were removed from which caches,
434
- // so we can't dedupe the remove counts based on keys.
435
- // That's why we return the total records deleted rather than the
436
- // total keys deleted.
437
-
438
-
439
- return removedCount + removedCountCustom;
190
+ var _this$_ssrOnlyCache$r2, _this$_ssrOnlyCache2;
191
+
192
+ // Apply the predicate to what we have in the appropriate cache.
193
+ 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;
440
194
  };
441
195
 
442
196
  this.cloneHydratableData = () => {
443
197
  // We return our hydration cache only.
444
- return this._hydrationAndDefaultCache.cloneData();
198
+ return this._hydrationCache.cloneData();
445
199
  };
446
200
 
447
- this._ssrOnlyCache = wonder_blocks_core_["Server"].isServerSide() ? ssrOnlyCache || new MemoryCache() : undefined;
448
- this._hydrationAndDefaultCache = memoryCache || new MemoryCache();
449
- }
450
- /**
451
- * Returns the default cache to use for the given handler.
452
- */
453
-
454
-
455
- _defaultCache(handler) {
456
- if (handler.hydrate) {
457
- return this._hydrationAndDefaultCache;
458
- } // If the handler doesn't want to hydrate, we return the SSR-only cache.
459
- // If we are client-side, we return our non-caching implementation.
460
-
461
-
462
- return this._ssrOnlyCache || NoCache.Default;
201
+ this._ssrOnlyCache = _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide() ? ssrOnlyCache || new _memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"]() : undefined;
202
+ this._hydrationCache = hydrationCache || new _memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"]();
463
203
  }
464
204
 
465
205
  _setCacheEntry(handler, options, entry) {
466
206
  const frozenEntry = Object.freeze(entry);
467
207
 
468
- if (this._ssrOnlyCache == null && handler.cache != null) {
469
- // We are not server-side, and our handler has its own cache,
470
- // so we use that to store values.
471
- handler.cache.store(handler, options, frozenEntry);
472
- } else {
473
- // We are either server-side, or our handler doesn't provide
474
- // a caching override.
475
- this._defaultCache(handler).store(handler, options, frozenEntry);
208
+ if (this._ssrOnlyCache != null) {
209
+ // We are server-side.
210
+ // We need to store this value.
211
+ if (handler.hydrate) {
212
+ this._hydrationCache.store(handler, options, frozenEntry);
213
+ } else {
214
+ this._ssrOnlyCache.store(handler, options, frozenEntry);
215
+ }
476
216
  }
477
217
 
478
218
  return frozenEntry;
@@ -485,89 +225,18 @@ class response_cache_ResponseCache {
485
225
 
486
226
 
487
227
  }
488
- // EXTERNAL MODULE: external "react"
489
- var external_react_ = __webpack_require__(0);
490
-
491
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/util/request-fulfillment.js
492
-
493
-
494
- let request_fulfillment_default;
495
-
496
- class request_fulfillment_RequestFulfillment {
497
- static get Default() {
498
- if (!request_fulfillment_default) {
499
- request_fulfillment_default = new request_fulfillment_RequestFulfillment();
500
- }
501
-
502
- return request_fulfillment_default;
503
- }
504
-
505
- constructor(responseCache = undefined) {
506
- this._requests = {};
507
-
508
- this._getHandlerSubcache = handler => {
509
- if (!this._requests[handler.type]) {
510
- this._requests[handler.type] = {};
511
- }
512
-
513
- return this._requests[handler.type];
514
- };
515
-
516
- this.fulfill = (handler, options) => {
517
- const handlerRequests = this._getHandlerSubcache(handler);
518
-
519
- const key = handler.getKey(options);
520
- /**
521
- * If we have an inflight request, we'll provide that.
522
- */
523
-
524
- const inflight = handlerRequests[key];
525
-
526
- if (inflight) {
527
- return inflight;
528
- }
529
- /**
530
- * We don't have an inflight request, so let's set one up.
531
- */
532
-
533
-
534
- const {
535
- cacheData,
536
- cacheError
537
- } = this._responseCache;
538
-
539
- try {
540
- const request = handler.fulfillRequest(options).then(data => {
541
- delete handlerRequests[key];
542
- /**
543
- * Let's cache the data!
544
- */
545
-
546
- return cacheData(handler, options, data);
547
- }).catch(error => {
548
- delete handlerRequests[key];
549
- /**
550
- * Let's cache the error!
551
- */
552
-
553
- return cacheError(handler, options, error);
554
- });
555
- handlerRequests[key] = request;
556
- return request;
557
- } catch (e) {
558
- /**
559
- * In this case, we don't cache an inflight request, because there
560
- * really isn't one.
561
- */
562
- return Promise.resolve(cacheError(handler, options, e));
563
- }
564
- };
565
228
 
566
- this._responseCache = responseCache || response_cache_ResponseCache.Default;
567
- }
229
+ /***/ }),
230
+ /* 3 */
231
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
568
232
 
569
- }
570
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/util/request-tracking.js
233
+ "use strict";
234
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return TrackerContext; });
235
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RequestTracker; });
236
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
237
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
238
+ /* harmony import */ var _response_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
239
+ /* harmony import */ var _request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);
571
240
 
572
241
 
573
242
 
@@ -577,13 +246,13 @@ class request_fulfillment_RequestFulfillment {
577
246
  *
578
247
  * INTERNAL USE ONLY
579
248
  */
580
- const TrackerContext = new external_react_["createContext"](null);
249
+ const TrackerContext = new react__WEBPACK_IMPORTED_MODULE_0__["createContext"](null);
581
250
  /**
582
251
  * The default instance is stored here.
583
252
  * It's created below in the Default() static property.
584
253
  */
585
254
 
586
- let request_tracking_default;
255
+ let _default;
587
256
  /**
588
257
  * Implements request tracking and fulfillment.
589
258
  *
@@ -591,13 +260,13 @@ let request_tracking_default;
591
260
  */
592
261
 
593
262
 
594
- class request_tracking_RequestTracker {
263
+ class RequestTracker {
595
264
  static get Default() {
596
- if (!request_tracking_default) {
597
- request_tracking_default = new request_tracking_RequestTracker();
265
+ if (!_default) {
266
+ _default = new RequestTracker();
598
267
  }
599
268
 
600
- return request_tracking_default;
269
+ return _default;
601
270
  }
602
271
  /**
603
272
  * These are the caches for tracked requests, their handlers, and responses.
@@ -675,8 +344,8 @@ class request_tracking_RequestTracker {
675
344
  return Promise.all(promises).then(() => this._responseCache.cloneHydratableData());
676
345
  };
677
346
 
678
- this._responseCache = responseCache || response_cache_ResponseCache.Default;
679
- this._requestFulfillment = new request_fulfillment_RequestFulfillment(responseCache);
347
+ this._responseCache = responseCache || _response_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ResponseCache */ "a"].Default;
348
+ this._requestFulfillment = new _request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestFulfillment */ "a"](responseCache);
680
349
  }
681
350
  /**
682
351
  * Track a request.
@@ -708,369 +377,499 @@ class request_tracking_RequestTracker {
708
377
 
709
378
 
710
379
  }
711
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/util/request-handler.js
380
+
381
+ /***/ }),
382
+ /* 4 */
383
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
384
+
385
+ "use strict";
386
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
387
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
388
+
389
+
712
390
  /**
713
- * Base implementation for creating a request handler.
391
+ * InterceptContext defines a map from handler type to interception methods.
714
392
  *
715
- * Provides a base implementation of the `IRequestHandler` base class for
716
- * use with the Wonder Blocks Data framework.
393
+ * INTERNAL USE ONLY
717
394
  */
718
- class RequestHandler {
719
- constructor(type, cache, hydrate = true) {
720
- this._type = type;
721
- this._cache = cache || null;
722
- this._hydrate = !!hydrate;
723
- }
395
+ const InterceptContext = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createContext"]({});
396
+ /* harmony default export */ __webpack_exports__["a"] = (InterceptContext);
724
397
 
725
- get type() {
726
- return this._type;
727
- }
398
+ /***/ }),
399
+ /* 5 */
400
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
728
401
 
729
- get cache() {
730
- return this._cache;
731
- }
402
+ "use strict";
403
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useData; });
404
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
405
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__);
406
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(0);
407
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
408
+ /* harmony import */ var _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);
409
+ /* harmony import */ var _components_intercept_context_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4);
410
+ /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(3);
411
+ /* harmony import */ var _util_result_from_cache_entry_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(12);
412
+ /* harmony import */ var _util_response_cache_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(2);
732
413
 
733
- get hydrate() {
734
- return this._hydrate;
735
- }
736
414
 
737
- shouldRefreshCache(options, cachedEntry) {
738
- /**
739
- * By default, the cache needs a refresh if the current entry is an
740
- * error.
741
- *
742
- * This means that an error will cause a re-request on render.
743
- * Useful if the server rendered an error, as it means the client
744
- * will update after rehydration.
745
- */
746
- return cachedEntry == null || cachedEntry.error != null;
747
- }
748
415
 
749
- getKey(options) {
750
- try {
751
- return options === undefined ? "undefined" : JSON.stringify(options);
752
- } catch (e) {
753
- throw new Error(`Failed to auto-generate key: ${e}`);
754
- }
755
- }
756
416
 
757
- fulfillRequest(options) {
758
- throw new Error("Not implemented");
759
- }
760
417
 
761
- }
762
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/components/track-data.js
763
418
 
764
419
 
420
+ const useData = (handler, options) => {
421
+ // If we're server-side or hydrating, we'll have a cached entry to use.
422
+ // So we get that and use it to initialize our state.
423
+ // This works in both hydration and SSR because the very first call to
424
+ // this will have cached data in those cases as it will be present on the
425
+ // initial render - and subsequent renders on the client it will be null.
426
+ const cachedResult = _util_response_cache_js__WEBPACK_IMPORTED_MODULE_6__[/* ResponseCache */ "a"].Default.getEntry(handler, options);
427
+ const [result, setResult] = Object(react__WEBPACK_IMPORTED_MODULE_1__["useState"])(cachedResult); // We only track data requests when we are server-side and we don't
428
+ // already have a result, as given by the cachedData (which is also the
429
+ // initial value for the result state).
765
430
 
431
+ const maybeTrack = Object(react__WEBPACK_IMPORTED_MODULE_1__["useContext"])(_util_request_tracking_js__WEBPACK_IMPORTED_MODULE_4__[/* TrackerContext */ "b"]);
766
432
 
767
- /**
768
- * Component to enable data request tracking when server-side rendering.
769
- */
770
- class track_data_TrackData extends external_react_["Component"] {
771
- render() {
772
- if (!wonder_blocks_core_["Server"].isServerSide()) {
773
- throw new Error("This component is not for use during client-side rendering");
774
- }
433
+ if (result == null && _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
434
+ maybeTrack == null ? void 0 : maybeTrack(handler, options);
435
+ } // Lookup to see if there's an interceptor for the handler.
436
+ // If we have one, we need to replace the handler with one that
437
+ // uses the interceptor.
775
438
 
776
- return /*#__PURE__*/external_react_["createElement"](TrackerContext.Provider, {
777
- value: request_tracking_RequestTracker.Default.trackDataRequest
778
- }, this.props.children);
779
- }
780
439
 
781
- }
782
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/components/internal-data.js
440
+ const interceptorMap = Object(react__WEBPACK_IMPORTED_MODULE_1__["useContext"])(_components_intercept_context_js__WEBPACK_IMPORTED_MODULE_3__[/* default */ "a"]);
441
+ const interceptor = interceptorMap[handler.type]; // We need to update our request when the handler changes or the key
442
+ // to the options change, so we keep track of those.
443
+ // However, even if we are hydrating from cache, we still need to make the
444
+ // request at least once, so we do not initialize these references.
783
445
 
446
+ const handlerRef = Object(react__WEBPACK_IMPORTED_MODULE_1__["useRef"])();
447
+ const keyRef = Object(react__WEBPACK_IMPORTED_MODULE_1__["useRef"])();
448
+ const interceptorRef = Object(react__WEBPACK_IMPORTED_MODULE_1__["useRef"])(); // This effect will ensure that we fulfill the request as desired.
784
449
 
450
+ Object(react__WEBPACK_IMPORTED_MODULE_1__["useEffect"])(() => {
451
+ // If we are server-side, then just skip the effect. We track requests
452
+ // during SSR and fulfill them outside of the React render cycle.
453
+ // NOTE: This shouldn't happen since effects would not run on the server
454
+ // but let's be defensive - I think it makes the code clearer.
785
455
 
456
+ /* istanbul ignore next */
457
+ if (_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
458
+ return;
459
+ } // Update our refs to the current handler and key.
786
460
 
787
461
 
788
- /**
789
- * This component is responsible for actually handling the data request.
790
- * It is wrapped by Data in order to support intercepts and be exported for use.
791
- *
792
- * INTERNAL USE ONLY
793
- */
794
- class internal_data_InternalData extends external_react_["Component"] {
795
- constructor(props) {
796
- super(props);
797
- this.state = this._buildStateAndfulfillNeeds(props);
798
- }
799
-
800
- componentDidMount() {
801
- this._mounted = true;
802
- }
462
+ handlerRef.current = handler;
463
+ keyRef.current = handler.getKey(options);
464
+ interceptorRef.current = interceptor; // If we're not hydrating a result, we want to make sure we set our
465
+ // result to null so that we're in the loading state.
803
466
 
804
- shouldComponentUpdate(nextProps, nextState) {
805
- /**
806
- * We only bother updating if our state changed.
807
- *
808
- * And we only update the state if props changed
809
- * or we got new data/error.
810
- */
811
- if (!this._propsMatch(nextProps)) {
812
- const newState = this._buildStateAndfulfillNeeds(nextProps);
813
-
814
- this.setState(newState);
467
+ if (cachedResult == null) {
468
+ // Mark ourselves as loading.
469
+ setResult(null);
815
470
  }
816
471
 
817
- return this.state.loading !== nextState.loading || this.state.data !== nextState.data || this.state.error !== nextState.error;
818
- }
472
+ const getMaybeInterceptedHandler = () => {
473
+ if (interceptor == null) {
474
+ return handler;
475
+ }
819
476
 
820
- componentWillUnmount() {
821
- this._mounted = false;
822
- }
477
+ const fulfillRequestFn = options => {
478
+ var _interceptor$fulfillR;
823
479
 
824
- _propsMatch(otherProps) {
825
- const {
826
- handler,
827
- options
828
- } = this.props;
829
- const {
830
- handler: prevHandler,
831
- options: prevOptions
832
- } = otherProps;
833
- return handler === prevHandler && handler.getKey(options) === prevHandler.getKey(prevOptions);
834
- }
480
+ return (_interceptor$fulfillR = interceptor.fulfillRequest(options)) != null ? _interceptor$fulfillR : handler.fulfillRequest(options);
481
+ };
482
+
483
+ return {
484
+ fulfillRequest: fulfillRequestFn,
485
+ getKey: options => handler.getKey(options),
486
+ type: handler.type,
487
+ hydrate: handler.hydrate
488
+ };
489
+ }; // We aren't server-side, so let's make the request.
490
+ // The request handler is in control of whether that request actually
491
+ // happens or not.
835
492
 
836
- _buildStateAndfulfillNeeds(propsAtFulfillment) {
837
- const {
838
- getEntry,
839
- handler,
840
- options
841
- } = propsAtFulfillment;
842
- const cachedData = getEntry(handler, options);
843
493
 
844
- if (!wonder_blocks_core_["Server"].isServerSide() && (cachedData == null || handler.shouldRefreshCache(options, cachedData))) {
494
+ let cancel = false;
495
+ _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestFulfillment */ "a"].Default.fulfill(getMaybeInterceptedHandler(), options).then(updateEntry => {
496
+ if (cancel) {
497
+ return;
498
+ }
499
+
500
+ setResult(updateEntry);
501
+ return;
502
+ }).catch(e => {
503
+ if (cancel) {
504
+ return;
505
+ }
845
506
  /**
846
- * We're not on the server, the cache missed, or our handler says
847
- * we should refresh the cache.
848
- *
849
- * Therefore, we need to request data.
850
- *
851
- * We have to do this here from the constructor so that this
852
- * data request is tracked when performing server-side rendering.
507
+ * We should never get here as errors in fulfillment are part
508
+ * of the `then`, but if we do.
853
509
  */
854
- request_fulfillment_RequestFulfillment.Default.fulfill(handler, options).then(cacheEntry => {
855
- /**
856
- * We get here, we should have updated the cache.
857
- * However, we need to update the component, but we
858
- * should only do that if the props are the same as they
859
- * were when this was called.
860
- */
861
- if (this._mounted && this._propsMatch(propsAtFulfillment)) {
862
- this.setState({
863
- loading: false,
864
- data: cacheEntry.data,
865
- error: cacheEntry.error
866
- });
867
- }
510
+ // eslint-disable-next-line no-console
868
511
 
869
- return null;
870
- }).catch(e => {
871
- /**
872
- * We should never get here, but if we do.
873
- */
874
- // eslint-disable-next-line no-console
875
- console.error(`Unexpected error occurred during data fulfillment: ${e}`);
876
-
877
- if (this._mounted && this._propsMatch(propsAtFulfillment)) {
878
- this.setState({
879
- loading: false,
880
- data: null,
881
- error: typeof e === "string" ? e : e.message
882
- });
883
- }
884
512
 
885
- return null;
513
+ console.error(`Unexpected error occurred during data fulfillment: ${e}`);
514
+ setResult({
515
+ data: null,
516
+ error: typeof e === "string" ? e : e.message
886
517
  });
887
- }
888
- /**
889
- * This is the default response for the server and for the initial
890
- * client-side render if we have cachedData.
891
- *
892
- * This ensures we don't make promises we don't want when doing
893
- * server-side rendering. Instead, we either have data from the cache
894
- * or we don't.
895
- */
518
+ return;
519
+ });
520
+ return () => {
521
+ cancel = true;
522
+ }; // - handler.getKey is a proxy for options
523
+ // - We don't want to trigger on cachedResult changing, we're
524
+ // just using that as a flag for render state if the other things
525
+ // trigger this effect.
526
+ // eslint-disable-next-line react-hooks/exhaustive-deps
527
+ }, [handler, handler.getKey(options), interceptor]);
528
+ return Object(_util_result_from_cache_entry_js__WEBPACK_IMPORTED_MODULE_5__[/* resultFromCacheEntry */ "a"])(result);
529
+ };
896
530
 
531
+ /***/ }),
532
+ /* 6 */
533
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
897
534
 
898
- return {
899
- loading: cachedData == null,
900
- data: cachedData && cachedData.data,
901
- error: cachedData && cachedData.error
902
- };
903
- }
535
+ "use strict";
536
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RequestFulfillment; });
537
+ /* harmony import */ var _response_cache_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
904
538
 
905
- _resultFromState() {
906
- const {
907
- loading,
908
- data,
909
- error
910
- } = this.state;
911
539
 
912
- if (loading) {
913
- return {
914
- loading: true
915
- };
916
- }
540
+ let _default;
917
541
 
918
- if (data != null) {
919
- return {
920
- loading: false,
921
- data
922
- };
542
+ class RequestFulfillment {
543
+ static get Default() {
544
+ if (!_default) {
545
+ _default = new RequestFulfillment();
923
546
  }
924
547
 
925
- if (error == null) {
926
- // We should never get here ever.
927
- throw new Error("Loaded result has invalid state where data and error are missing");
928
- }
548
+ return _default;
549
+ }
929
550
 
930
- return {
931
- loading: false,
932
- error
551
+ constructor(responseCache = undefined) {
552
+ this._requests = {};
553
+
554
+ this._getHandlerSubcache = handler => {
555
+ if (!this._requests[handler.type]) {
556
+ this._requests[handler.type] = {};
557
+ }
558
+
559
+ return this._requests[handler.type];
933
560
  };
934
- }
935
561
 
936
- _renderContent(result) {
937
- const {
938
- children
939
- } = this.props;
940
- return children(result);
941
- }
562
+ this.fulfill = (handler, options) => {
563
+ const handlerRequests = this._getHandlerSubcache(handler);
942
564
 
943
- _renderWithTrackingContext(result) {
944
- return /*#__PURE__*/external_react_["createElement"](TrackerContext.Consumer, null, track => {
565
+ const key = handler.getKey(options);
945
566
  /**
946
- * If data tracking wasn't enabled, don't do it.
567
+ * If we have an inflight request, we'll provide that.
947
568
  */
948
- if (track != null) {
949
- track(this.props.handler, this.props.options);
569
+
570
+ const inflight = handlerRequests[key];
571
+
572
+ if (inflight) {
573
+ return inflight;
950
574
  }
575
+ /**
576
+ * We don't have an inflight request, so let's set one up.
577
+ */
951
578
 
952
- return this._renderContent(result);
953
- });
954
- }
955
579
 
956
- render() {
957
- const result = this._resultFromState(); // We only track data requests when we are server-side and we don't
958
- // already have a result. The existence of a result is indicated by the
959
- // loading flag being false.
580
+ const {
581
+ cacheData,
582
+ cacheError
583
+ } = this._responseCache;
960
584
 
585
+ try {
586
+ const request = handler.fulfillRequest(options).then(data => {
587
+ delete handlerRequests[key];
588
+ /**
589
+ * Let's cache the data!
590
+ *
591
+ * NOTE: This only caches when we're server side.
592
+ */
961
593
 
962
- if (result.loading && wonder_blocks_core_["Server"].isServerSide()) {
963
- return this._renderWithTrackingContext(result);
964
- }
594
+ return cacheData(handler, options, data);
595
+ }).catch(error => {
596
+ delete handlerRequests[key];
597
+ /**
598
+ * Let's cache the error!
599
+ *
600
+ * NOTE: This only caches when we're server side.
601
+ */
965
602
 
966
- return this._renderContent(result);
603
+ return cacheError(handler, options, error);
604
+ });
605
+ handlerRequests[key] = request;
606
+ return request;
607
+ } catch (e) {
608
+ /**
609
+ * In this case, we don't cache an inflight request, because there
610
+ * really isn't one.
611
+ */
612
+ return Promise.resolve(cacheError(handler, options, e));
613
+ }
614
+ };
615
+
616
+ this._responseCache = responseCache || _response_cache_js__WEBPACK_IMPORTED_MODULE_0__[/* ResponseCache */ "a"].Default;
967
617
  }
968
618
 
969
619
  }
970
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/components/intercept-context.js
971
620
 
621
+ /***/ }),
622
+ /* 7 */
623
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
972
624
 
625
+ "use strict";
626
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return MemoryCache; });
627
+ function deepClone(source) {
628
+ /**
629
+ * We want to deep clone the source cache to dodge mutations by external
630
+ * references. So we serialize the source cache to JSON and parse it
631
+ * back into a new object.
632
+ *
633
+ * NOTE: This doesn't work for get/set property accessors.
634
+ */
635
+ const serializedInitCache = JSON.stringify(source);
636
+ const cloneInitCache = JSON.parse(serializedInitCache);
637
+ return Object.freeze(cloneInitCache);
638
+ }
973
639
  /**
974
- * InterceptContext defines a map from handler type to interception methods.
975
- *
976
640
  * INTERNAL USE ONLY
641
+ *
642
+ * Special case cache implementation for the memory cache.
643
+ *
644
+ * This is only used within our framework for SSR (see ./response-cache.js).
977
645
  */
978
- const InterceptContext = /*#__PURE__*/external_react_["createContext"]({});
979
- /* harmony default export */ var intercept_context = (InterceptContext);
980
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/components/data.js
981
646
 
982
647
 
648
+ class MemoryCache {
649
+ constructor(source = null) {
650
+ this.store = (handler, options, entry) => {
651
+ const requestType = handler.type;
652
+ const frozenEntry = Object.freeze(entry); // Ensure we have a cache location for this handler type.
653
+
654
+ this._cache[requestType] = this._cache[requestType] || {}; // Cache the data.
655
+
656
+ const key = handler.getKey(options);
657
+ this._cache[requestType][key] = frozenEntry;
658
+ };
659
+
660
+ this.retrieve = (handler, options) => {
661
+ const requestType = handler.type; // Get the internal subcache for the handler.
662
+
663
+ const handlerCache = this._cache[requestType];
664
+
665
+ if (!handlerCache) {
666
+ return null;
667
+ } // Get the response.
668
+
669
+
670
+ const key = handler.getKey(options);
671
+ const internalEntry = handlerCache[key];
672
+
673
+ if (internalEntry == null) {
674
+ return null;
675
+ }
676
+
677
+ return internalEntry;
678
+ };
679
+
680
+ this.remove = (handler, options) => {
681
+ const requestType = handler.type; // NOTE(somewhatabstract): We could invoke removeAll with a predicate
682
+ // to match the key of the entry we're removing, but that's an
683
+ // inefficient way to remove a single item, so let's not do that.
684
+ // Get the internal subcache for the handler.
685
+
686
+ const handlerCache = this._cache[requestType];
687
+
688
+ if (!handlerCache) {
689
+ return false;
690
+ } // Get the entry.
691
+
692
+
693
+ const key = handler.getKey(options);
694
+ const internalEntry = handlerCache[key];
695
+
696
+ if (internalEntry == null) {
697
+ return false;
698
+ } // Delete the entry.
699
+
700
+
701
+ delete handlerCache[key];
702
+ return true;
703
+ };
983
704
 
705
+ this.removeAll = (handler, predicate) => {
706
+ const requestType = handler.type; // Get the internal subcache for the handler.
984
707
 
708
+ const handlerCache = this._cache[requestType];
709
+
710
+ if (!handlerCache) {
711
+ return 0;
712
+ }
713
+
714
+ let removedCount = 0;
985
715
 
716
+ if (typeof predicate === "function") {
717
+ // Apply the predicate to what we have cached.
718
+ for (const [key, entry] of Object.entries(handlerCache)) {
719
+ if (predicate(key, entry)) {
720
+ removedCount++;
721
+ delete handlerCache[key];
722
+ }
723
+ }
724
+ } else {
725
+ // We're removing everything so delete the entire subcache.
726
+ removedCount = Object.keys(handlerCache).length;
727
+ delete this._cache[requestType];
728
+ }
729
+
730
+ return removedCount;
731
+ };
732
+
733
+ this.cloneData = () => {
734
+ try {
735
+ return deepClone(this._cache);
736
+ } catch (e) {
737
+ throw new Error(`An error occurred while trying to clone the cache: ${e}`);
738
+ }
739
+ };
740
+
741
+ this._cache = {};
742
+
743
+ if (source != null) {
744
+ try {
745
+ /**
746
+ * Object.assign only performs a shallow clone.
747
+ * So we deep clone it and then assign the clone values to our
748
+ * internal cache.
749
+ */
750
+ const cloneInitCache = deepClone(source);
751
+ Object.assign(this._cache, cloneInitCache);
752
+ } catch (e) {
753
+ throw new Error(`An error occurred trying to initialize from a response cache snapshot: ${e}`);
754
+ }
755
+ }
756
+ }
757
+ /**
758
+ * Indicate if this cache is being used or now.
759
+ *
760
+ * When the cache has entries, returns `true`; otherwise, returns `false`.
761
+ */
762
+
763
+
764
+ get inUse() {
765
+ return Object.keys(this._cache).length > 0;
766
+ }
767
+
768
+ }
769
+
770
+ /***/ }),
771
+ /* 8 */
772
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
773
+
774
+ "use strict";
775
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RequestHandler; });
986
776
  /**
987
- * This component is the main component of Wonder Blocks Data. With this, data
988
- * requirements can be placed in a React application in a manner that will
989
- * support server-side rendering and efficient caching.
777
+ * Base implementation for creating a request handler.
778
+ *
779
+ * Provides a base implementation of the `IRequestHandler` base class for
780
+ * use with the Wonder Blocks Data framework.
990
781
  */
991
- class data_Data extends external_react_["Component"] {
992
- _getHandlerFromInterceptor(interceptor) {
993
- const {
994
- handler
995
- } = this.props;
996
-
997
- if (!interceptor) {
998
- return handler;
999
- }
782
+ class RequestHandler {
783
+ constructor(type, hydrate = true) {
784
+ this._type = type;
785
+ this._hydrate = !!hydrate;
786
+ }
1000
787
 
1001
- const {
1002
- fulfillRequest,
1003
- shouldRefreshCache
1004
- } = interceptor;
1005
- const fulfillRequestFn = fulfillRequest ? options => {
1006
- const interceptedResult = fulfillRequest(options);
1007
- return interceptedResult != null ? interceptedResult : handler.fulfillRequest(options);
1008
- } : options => handler.fulfillRequest(options);
1009
- const shouldRefreshCacheFn = shouldRefreshCache ? (options, cacheEntry) => {
1010
- const interceptedResult = shouldRefreshCache(options, cacheEntry);
1011
- return interceptedResult != null ? interceptedResult : handler.shouldRefreshCache(options, cacheEntry);
1012
- } : (options, cacheEntry) => handler.shouldRefreshCache(options, cacheEntry);
1013
- return {
1014
- fulfillRequest: fulfillRequestFn,
1015
- shouldRefreshCache: shouldRefreshCacheFn,
1016
- getKey: options => handler.getKey(options),
1017
- type: handler.type,
1018
- cache: handler.cache,
1019
- hydrate: handler.hydrate
1020
- };
788
+ get type() {
789
+ return this._type;
1021
790
  }
1022
791
 
1023
- _getCacheLookupFnFromInterceptor(interceptor) {
1024
- const getEntry = interceptor && interceptor.getEntry;
792
+ get hydrate() {
793
+ return this._hydrate;
794
+ }
1025
795
 
1026
- if (!getEntry) {
1027
- return response_cache_ResponseCache.Default.getEntry;
796
+ getKey(options) {
797
+ try {
798
+ return options === undefined ? "undefined" : JSON.stringify(options);
799
+ } catch (e) {
800
+ throw new Error(`Failed to auto-generate key: ${e}`);
1028
801
  }
802
+ }
1029
803
 
1030
- return (handler, options) => {
1031
- // 1. Lookup the current cache value.
1032
- const cacheEntry = response_cache_ResponseCache.Default.getEntry(handler, options); // 2. See if our interceptor wants to override it.
804
+ fulfillRequest(options) {
805
+ throw new Error("Not implemented");
806
+ }
1033
807
 
1034
- const interceptedData = getEntry(options, cacheEntry); // 3. Return the appropriate response.
808
+ }
1035
809
 
1036
- return interceptedData != null ? interceptedData : cacheEntry;
1037
- };
1038
- }
810
+ /***/ }),
811
+ /* 9 */
812
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
1039
813
 
1040
- render() {
1041
- return /*#__PURE__*/external_react_["createElement"](intercept_context.Consumer, null, value => {
1042
- const handlerType = this.props.handler.type;
1043
- const interceptor = value[handlerType];
814
+ "use strict";
815
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return TrackData; });
816
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
817
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
818
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1);
819
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__);
820
+ /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3);
1044
821
 
1045
- const handler = this._getHandlerFromInterceptor(interceptor);
1046
822
 
1047
- const getEntry = this._getCacheLookupFnFromInterceptor(interceptor);
1048
- /**
1049
- * Need to share our types with InternalData so Flow
1050
- * doesn't need to infer them and find mismatches.
1051
- * However, just deriving a new component creates issues
1052
- * where InternalData starts rerendering too often.
1053
- * Couldn't track down why, so suppressing the error
1054
- * instead.
1055
- */
1056
823
 
1057
824
 
1058
- return /*#__PURE__*/external_react_["createElement"](internal_data_InternalData // $FlowIgnore[incompatible-type-arg]
1059
- , {
1060
- handler: handler,
1061
- options: this.props.options,
1062
- getEntry: getEntry
1063
- }, result => this.props.children(result));
1064
- });
825
+ /**
826
+ * Component to enable data request tracking when server-side rendering.
827
+ */
828
+ class TrackData extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
829
+ render() {
830
+ if (!_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__["Server"].isServerSide()) {
831
+ throw new Error("This component is not for use during client-side rendering");
832
+ }
833
+
834
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__[/* TrackerContext */ "b"].Provider, {
835
+ value: _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestTracker */ "a"].Default.trackDataRequest
836
+ }, this.props.children);
1065
837
  }
1066
838
 
1067
839
  }
1068
- // EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/extends.js
1069
- var helpers_extends = __webpack_require__(2);
1070
- var extends_default = /*#__PURE__*/__webpack_require__.n(helpers_extends);
1071
840
 
1072
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/components/intercept-data.js
841
+ /***/ }),
842
+ /* 10 */
843
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
1073
844
 
845
+ "use strict";
846
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
847
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
848
+ /* harmony import */ var _hooks_use_data_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
849
+
850
+
851
+
852
+ /**
853
+ * This component is the main component of Wonder Blocks Data. With this, data
854
+ * requirements can be placed in a React application in a manner that will
855
+ * support server-side rendering and efficient caching.
856
+ */
857
+ const Data = props => {
858
+ const data = Object(_hooks_use_data_js__WEBPACK_IMPORTED_MODULE_1__[/* useData */ "a"])(props.handler, props.options);
859
+ return props.children(data);
860
+ };
861
+
862
+ /* harmony default export */ __webpack_exports__["a"] = (Data);
863
+
864
+ /***/ }),
865
+ /* 11 */
866
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
867
+
868
+ "use strict";
869
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return InterceptData; });
870
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
871
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
872
+ /* harmony import */ var _intercept_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
1074
873
 
1075
874
 
1076
875
 
@@ -1079,9 +878,6 @@ var extends_default = /*#__PURE__*/__webpack_require__.n(helpers_extends);
1079
878
  * type of a given handler and provide alternative results. This is mostly
1080
879
  * useful for testing.
1081
880
  *
1082
- * Results from this interceptor will end up in the cache. If you
1083
- * wish to only override the cache, use `InterceptCache` instead.
1084
- *
1085
881
  * This component is not recommended for use in production code as it
1086
882
  * can prevent predictable functioning of the Wonder Blocks Data framework.
1087
883
  * One possible side-effect is that inflight requests from the interceptor could
@@ -1093,88 +889,117 @@ var extends_default = /*#__PURE__*/__webpack_require__.n(helpers_extends);
1093
889
  * new instance will replace this interceptor for its children. All methods
1094
890
  * will be replaced.
1095
891
  */
1096
- class intercept_data_InterceptData extends external_react_["Component"] {
892
+ class InterceptData extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
1097
893
  render() {
1098
- return /*#__PURE__*/external_react_["createElement"](intercept_context.Consumer, null, value => {
894
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"].Consumer, null, value => {
1099
895
  const handlerType = this.props.handler.type;
1100
-
1101
- const interceptor = extends_default()({}, value[handlerType], {
1102
- fulfillRequest: this.props.fulfillRequest || null,
1103
- shouldRefreshCache: this.props.shouldRefreshCache || null
1104
- });
1105
-
1106
- const newValue = extends_default()({}, value, {
896
+ const interceptor = { ...value[handlerType],
897
+ fulfillRequest: this.props.fulfillRequest
898
+ };
899
+ const newValue = { ...value,
1107
900
  [handlerType]: interceptor
1108
- });
1109
-
1110
- return /*#__PURE__*/external_react_["createElement"](intercept_context.Provider, {
901
+ };
902
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"].Provider, {
1111
903
  value: newValue
1112
904
  }, this.props.children);
1113
905
  });
1114
906
  }
1115
907
 
1116
908
  }
1117
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/components/intercept-cache.js
1118
-
1119
-
1120
909
 
910
+ /***/ }),
911
+ /* 12 */
912
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
1121
913
 
914
+ "use strict";
915
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return resultFromCacheEntry; });
1122
916
  /**
1123
- * This component provides a mechanism to intercept cache lookups for the
1124
- * type of a given handler and provide alternative values. This is mostly
1125
- * useful for testing.
1126
- *
1127
- * This does not modify the cache in any way. If you want to intercept
1128
- * requests and cache based on the intercept, then use `InterceptData`.
1129
- *
1130
- * This component is generally not suitable for use in production code as it
1131
- * can prevent predictable functioning of the Wonder Blocks Data framework.
1132
- *
1133
- * These components do not chain. If a different `InterceptCache` instance is
1134
- * rendered within this one that intercepts the same handler type, then that
1135
- * new instance will replace this interceptor for its children.
917
+ * Turns a cache entry into a stateful result.
1136
918
  */
1137
- class intercept_cache_InterceptCache extends external_react_["Component"] {
1138
- render() {
1139
- return /*#__PURE__*/external_react_["createElement"](intercept_context.Consumer, null, value => {
1140
- const handlerType = this.props.handler.type;
919
+ const resultFromCacheEntry = cacheEntry => {
920
+ // No cache entry means we didn't load one yet.
921
+ if (cacheEntry == null) {
922
+ return {
923
+ status: "loading"
924
+ };
925
+ }
1141
926
 
1142
- const interceptor = extends_default()({}, value[handlerType], {
1143
- getEntry: this.props.getEntry
1144
- });
927
+ const {
928
+ data,
929
+ error
930
+ } = cacheEntry;
1145
931
 
1146
- const newValue = extends_default()({}, value, {
1147
- [handlerType]: interceptor
1148
- });
932
+ if (data != null) {
933
+ return {
934
+ status: "success",
935
+ data
936
+ };
937
+ }
1149
938
 
1150
- return /*#__PURE__*/external_react_["createElement"](intercept_context.Provider, {
1151
- value: newValue
1152
- }, this.props.children);
1153
- });
939
+ if (error == null) {
940
+ // We should never get here ever.
941
+ return {
942
+ status: "error",
943
+ error: "Loaded result has invalid state where data and error are missing"
944
+ };
1154
945
  }
1155
946
 
1156
- }
1157
- // CONCATENATED MODULE: ./packages/wonder-blocks-data/src/index.js
947
+ return {
948
+ status: "error",
949
+ error
950
+ };
951
+ };
1158
952
 
953
+ /***/ }),
954
+ /* 13 */
955
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
956
+
957
+ "use strict";
958
+ __webpack_require__.r(__webpack_exports__);
959
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "initializeCache", function() { return initializeCache; });
960
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fulfillAllDataRequests", function() { return fulfillAllDataRequests; });
961
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "hasUnfulfilledRequests", function() { return hasUnfulfilledRequests; });
962
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "removeFromCache", function() { return removeFromCache; });
963
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "removeAllFromCache", function() { return removeAllFromCache; });
964
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
965
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__);
966
+ /* harmony import */ var _util_response_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
967
+ /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3);
968
+ /* harmony import */ var _util_request_handler_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8);
969
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "RequestHandler", function() { return _util_request_handler_js__WEBPACK_IMPORTED_MODULE_3__["a"]; });
970
+
971
+ /* harmony import */ var _components_track_data_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9);
972
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "TrackData", function() { return _components_track_data_js__WEBPACK_IMPORTED_MODULE_4__["a"]; });
1159
973
 
974
+ /* harmony import */ var _components_data_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(10);
975
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Data", function() { return _components_data_js__WEBPACK_IMPORTED_MODULE_5__["a"]; });
1160
976
 
1161
- const initializeCache = source => response_cache_ResponseCache.Default.initialize(source);
977
+ /* harmony import */ var _components_intercept_data_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(11);
978
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "InterceptData", function() { return _components_intercept_data_js__WEBPACK_IMPORTED_MODULE_6__["a"]; });
979
+
980
+ /* harmony import */ var _hooks_use_data_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(5);
981
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useData", function() { return _hooks_use_data_js__WEBPACK_IMPORTED_MODULE_7__["a"]; });
982
+
983
+
984
+
985
+
986
+ const initializeCache = source => _util_response_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ResponseCache */ "a"].Default.initialize(source);
1162
987
  const fulfillAllDataRequests = () => {
1163
- if (!wonder_blocks_core_["Server"].isServerSide()) {
988
+ if (!_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
1164
989
  return Promise.reject(new Error("Data requests are not tracked when client-side"));
1165
990
  }
1166
991
 
1167
- return request_tracking_RequestTracker.Default.fulfillTrackedRequests();
992
+ return _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestTracker */ "a"].Default.fulfillTrackedRequests();
1168
993
  };
1169
994
  const hasUnfulfilledRequests = () => {
1170
- if (!wonder_blocks_core_["Server"].isServerSide()) {
995
+ if (!_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
1171
996
  throw new Error("Data requests are not tracked when client-side");
1172
997
  }
1173
998
 
1174
- return request_tracking_RequestTracker.Default.hasUnfulfilledRequests;
999
+ return _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestTracker */ "a"].Default.hasUnfulfilledRequests;
1175
1000
  };
1176
- const removeFromCache = (handler, options) => response_cache_ResponseCache.Default.remove(handler, options);
1177
- const removeAllFromCache = (handler, predicate) => response_cache_ResponseCache.Default.removeAll(handler, predicate);
1001
+ const removeFromCache = (handler, options) => _util_response_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ResponseCache */ "a"].Default.remove(handler, options);
1002
+ const removeAllFromCache = (handler, predicate) => _util_response_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ResponseCache */ "a"].Default.removeAll(handler, predicate);
1178
1003
  /**
1179
1004
  * TODO(somewhatabstract): Export each cache type we implement.
1180
1005
  *
@@ -1187,6 +1012,5 @@ const removeAllFromCache = (handler, predicate) => response_cache_ResponseCache.
1187
1012
 
1188
1013
 
1189
1014
 
1190
-
1191
1015
  /***/ })
1192
1016
  /******/ ]);