@khanacademy/wonder-blocks-data 4.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/es/index.js +793 -375
  3. package/dist/index.js +1203 -523
  4. package/legacy-docs.md +3 -0
  5. package/package.json +2 -2
  6. package/src/__docs__/_overview_.stories.mdx +18 -0
  7. package/src/__docs__/_overview_graphql.stories.mdx +35 -0
  8. package/src/__docs__/_overview_ssr_.stories.mdx +185 -0
  9. package/src/__docs__/_overview_testing_.stories.mdx +123 -0
  10. package/src/__docs__/exports.clear-shared-cache.stories.mdx +20 -0
  11. package/src/__docs__/exports.data-error.stories.mdx +23 -0
  12. package/src/__docs__/exports.data-errors.stories.mdx +23 -0
  13. package/src/{components/data.md → __docs__/exports.data.stories.mdx} +15 -18
  14. package/src/__docs__/exports.fulfill-all-data-requests.stories.mdx +24 -0
  15. package/src/__docs__/exports.gql-error.stories.mdx +23 -0
  16. package/src/__docs__/exports.gql-errors.stories.mdx +20 -0
  17. package/src/__docs__/exports.gql-router.stories.mdx +29 -0
  18. package/src/__docs__/exports.has-unfulfilled-requests.stories.mdx +20 -0
  19. package/src/__docs__/exports.intercept-requests.stories.mdx +69 -0
  20. package/src/__docs__/exports.intialize-cache.stories.mdx +29 -0
  21. package/src/__docs__/exports.remove-all-from-cache.stories.mdx +24 -0
  22. package/src/__docs__/exports.remove-from-cache.stories.mdx +25 -0
  23. package/src/__docs__/exports.request-fulfillment.stories.mdx +36 -0
  24. package/src/__docs__/exports.scoped-in-memory-cache.stories.mdx +92 -0
  25. package/src/__docs__/exports.serializable-in-memory-cache.stories.mdx +112 -0
  26. package/src/__docs__/exports.status.stories.mdx +31 -0
  27. package/src/{components/track-data.md → __docs__/exports.track-data.stories.mdx} +15 -0
  28. package/src/__docs__/exports.use-cached-effect.stories.mdx +41 -0
  29. package/src/__docs__/exports.use-gql.stories.mdx +73 -0
  30. package/src/__docs__/exports.use-hydratable-effect.stories.mdx +43 -0
  31. package/src/__docs__/exports.use-server-effect.stories.mdx +38 -0
  32. package/src/__docs__/exports.use-shared-cache.stories.mdx +30 -0
  33. package/src/__docs__/exports.when-client-side.stories.mdx +33 -0
  34. package/src/__docs__/types.cached-response.stories.mdx +29 -0
  35. package/src/__docs__/types.error-options.stories.mdx +21 -0
  36. package/src/__docs__/types.gql-context.stories.mdx +20 -0
  37. package/src/__docs__/types.gql-fetch-fn.stories.mdx +24 -0
  38. package/src/__docs__/types.gql-fetch-options.stories.mdx +24 -0
  39. package/src/__docs__/types.gql-operation-type.stories.mdx +24 -0
  40. package/src/__docs__/types.gql-operation.stories.mdx +67 -0
  41. package/src/__docs__/types.response-cache.stories.mdx +33 -0
  42. package/src/__docs__/types.result.stories.mdx +39 -0
  43. package/src/__docs__/types.scoped-cache.stories.mdx +27 -0
  44. package/src/__docs__/types.valid-cache-data.stories.mdx +23 -0
  45. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +0 -80
  46. package/src/__tests__/generated-snapshot.test.js +7 -31
  47. package/src/components/__tests__/data.test.js +160 -154
  48. package/src/components/__tests__/intercept-requests.test.js +58 -0
  49. package/src/components/data.js +22 -126
  50. package/src/components/intercept-context.js +4 -5
  51. package/src/components/intercept-requests.js +69 -0
  52. package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +8 -8
  53. package/src/hooks/__tests__/use-cached-effect.test.js +507 -0
  54. package/src/hooks/__tests__/use-gql-router-context.test.js +133 -0
  55. package/src/hooks/__tests__/use-gql.test.js +1 -30
  56. package/src/hooks/__tests__/use-hydratable-effect.test.js +708 -0
  57. package/src/hooks/__tests__/use-request-interception.test.js +255 -0
  58. package/src/hooks/__tests__/use-server-effect.test.js +39 -11
  59. package/src/hooks/use-cached-effect.js +225 -0
  60. package/src/hooks/use-gql-router-context.js +50 -0
  61. package/src/hooks/use-gql.js +22 -52
  62. package/src/hooks/use-hydratable-effect.js +206 -0
  63. package/src/hooks/use-request-interception.js +51 -0
  64. package/src/hooks/use-server-effect.js +14 -7
  65. package/src/hooks/use-shared-cache.js +13 -11
  66. package/src/index.js +54 -2
  67. package/src/util/__tests__/__snapshots__/serializable-in-memory-cache.test.js.snap +19 -0
  68. package/src/util/__tests__/merge-gql-context.test.js +74 -0
  69. package/src/util/__tests__/request-fulfillment.test.js +23 -42
  70. package/src/util/__tests__/request-tracking.test.js +26 -7
  71. package/src/util/__tests__/result-from-cache-response.test.js +19 -5
  72. package/src/util/__tests__/scoped-in-memory-cache.test.js +6 -85
  73. package/src/util/__tests__/serializable-in-memory-cache.test.js +398 -0
  74. package/src/util/__tests__/ssr-cache.test.js +52 -52
  75. package/src/util/abort-error.js +15 -0
  76. package/src/util/data-error.js +58 -0
  77. package/src/util/get-gql-data-from-response.js +3 -2
  78. package/src/util/gql-error.js +19 -11
  79. package/src/util/merge-gql-context.js +34 -0
  80. package/src/util/request-fulfillment.js +49 -46
  81. package/src/util/request-tracking.js +69 -15
  82. package/src/util/result-from-cache-response.js +12 -16
  83. package/src/util/scoped-in-memory-cache.js +24 -47
  84. package/src/util/serializable-in-memory-cache.js +49 -0
  85. package/src/util/ssr-cache.js +9 -8
  86. package/src/util/status.js +30 -0
  87. package/src/util/types.js +18 -1
  88. package/docs.md +0 -122
  89. package/src/components/__tests__/intercept-data.test.js +0 -63
  90. package/src/components/intercept-data.js +0 -66
  91. package/src/components/intercept-data.md +0 -51
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 = 28);
86
86
  /******/ })
87
87
  /************************************************************************/
88
88
  /******/ ([
@@ -93,32 +93,111 @@ module.exports = require("react");
93
93
 
94
94
  /***/ }),
95
95
  /* 1 */
96
- /***/ (function(module, exports) {
96
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
97
97
 
98
- module.exports = require("@khanacademy/wonder-stuff-core");
98
+ "use strict";
99
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return DataErrors; });
100
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return DataError; });
101
+ /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
102
+ /* 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
+
104
+
105
+ /**
106
+ * Error kinds for DataError.
107
+ */
108
+ const DataErrors = Object.freeze({
109
+ /**
110
+ * The kind of error is not known.
111
+ */
112
+ Unknown: "Unknown",
113
+
114
+ /**
115
+ * The error is internal to the executing code.
116
+ */
117
+ Internal: "Internal",
118
+
119
+ /**
120
+ * There was a problem with the provided input.
121
+ */
122
+ InvalidInput: "InvalidInput",
123
+
124
+ /**
125
+ * A network error occurred.
126
+ */
127
+ Network: "Network",
128
+
129
+ /**
130
+ * Response could not be parsed.
131
+ */
132
+ Parse: "Parse",
133
+
134
+ /**
135
+ * An error that occurred during SSR and was hydrated from cache
136
+ */
137
+ Hydrated: "Hydrated"
138
+ });
139
+ /**
140
+ * An error from the Wonder Blocks Data API.
141
+ *
142
+ * Errors of this type will have names of the format:
143
+ * `${kind}DataError`
144
+ */
145
+
146
+ class DataError extends _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["KindError"] {
147
+ constructor(message, kind, {
148
+ metadata,
149
+ cause
150
+ } = {}) {
151
+ super(message, kind, {
152
+ metadata,
153
+ cause,
154
+ name: "Data"
155
+ });
156
+ }
157
+
158
+ }
99
159
 
100
160
  /***/ }),
101
161
  /* 2 */
162
+ /***/ (function(module, exports) {
163
+
164
+ module.exports = require("@khanacademy/wonder-blocks-core");
165
+
166
+ /***/ }),
167
+ /* 3 */
102
168
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
103
169
 
104
170
  "use strict";
105
171
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return GqlErrors; });
106
172
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return GqlError; });
107
- /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
173
+ /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
108
174
  /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__);
109
175
 
110
176
 
111
177
  /**
112
178
  * Error kinds for GqlError.
113
179
  */
114
- const GqlErrors = Object.freeze({ ..._khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["Errors"],
115
- Network: "Network",
116
- Parse: "Parse",
180
+ const GqlErrors = Object.freeze({
181
+ /**
182
+ * An internal framework error.
183
+ */
184
+ Internal: "Internal",
185
+
186
+ /**
187
+ * Response does not have the correct structure for a GraphQL response.
188
+ */
117
189
  BadResponse: "BadResponse",
190
+
191
+ /**
192
+ * A valid GraphQL result with errors field in the payload.
193
+ */
118
194
  ErrorResult: "ErrorResult"
119
195
  });
120
196
  /**
121
197
  * An error from the GQL API.
198
+ *
199
+ * Errors of this type will have names of the format:
200
+ * `${kind}GqlError`
122
201
  */
123
202
 
124
203
  class GqlError extends _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["KindError"] {
@@ -129,27 +208,50 @@ class GqlError extends _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0
129
208
  super(message, kind, {
130
209
  metadata,
131
210
  cause,
132
- prefix: "Gql"
211
+ name: "Gql"
133
212
  });
134
213
  }
135
214
 
136
215
  }
137
216
 
138
217
  /***/ }),
139
- /* 3 */
140
- /***/ (function(module, exports) {
218
+ /* 4 */
219
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
141
220
 
142
- module.exports = require("@khanacademy/wonder-blocks-core");
221
+ "use strict";
222
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Status; });
223
+ const loadingStatus = Object.freeze({
224
+ status: "loading"
225
+ });
226
+ const abortedStatus = Object.freeze({
227
+ status: "aborted"
228
+ });
229
+ /**
230
+ * Create Result<TData> instances with specific statuses.
231
+ */
232
+
233
+ const Status = Object.freeze({
234
+ loading: () => loadingStatus,
235
+ aborted: () => abortedStatus,
236
+ success: data => ({
237
+ status: "success",
238
+ data
239
+ }),
240
+ error: error => ({
241
+ status: "error",
242
+ error
243
+ })
244
+ });
143
245
 
144
246
  /***/ }),
145
- /* 4 */
247
+ /* 5 */
146
248
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
147
249
 
148
250
  "use strict";
149
251
  /* 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);
252
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
151
253
  /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__);
152
- /* harmony import */ var _scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
254
+ /* harmony import */ var _serializable_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(12);
153
255
 
154
256
 
155
257
  const DefaultScope = "default";
@@ -181,7 +283,7 @@ class SsrCache {
181
283
  throw new Error("Cannot initialize data response cache more than once");
182
284
  }
183
285
 
184
- this._hydrationCache = new _scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* ScopedInMemoryCache */ "a"]({
286
+ this._hydrationCache = new _serializable_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* SerializableInMemoryCache */ "a"]({
185
287
  // $FlowIgnore[incompatible-call]
186
288
  [DefaultScope]: source
187
289
  });
@@ -259,8 +361,8 @@ class SsrCache {
259
361
  return (_cache$DefaultScope = cache[DefaultScope]) != null ? _cache$DefaultScope : {};
260
362
  };
261
363
 
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"]();
364
+ this._ssrOnlyCache = _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide() ? ssrOnlyCache || new _serializable_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* SerializableInMemoryCache */ "a"]() : undefined;
365
+ this._hydrationCache = hydrationCache || new _serializable_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* SerializableInMemoryCache */ "a"]();
264
366
  }
265
367
 
266
368
  _setCachedResponse(id, entry, hydrate) {
@@ -293,124 +395,13 @@ class SsrCache {
293
395
  }
294
396
 
295
397
  /***/ }),
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
-
398
+ /* 6 */
399
+ /***/ (function(module, exports) {
409
400
 
410
- }
401
+ module.exports = require("@khanacademy/wonder-stuff-core");
411
402
 
412
403
  /***/ }),
413
- /* 6 */
404
+ /* 7 */
414
405
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
415
406
 
416
407
  "use strict";
@@ -418,8 +409,8 @@ class ScopedInMemoryCache {
418
409
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RequestTracker; });
419
410
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
420
411
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
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__(9);
412
+ /* harmony import */ var _ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
413
+ /* harmony import */ var _request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10);
423
414
 
424
415
 
425
416
 
@@ -477,11 +468,55 @@ class RequestTracker {
477
468
 
478
469
  this.fulfillTrackedRequests = () => {
479
470
  const promises = [];
471
+ const {
472
+ cacheData,
473
+ cacheError
474
+ } = this._responseCache;
480
475
 
481
476
  for (const requestKey of Object.keys(this._trackedRequests)) {
482
- const promise = this._requestFulfillment.fulfill(requestKey, this._trackedRequests[requestKey]);
483
-
484
- promises.push(promise);
477
+ const options = this._trackedRequests[requestKey];
478
+
479
+ try {
480
+ promises.push(this._requestFulfillment.fulfill(requestKey, { ...options
481
+ }).then(result => {
482
+ switch (result.status) {
483
+ case "success":
484
+ /**
485
+ * Let's cache the data!
486
+ *
487
+ * NOTE: This only caches when we're
488
+ * server side.
489
+ */
490
+ cacheData(requestKey, result.data, options.hydrate);
491
+ break;
492
+
493
+ case "error":
494
+ /**
495
+ * Let's cache the error!
496
+ *
497
+ * NOTE: This only caches when we're
498
+ * server side.
499
+ */
500
+ cacheError(requestKey, result.error, options.hydrate);
501
+ break;
502
+ } // For status === "loading":
503
+ // Could never get here unless we wrote
504
+ // the code wrong. Rather than bloat
505
+ // code with useless error, just ignore.
506
+ // For status === "aborted":
507
+ // We won't cache this.
508
+ // We don't hydrate aborted requests,
509
+ // so the client would just see them
510
+ // as unfulfilled data.
511
+
512
+
513
+ return;
514
+ }));
515
+ } catch (e) {
516
+ // This captures if there are problems in the code that
517
+ // begins the requests.
518
+ promises.push(Promise.resolve(cacheError(requestKey, e, options.hydrate)));
519
+ }
485
520
  }
486
521
  /**
487
522
  * Clear out our tracked info.
@@ -489,16 +524,15 @@ class RequestTracker {
489
524
  * We call this now for a simpler API.
490
525
  *
491
526
  * If we reset the tracked calls after all promises resolve, any
492
- * requst tracking done while promises are in flight would be lost.
527
+ * request tracking done while promises are in flight would be lost.
493
528
  *
494
529
  * If we don't reset at all, then we have to expose the `reset` call
495
530
  * for consumers to use, or they'll only ever be able to accumulate
496
531
  * more and more tracked requests, having to fulfill them all every
497
532
  * time.
498
533
  *
499
- * Calling it here means we can have multiple "track -> request" cycles
500
- * in a row and in an easy to reason about manner.
501
- *
534
+ * Calling it here means we can have multiple "track -> request"
535
+ * cycles in a row and in an easy to reason about manner.
502
536
  */
503
537
 
504
538
 
@@ -511,7 +545,7 @@ class RequestTracker {
511
545
  };
512
546
 
513
547
  this._responseCache = responseCache || _ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* SsrCache */ "a"].Default;
514
- this._requestFulfillment = new _request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestFulfillment */ "a"](responseCache);
548
+ this._requestFulfillment = new _request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestFulfillment */ "a"]();
515
549
  }
516
550
  /**
517
551
  * Track a request.
@@ -545,71 +579,96 @@ class RequestTracker {
545
579
  }
546
580
 
547
581
  /***/ }),
548
- /* 7 */
582
+ /* 8 */
549
583
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
550
584
 
551
585
  "use strict";
586
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return clearSharedCache; });
587
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return useSharedCache; });
552
588
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
553
589
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
590
+ /* harmony import */ var _util_data_error_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1);
591
+ /* harmony import */ var _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
592
+
593
+
554
594
 
555
595
 
556
596
  /**
557
- * InterceptContext defines a map from request ID to interception methods.
597
+ * This is the cache.
598
+ * It's incredibly complex.
599
+ * Very in-memory. So cache. Such complex. Wow.
600
+ */
601
+ const cache = new _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_2__[/* ScopedInMemoryCache */ "a"]();
602
+ /**
603
+ * Clear the in-memory cache or a single scope within it.
604
+ */
605
+
606
+ const clearSharedCache = (scope = "") => {
607
+ // If we have a valid scope (empty string is falsy), then clear that scope.
608
+ if (scope && typeof scope === "string") {
609
+ cache.purgeScope(scope);
610
+ } else {
611
+ // Just reset the object. This should be sufficient.
612
+ cache.purgeAll();
613
+ }
614
+ };
615
+ /**
616
+ * Hook to retrieve data from and store data in an in-memory cache.
558
617
  *
559
- * INTERNAL USE ONLY
618
+ * @returns {[?ReadOnlyCacheValue, CacheValueFn]}
619
+ * Returns an array containing the current cache entry (or undefined), a
620
+ * function to set the cache entry (passing null or undefined to this function
621
+ * will delete the entry).
622
+ *
623
+ * To clear a single scope within the cache or the entire cache,
624
+ * the `clearScopedCache` export is available.
625
+ *
626
+ * NOTE: Unlike useState or useReducer, we don't automatically update folks
627
+ * if the value they reference changes. We might add it later (if we need to),
628
+ * but the likelihood here is that things won't be changing in this cache in a
629
+ * way where we would need that. If we do (and likely only in specific
630
+ * circumstances), we should consider adding a simple boolean useState that can
631
+ * be toggled to cause a rerender whenever the referenced cached data changes
632
+ * so that callers can re-render on cache changes. However, we should make
633
+ * sure this toggling is optional - or we could use a callback argument, to
634
+ * achieve this on an as-needed basis.
560
635
  */
561
- const InterceptContext = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createContext"]({});
562
- /* harmony default export */ __webpack_exports__["a"] = (InterceptContext);
563
636
 
564
- /***/ }),
565
- /* 8 */
566
- /***/ (function(module, __webpack_exports__, __webpack_require__) {
637
+ const useSharedCache = (id, scope, initialValue) => {
638
+ // Verify arguments.
639
+ if (!id || typeof id !== "string") {
640
+ throw new _util_data_error_js__WEBPACK_IMPORTED_MODULE_1__[/* DataError */ "a"]("id must be a non-empty string", _util_data_error_js__WEBPACK_IMPORTED_MODULE_1__[/* DataErrors */ "b"].InvalidInput);
641
+ }
567
642
 
568
- "use strict";
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);
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__);
572
- /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(0);
573
- /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
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);
643
+ if (!scope || typeof scope !== "string") {
644
+ throw new _util_data_error_js__WEBPACK_IMPORTED_MODULE_1__[/* DataError */ "a"]("scope must be a non-empty string", _util_data_error_js__WEBPACK_IMPORTED_MODULE_1__[/* DataErrors */ "b"].InvalidInput);
645
+ } // Memoize our APIs.
646
+ // This one allows callers to set or replace the cached value.
576
647
 
577
648
 
649
+ const cacheValue = react__WEBPACK_IMPORTED_MODULE_0__["useCallback"](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
650
+ // since our last run through. Also, our cache does not know what type it
651
+ // stores, so we have to cast it to the type we're exporting. This is a
652
+ // dev time courtesy, rather than a runtime thing.
653
+ // $FlowIgnore[incompatible-type]
578
654
 
655
+ let currentValue = cache.get(scope, id); // If we have an initial value, we need to add it to the cache
656
+ // and use it as our current value.
579
657
 
658
+ if (currentValue == null && initialValue !== undefined) {
659
+ // Get the initial value.
660
+ const value = typeof initialValue === "function" ? initialValue() : initialValue;
580
661
 
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 = (id, handler, hydrate = true) => {
597
- // If we're server-side or hydrating, we'll have a cached entry to use.
598
- // So we get that and use it to initialize our state.
599
- // This works in both hydration and SSR because the very first call to
600
- // this will have cached data in those cases as it will be present on the
601
- // initial render - and subsequent renders on the client it will be null.
602
- const cachedResult = _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_3__[/* SsrCache */ "a"].Default.getEntry(id); // We only track data requests when we are server-side and we don't
603
- // already have a result, as given by the cachedData (which is also the
604
- // initial value for the result state).
662
+ if (value != null) {
663
+ // Update the cache.
664
+ cacheValue(value); // Make sure we return this value as our current value.
605
665
 
606
- const maybeTrack = Object(react__WEBPACK_IMPORTED_MODULE_1__["useContext"])(_util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__[/* TrackerContext */ "b"]);
666
+ currentValue = value;
667
+ }
668
+ } // Now we have everything, let's return it.
607
669
 
608
- if (cachedResult == null && _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
609
- maybeTrack == null ? void 0 : maybeTrack(id, handler, hydrate);
610
- }
611
670
 
612
- return cachedResult;
671
+ return [currentValue, cacheValue];
613
672
  };
614
673
 
615
674
  /***/ }),
@@ -617,22 +676,142 @@ const useServerEffect = (id, handler, hydrate = true) => {
617
676
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
618
677
 
619
678
  "use strict";
620
- /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RequestFulfillment; });
621
- /* harmony import */ var _ssr_cache_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
679
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return ScopedInMemoryCache; });
680
+ /* harmony import */ var _data_error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
622
681
 
623
682
 
624
- let _default;
683
+ /**
684
+ * Describe an in-memory cache.
685
+ */
686
+ class ScopedInMemoryCache {
687
+ constructor(initialCache = {}) {
688
+ this._cache = initialCache;
689
+ }
690
+ /**
691
+ * Indicate if this cache is being used or not.
692
+ *
693
+ * When the cache has entries, returns `true`; otherwise, returns `false`.
694
+ */
625
695
 
626
- class RequestFulfillment {
627
- static get Default() {
628
- if (!_default) {
629
- _default = new RequestFulfillment();
696
+
697
+ get inUse() {
698
+ return Object.keys(this._cache).length > 0;
699
+ }
700
+ /**
701
+ * Set a value in the cache.
702
+ */
703
+
704
+
705
+ set(scope, id, value) {
706
+ var _this$_cache$scope;
707
+
708
+ if (!id || typeof id !== "string") {
709
+ throw new _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataError */ "a"]("id must be non-empty string", _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataErrors */ "b"].InvalidInput);
630
710
  }
631
711
 
632
- return _default;
712
+ if (!scope || typeof scope !== "string") {
713
+ throw new _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataError */ "a"]("scope must be non-empty string", _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataErrors */ "b"].InvalidInput);
714
+ }
715
+
716
+ if (typeof value === "function") {
717
+ throw new _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataError */ "a"]("value must be a non-function value", _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataErrors */ "b"].InvalidInput);
718
+ }
719
+
720
+ this._cache[scope] = (_this$_cache$scope = this._cache[scope]) != null ? _this$_cache$scope : {};
721
+ this._cache[scope][id] = value;
722
+ }
723
+ /**
724
+ * Retrieve a value from the cache.
725
+ */
726
+
727
+
728
+ get(scope, id) {
729
+ var _this$_cache$scope$id, _this$_cache$scope2;
730
+
731
+ return (_this$_cache$scope$id = (_this$_cache$scope2 = this._cache[scope]) == null ? void 0 : _this$_cache$scope2[id]) != null ? _this$_cache$scope$id : null;
633
732
  }
733
+ /**
734
+ * Purge an item from the cache.
735
+ */
634
736
 
635
- constructor(responseCache = undefined) {
737
+
738
+ purge(scope, id) {
739
+ var _this$_cache$scope3;
740
+
741
+ if (!((_this$_cache$scope3 = this._cache[scope]) != null && _this$_cache$scope3[id])) {
742
+ return;
743
+ }
744
+
745
+ delete this._cache[scope][id];
746
+
747
+ if (Object.keys(this._cache[scope]).length === 0) {
748
+ delete this._cache[scope];
749
+ }
750
+ }
751
+ /**
752
+ * Purge a scope of items that match the given predicate.
753
+ *
754
+ * If the predicate is omitted, then all items in the scope are purged.
755
+ */
756
+
757
+
758
+ purgeScope(scope, predicate) {
759
+ if (!this._cache[scope]) {
760
+ return;
761
+ }
762
+
763
+ if (predicate == null) {
764
+ delete this._cache[scope];
765
+ return;
766
+ }
767
+
768
+ for (const key of Object.keys(this._cache[scope])) {
769
+ if (predicate(key, this._cache[scope][key])) {
770
+ delete this._cache[scope][key];
771
+ }
772
+ }
773
+
774
+ if (Object.keys(this._cache[scope]).length === 0) {
775
+ delete this._cache[scope];
776
+ }
777
+ }
778
+ /**
779
+ * Purge all items from the cache that match the given predicate.
780
+ *
781
+ * If the predicate is omitted, then all items in the cache are purged.
782
+ */
783
+
784
+
785
+ purgeAll(predicate) {
786
+ if (predicate == null) {
787
+ this._cache = {};
788
+ return;
789
+ }
790
+
791
+ for (const scope of Object.keys(this._cache)) {
792
+ this.purgeScope(scope, (id, value) => predicate(scope, id, value));
793
+ }
794
+ }
795
+
796
+ }
797
+
798
+ /***/ }),
799
+ /* 10 */
800
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
801
+
802
+ "use strict";
803
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RequestFulfillment; });
804
+ /* harmony import */ var _data_error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
805
+
806
+
807
+ let _default;
808
+ /**
809
+ * This fulfills a request, making sure that in-flight requests are shared.
810
+ */
811
+
812
+
813
+ class RequestFulfillment {
814
+ constructor() {
636
815
  this._requests = {};
637
816
 
638
817
  this.fulfill = (id, {
@@ -652,173 +831,539 @@ class RequestFulfillment {
652
831
  */
653
832
 
654
833
 
655
- const {
656
- cacheData,
657
- cacheError
658
- } = this._responseCache;
834
+ const request = handler().then(data => ({
835
+ status: "success",
836
+ data
837
+ })).catch(error => {
838
+ const actualError = typeof error === "string" ? new _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataError */ "a"]("Request failed", _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataErrors */ "b"].Unknown, {
839
+ metadata: {
840
+ unexpectedError: error
841
+ }
842
+ }) : error; // Return aborted result if the request was aborted.
843
+ // The only way to detect this reliably, it seems, is to
844
+ // check the error name and see if it's "AbortError" (this
845
+ // is also what Apollo does).
846
+ // Even then, it's reliant on the handler supporting aborts.
847
+ // TODO(somewhatabstract, FEI-4276): Add first class abort
848
+ // support to the handler API.
849
+
850
+ if (actualError.name === "AbortError") {
851
+ return {
852
+ status: "aborted"
853
+ };
854
+ }
659
855
 
660
- try {
661
- const request = handler().then(data => {
662
- delete this._requests[id];
856
+ return {
857
+ status: "error",
858
+ error: actualError
859
+ };
860
+ }).finally(() => {
861
+ delete this._requests[id];
862
+ }); // Store the request in our cache.
663
863
 
664
- if (data == null) {
665
- // Request aborted. We won't cache this.
666
- return null;
667
- }
668
- /**
669
- * Let's cache the data!
670
- *
671
- * NOTE: This only caches when we're server side.
672
- */
673
-
674
-
675
- return cacheData(id, data, hydrate);
676
- }).catch(error => {
677
- delete this._requests[id];
678
- /**
679
- * Let's cache the error!
680
- *
681
- * NOTE: This only caches when we're server side.
682
- */
683
-
684
- return cacheError(id, error, hydrate);
685
- });
686
- this._requests[id] = request;
687
- return request;
688
- } catch (e) {
689
- /**
690
- * In this case, we don't cache an inflight request, because there
691
- * really isn't one.
692
- */
693
- return Promise.resolve(cacheError(id, e, hydrate));
694
- }
864
+ this._requests[id] = request;
865
+ return request;
695
866
  };
867
+ }
868
+
869
+ static get Default() {
870
+ if (!_default) {
871
+ _default = new RequestFulfillment();
872
+ }
873
+
874
+ return _default;
875
+ }
876
+
877
+ }
878
+
879
+ /***/ }),
880
+ /* 11 */
881
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
882
+
883
+ "use strict";
884
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return WhenClientSide; });
885
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return useHydratableEffect; });
886
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
887
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
888
+ /* harmony import */ var _util_abort_error_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(24);
889
+ /* harmony import */ var _use_server_effect_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(14);
890
+ /* harmony import */ var _use_shared_cache_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8);
891
+ /* harmony import */ var _use_cached_effect_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(15);
892
+
893
+
894
+
895
+
896
+
897
+
898
+ /**
899
+ * Policies to define how a hydratable effect should behave client-side.
900
+ */
901
+ const WhenClientSide = __webpack_require__(29).Mirrored(["DoNotHydrate", "ExecuteWhenNoResult", "ExecuteWhenNoSuccessResult", "AlwaysExecute"]);
902
+ const DefaultScope = "useHydratableEffect";
903
+ /**
904
+ * Hook to execute an async operation on server and client.
905
+ *
906
+ * This hook executes the given handler on the server and on the client,
907
+ * and, depending on the given options, can hydrate the server-side result.
908
+ *
909
+ * Results are cached on the client so they can be shared between equivalent
910
+ * invocations. Cache changes from one hook instance do not trigger renders
911
+ * in components that use the same requestID.
912
+ */
913
+
914
+ const useHydratableEffect = (requestId, handler, options = {}) => {
915
+ const {
916
+ clientBehavior = WhenClientSide.ExecuteWhenNoSuccessResult,
917
+ skip = false,
918
+ retainResultOnChange = false,
919
+ onResultChanged,
920
+ scope = DefaultScope
921
+ } = options; // Now we instruct the server to perform the operation.
922
+ // When client-side, this will look up any response for hydration; it does
923
+ // not invoke the handler.
924
+
925
+ const serverResult = Object(_use_server_effect_js__WEBPACK_IMPORTED_MODULE_2__[/* useServerEffect */ "a"])(requestId, // If we're skipped (unlikely in server worlds, but maybe),
926
+ // just give an aborted response.
927
+ skip ? () => Promise.reject(new _util_abort_error_js__WEBPACK_IMPORTED_MODULE_1__[/* AbortError */ "a"]("skipped")) : handler, // Only hydrate if our behavior isn't telling us not to.
928
+ clientBehavior !== WhenClientSide.DoNotHydrate);
929
+ const getDefaultCacheValue = react__WEBPACK_IMPORTED_MODULE_0__["useCallback"](() => {
930
+ // If we don't have a requestId, it's our first render, the one
931
+ // where we hydrated. So defer to our clientBehavior value.
932
+ switch (clientBehavior) {
933
+ case WhenClientSide.DoNotHydrate:
934
+ case WhenClientSide.AlwaysExecute:
935
+ // Either we weren't hydrating at all, or we don't care
936
+ // if we hydrated something or not, either way, we're
937
+ // doing a request.
938
+ return null;
939
+
940
+ case WhenClientSide.ExecuteWhenNoResult:
941
+ // We only execute if we didn't hydrate something.
942
+ // So, returning the hydration result as default for our
943
+ // cache, will then prevent the cached effect running.
944
+ return serverResult;
945
+
946
+ case WhenClientSide.ExecuteWhenNoSuccessResult:
947
+ // We only execute if we didn't hydrate a success result.
948
+ if ((serverResult == null ? void 0 : serverResult.status) === "success") {
949
+ // So, returning the hydration result as default for our
950
+ // cache, will then prevent the cached effect running.
951
+ return serverResult;
952
+ }
953
+
954
+ return null;
955
+ } // There is no reason for this to change after the first render.
956
+ // eslint-disable-next-line react-hooks/exhaustive-deps
957
+
958
+ }, []); // Instead of using state, which would be local to just this hook instance,
959
+ // we use a shared in-memory cache.
960
+
961
+ Object(_use_shared_cache_js__WEBPACK_IMPORTED_MODULE_3__[/* useSharedCache */ "b"])(requestId, // The key of the cached item
962
+ scope, // The scope of the cached items
963
+ getDefaultCacheValue); // When we're client-side, we ultimately want the result from this call.
964
+
965
+ const clientResult = Object(_use_cached_effect_js__WEBPACK_IMPORTED_MODULE_4__[/* useCachedEffect */ "a"])(requestId, handler, {
966
+ skip,
967
+ onResultChanged,
968
+ retainResultOnChange,
969
+ scope
970
+ }); // OK, now which result do we return.
971
+ // Well, we return the serverResult on our very first call and then
972
+ // the clientResult thereafter. The great thing is that after the very
973
+ // first call, the serverResult is going to be `null` anyway.
974
+
975
+ return serverResult != null ? serverResult : clientResult;
976
+ };
977
+
978
+ /***/ }),
979
+ /* 12 */
980
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
981
+
982
+ "use strict";
983
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return SerializableInMemoryCache; });
984
+ /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
985
+ /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__);
986
+ /* harmony import */ var _data_error_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1);
987
+ /* harmony import */ var _scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
988
+
696
989
 
697
- this._responseCache = responseCache || _ssr_cache_js__WEBPACK_IMPORTED_MODULE_0__[/* SsrCache */ "a"].Default;
990
+
991
+
992
+ /**
993
+ * Describe a serializable in-memory cache.
994
+ */
995
+ class SerializableInMemoryCache extends _scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_2__[/* ScopedInMemoryCache */ "a"] {
996
+ constructor(initialCache = {}) {
997
+ try {
998
+ super(Object(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["clone"])(initialCache));
999
+ } catch (e) {
1000
+ throw new _data_error_js__WEBPACK_IMPORTED_MODULE_1__[/* DataError */ "a"](`An error occurred trying to initialize from a response cache snapshot: ${e}`, _data_error_js__WEBPACK_IMPORTED_MODULE_1__[/* DataErrors */ "b"].InvalidInput);
1001
+ }
698
1002
  }
699
1003
  /**
700
- * Get a promise of a request for a given handler and options.
701
- *
702
- * This will return an inflight request if one exists, otherwise it will
703
- * make a new request. Inflight requests are deleted once they resolve.
1004
+ * Set a value in the cache.
704
1005
  */
705
1006
 
706
1007
 
1008
+ set(scope, id, value) {
1009
+ super.set(scope, id, Object.freeze(Object(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["clone"])(value)));
1010
+ }
1011
+ /**
1012
+ * Clone the cache.
1013
+ */
1014
+
1015
+
1016
+ clone() {
1017
+ try {
1018
+ return Object(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["clone"])(this._cache);
1019
+ } catch (e) {
1020
+ throw new _data_error_js__WEBPACK_IMPORTED_MODULE_1__[/* DataError */ "a"]("An error occurred while trying to clone the cache", _data_error_js__WEBPACK_IMPORTED_MODULE_1__[/* DataErrors */ "b"].Internal, {
1021
+ cause: e
1022
+ });
1023
+ }
1024
+ }
1025
+
707
1026
  }
708
1027
 
709
1028
  /***/ }),
710
- /* 10 */
1029
+ /* 13 */
711
1030
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
712
1031
 
713
1032
  "use strict";
714
- /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return GqlRouterContext; });
715
1033
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
716
1034
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
717
1035
 
718
- const GqlRouterContext = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createContext"](null);
1036
+
1037
+ /**
1038
+ * InterceptContext defines a map from request ID to interception methods.
1039
+ *
1040
+ * INTERNAL USE ONLY
1041
+ */
1042
+ const InterceptContext = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createContext"]([]);
1043
+ /* harmony default export */ __webpack_exports__["a"] = (InterceptContext);
719
1044
 
720
1045
  /***/ }),
721
- /* 11 */
1046
+ /* 14 */
722
1047
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
723
1048
 
724
1049
  "use strict";
725
- /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return clearSharedCache; });
726
- /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return useSharedCache; });
727
- /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
728
- /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
729
- /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1);
730
- /* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_1__);
731
- /* harmony import */ var _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);
1050
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useServerEffect; });
1051
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
1052
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__);
1053
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(0);
1054
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
1055
+ /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7);
1056
+ /* harmony import */ var _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5);
1057
+ /* harmony import */ var _util_result_from_cache_response_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(25);
1058
+ /* harmony import */ var _use_request_interception_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(16);
1059
+
1060
+
1061
+
732
1062
 
733
1063
 
734
1064
 
735
1065
 
736
1066
  /**
737
- * This is the cache.
738
- * It's incredibly complex.
739
- * Very in-memory. So cache. Such complex. Wow.
1067
+ * Hook to perform an asynchronous action during server-side rendering.
1068
+ *
1069
+ * This hook registers an asynchronous action to be performed during
1070
+ * server-side rendering. The action is performed only once, and the result
1071
+ * is cached against the given identifier so that subsequent calls return that
1072
+ * cached result allowing components to render more of the component.
1073
+ *
1074
+ * This hook requires the Wonder Blocks Data functionality for resolving
1075
+ * pending requests, as well as support for the hydration cache to be
1076
+ * embedded into a page so that the result can by hydrated (if that is a
1077
+ * requirement).
1078
+ *
1079
+ * The asynchronous action is never invoked on the client-side.
740
1080
  */
741
- const cache = new _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_2__[/* ScopedInMemoryCache */ "a"]();
1081
+ const useServerEffect = (requestId, handler, hydrate = true) => {
1082
+ // Plug in to the request interception framework for code that wants
1083
+ // to use that.
1084
+ const interceptedHandler = Object(_use_request_interception_js__WEBPACK_IMPORTED_MODULE_5__[/* useRequestInterception */ "a"])(requestId, handler); // If we're server-side or hydrating, we'll have a cached entry to use.
1085
+ // So we get that and use it to initialize our state.
1086
+ // This works in both hydration and SSR because the very first call to
1087
+ // this will have cached data in those cases as it will be present on the
1088
+ // initial render - and subsequent renders on the client it will be null.
1089
+
1090
+ 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
1091
+ // already have a result, as given by the cachedData (which is also the
1092
+ // initial value for the result state).
1093
+
1094
+ const maybeTrack = Object(react__WEBPACK_IMPORTED_MODULE_1__["useContext"])(_util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__[/* TrackerContext */ "b"]);
1095
+
1096
+ if (cachedResult == null && _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
1097
+ maybeTrack == null ? void 0 : maybeTrack(requestId, interceptedHandler, hydrate);
1098
+ } // A null result means there was no result to hydrate.
1099
+
1100
+
1101
+ return cachedResult == null ? null : Object(_util_result_from_cache_response_js__WEBPACK_IMPORTED_MODULE_4__[/* resultFromCachedResponse */ "a"])(cachedResult);
1102
+ };
1103
+
1104
+ /***/ }),
1105
+ /* 15 */
1106
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
1107
+
1108
+ "use strict";
1109
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useCachedEffect; });
1110
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
1111
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
1112
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
1113
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__);
1114
+ /* harmony import */ var _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10);
1115
+ /* harmony import */ var _util_status_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4);
1116
+ /* harmony import */ var _use_shared_cache_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(8);
1117
+ /* harmony import */ var _use_request_interception_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(16);
1118
+
1119
+
1120
+
1121
+
1122
+
1123
+
1124
+ const DefaultScope = "useCachedEffect";
742
1125
  /**
743
- * Clear the in-memory cache or a single scope within it.
1126
+ * Hook to execute and cache an async operation on the client.
1127
+ *
1128
+ * This hook executes the given handler on the client if there is no
1129
+ * cached result to use.
1130
+ *
1131
+ * Results are cached so they can be shared between equivalent invocations.
1132
+ * In-flight requests are also shared, so that concurrent calls will
1133
+ * behave as one might exect. Cache updates invoked by one hook instance
1134
+ * do not trigger renders in components that use the same requestID; however,
1135
+ * that should not matter since concurrent requests will share the same
1136
+ * in-flight request, and subsequent renders will grab from the cache.
1137
+ *
1138
+ * Once the request has been tried once and a non-loading response has been
1139
+ * cached, the request will not executed made again.
744
1140
  */
745
1141
 
746
- const clearSharedCache = (scope = "") => {
747
- // If we have a valid scope (empty string is falsy), then clear that scope.
748
- if (scope && typeof scope === "string") {
749
- cache.purgeScope(scope);
750
- } else {
751
- // Just reset the object. This should be sufficient.
752
- cache.purgeAll();
753
- }
1142
+ const useCachedEffect = (requestId, handler, options = {}) => {
1143
+ const {
1144
+ skip: hardSkip = false,
1145
+ retainResultOnChange = false,
1146
+ onResultChanged,
1147
+ scope = DefaultScope
1148
+ } = options; // Plug in to the request interception framework for code that wants
1149
+ // to use that.
1150
+
1151
+ const interceptedHandler = Object(_use_request_interception_js__WEBPACK_IMPORTED_MODULE_5__[/* useRequestInterception */ "a"])(requestId, handler); // Instead of using state, which would be local to just this hook instance,
1152
+ // we use a shared in-memory cache.
1153
+
1154
+ const [mostRecentResult, setMostRecentResult] = Object(_use_shared_cache_js__WEBPACK_IMPORTED_MODULE_4__[/* useSharedCache */ "b"])(requestId, // The key of the cached item
1155
+ scope // The scope of the cached items
1156
+ // No default value. We don't want the loading status there; to ensure
1157
+ // that all calls when the request is in-flight will update once that
1158
+ // request is done, we want the cache to be empty until that point.
1159
+ ); // Build a function that will update the cache and either invoke the
1160
+ // callback provided in options, or force an update.
1161
+
1162
+ const forceUpdate = Object(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__["useForceUpdate"])();
1163
+ const setCacheAndNotify = react__WEBPACK_IMPORTED_MODULE_0__["useCallback"](value => {
1164
+ setMostRecentResult(value); // If our caller provided a cacheUpdated callback, we use that.
1165
+ // Otherwise, we toggle our little state update.
1166
+
1167
+ if (onResultChanged != null) {
1168
+ onResultChanged(value);
1169
+ } else {
1170
+ forceUpdate();
1171
+ }
1172
+ }, [setMostRecentResult, onResultChanged, forceUpdate]); // We need to trigger a re-render when the request ID changes as that
1173
+ // indicates its a different request. We don't default the current id as
1174
+ // this is a proxy for the first render, where we will make the request
1175
+ // if we don't already have a cached value.
1176
+
1177
+ const requestIdRef = react__WEBPACK_IMPORTED_MODULE_0__["useRef"]();
1178
+ const previousRequestId = requestIdRef.current; // Calculate our soft skip state.
1179
+ // Soft skip changes are things that should skip the effect if something
1180
+ // else triggers the effect to run, but should not itself trigger the effect
1181
+ // (which would cancel a previous invocation).
1182
+
1183
+ const softSkip = react__WEBPACK_IMPORTED_MODULE_0__["useMemo"](() => {
1184
+ if (requestId === previousRequestId) {
1185
+ // If the requestId is unchanged, it means we already rendered at
1186
+ // least once and so we already made the request at least once. So
1187
+ // we can bail out right here.
1188
+ return true;
1189
+ } // If we already have a cached value, we're going to skip.
1190
+
1191
+
1192
+ if (mostRecentResult != null) {
1193
+ return true;
1194
+ }
1195
+
1196
+ return false;
1197
+ }, [requestId, previousRequestId, mostRecentResult]); // So now we make sure the client-side request happens per our various
1198
+ // options.
1199
+
1200
+ react__WEBPACK_IMPORTED_MODULE_0__["useEffect"](() => {
1201
+ let cancel = false; // We don't do anything if we've been told to hard skip (a hard skip
1202
+ // means we should cancel the previous request and is therefore a
1203
+ // dependency on that), or we have determined we have already done
1204
+ // enough and can soft skip (a soft skip doesn't trigger the request
1205
+ // to re-run; we don't want to cancel the in progress effect if we're
1206
+ // soft skipping.
1207
+
1208
+ if (hardSkip || softSkip) {
1209
+ return;
1210
+ } // If we got here, we're going to perform the request.
1211
+ // Let's make sure our ref is set to the most recent requestId.
1212
+
1213
+
1214
+ requestIdRef.current = requestId; // OK, we've done all our checks and things. It's time to make the
1215
+ // request. We use our request fulfillment here so that in-flight
1216
+ // requests are shared.
1217
+ // NOTE: Our request fulfillment handles the error cases here.
1218
+ // Catching shouldn't serve a purpose.
1219
+ // eslint-disable-next-line promise/catch-or-return
1220
+
1221
+ _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestFulfillment */ "a"].Default.fulfill(requestId, {
1222
+ handler: interceptedHandler
1223
+ }).then(result => {
1224
+ if (cancel) {
1225
+ // We don't modify our result if an earlier effect was
1226
+ // cancelled as it means that this hook no longer cares about
1227
+ // that old request.
1228
+ return;
1229
+ }
1230
+
1231
+ setCacheAndNotify(result);
1232
+ return; // Shut up eslint always-return rule.
1233
+ });
1234
+ return () => {
1235
+ // TODO(somewhatabstract, FEI-4276): Eventually, we will want to be
1236
+ // able abort in-flight requests, but for now, we don't have that.
1237
+ // (Of course, we will only want to abort them if no one is waiting
1238
+ // on them)
1239
+ // For now, we just block cancelled requests from changing our
1240
+ // cache.
1241
+ cancel = true;
1242
+ }; // We only want to run this effect if the requestId, or skip values
1243
+ // change. These are the only two things that should affect the
1244
+ // cancellation of a pending request. We do not update if the handler
1245
+ // changes, in order to simplify the API - otherwise, callers would
1246
+ // not be able to use inline functions with this hook.
1247
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1248
+ }, [hardSkip, requestId]); // We track the last result we returned in order to support the
1249
+ // "retainResultOnChange" option.
1250
+
1251
+ const lastResultAgnosticOfIdRef = react__WEBPACK_IMPORTED_MODULE_0__["useRef"](_util_status_js__WEBPACK_IMPORTED_MODULE_3__[/* Status */ "a"].loading());
1252
+ const loadingResult = retainResultOnChange ? lastResultAgnosticOfIdRef.current : _util_status_js__WEBPACK_IMPORTED_MODULE_3__[/* Status */ "a"].loading(); // Loading is a transient state, so we only use it here; it's not something
1253
+ // we cache.
1254
+
1255
+ const result = react__WEBPACK_IMPORTED_MODULE_0__["useMemo"](() => mostRecentResult != null ? mostRecentResult : loadingResult, [mostRecentResult, loadingResult]);
1256
+ lastResultAgnosticOfIdRef.current = result;
1257
+ return result;
754
1258
  };
1259
+
1260
+ /***/ }),
1261
+ /* 16 */
1262
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
1263
+
1264
+ "use strict";
1265
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useRequestInterception; });
1266
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
1267
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
1268
+ /* harmony import */ var _components_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(13);
1269
+
1270
+
1271
+
755
1272
  /**
756
- * Hook to retrieve data from and store data in an in-memory cache.
757
- *
758
- * @returns {[?ReadOnlyCacheValue, CacheValueFn]}
759
- * Returns an array containing the current cache entry (or undefined), a
760
- * function to set the cache entry (passing null or undefined to this function
761
- * will delete the entry).
1273
+ * Allow request handling to be intercepted.
762
1274
  *
763
- * To clear a single scope within the cache or the entire cache,
764
- * the `clearScopedCache` export is available.
1275
+ * Hook to take a uniquely identified request handler and return a
1276
+ * method that will support request interception from the InterceptRequest
1277
+ * component.
765
1278
  *
766
- * NOTE: Unlike useState or useReducer, we don't automatically update folks
767
- * if the value they reference changes. We might add it later (if we need to),
768
- * but the likelihood here is that things won't be changing in this cache in a
769
- * way where we would need that. If we do (and likely only in specific
770
- * circumstances), we should consider adding a simple boolean useState that can
771
- * be toggled to cause a rerender whenever the referenced cached data changes
772
- * so that callers can re-render on cache changes. However, we should make
773
- * sure this toggling is optional - or we could use a callback argument, to
774
- * achieve this on an as-needed basis.
1279
+ * If you want request interception to be supported with `useServerEffect` or
1280
+ * any client-side effect that uses the handler, call this first to generate
1281
+ * an intercepted handler, and then invoke `useServerEffect` (or other things)
1282
+ * with that intercepted handler.
775
1283
  */
1284
+ const useRequestInterception = (requestId, handler) => {
1285
+ // Get the interceptors that have been registered.
1286
+ 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
1287
+ // request is intercepted before ultimately calling the original handler
1288
+ // if nothing intercepted it.
1289
+ // We memoize this so that it only changes if something related to it
1290
+ // changes.
1291
+
1292
+ const interceptedHandler = react__WEBPACK_IMPORTED_MODULE_0__["useCallback"](() => {
1293
+ // Call the interceptors from closest to furthest.
1294
+ // If one returns a non-null result, then we keep that.
1295
+ const interceptResponse = interceptors.reduceRight((prev, interceptor) => {
1296
+ if (prev != null) {
1297
+ return prev;
1298
+ }
776
1299
 
777
- const useSharedCache = (id, scope, initialValue) => {
778
- // Verify arguments.
779
- if (!id || typeof id !== "string") {
780
- 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);
781
- }
782
-
783
- if (!scope || typeof scope !== "string") {
784
- 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);
785
- } // Memoize our APIs.
786
- // This one allows callers to set or replace the cached value.
787
-
1300
+ return interceptor(requestId);
1301
+ }, null); // If nothing intercepted this request, invoke the original handler.
1302
+ // NOTE: We can't guarantee all interceptors return the same type
1303
+ // as our handler, so how can flow know? Let's just suppress that.
1304
+ // $FlowFixMe[incompatible-return]
788
1305
 
789
- 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
790
- // since our last run through. Also, our cache does not know what type it
791
- // stores, so we have to cast it to the type we're exporting. This is a
792
- // dev time courtesy, rather than a runtime thing.
793
- // $FlowIgnore[incompatible-type]
1306
+ return interceptResponse != null ? interceptResponse : handler();
1307
+ }, [handler, interceptors, requestId]);
1308
+ return interceptedHandler;
1309
+ };
794
1310
 
795
- let currentValue = cache.get(scope, id); // If we have an initial value, we need to add it to the cache
796
- // and use it as our current value.
1311
+ /***/ }),
1312
+ /* 17 */
1313
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
797
1314
 
798
- if (currentValue == null && initialValue !== undefined) {
799
- // Get the initial value.
800
- const value = typeof initialValue === "function" ? initialValue() : initialValue; // Update the cache.
1315
+ "use strict";
1316
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return GqlRouterContext; });
1317
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
1318
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
801
1319
 
802
- cacheValue(value); // Make sure we return this value as our current value.
1320
+ const GqlRouterContext = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createContext"](null);
803
1321
 
804
- currentValue = value;
805
- } // Now we have everything, let's return it.
1322
+ /***/ }),
1323
+ /* 18 */
1324
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
806
1325
 
1326
+ "use strict";
1327
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return mergeGqlContext; });
1328
+ /**
1329
+ * Construct a complete GqlContext from current defaults and a partial context.
1330
+ *
1331
+ * Values in the partial context that are `undefined` will be ignored.
1332
+ * Values in the partial context that are `null` will be deleted.
1333
+ */
1334
+ const mergeGqlContext = (defaultContext, overrides) => {
1335
+ // Let's merge the partial context default context. We deliberately
1336
+ // don't spread because spreading would overwrite default context
1337
+ // values with undefined or null if the partial context includes a value
1338
+ // explicitly set to undefined or null.
1339
+ return Object.keys(overrides).reduce((acc, key) => {
1340
+ // Undefined values are ignored.
1341
+ if (overrides[key] !== undefined) {
1342
+ if (overrides[key] === null) {
1343
+ // Null indicates we delete this context value.
1344
+ delete acc[key];
1345
+ } else {
1346
+ // Otherwise, we set it.
1347
+ acc[key] = overrides[key];
1348
+ }
1349
+ }
807
1350
 
808
- return [currentValue, cacheValue];
1351
+ return acc;
1352
+ }, { ...defaultContext
1353
+ });
809
1354
  };
810
1355
 
811
1356
  /***/ }),
812
- /* 12 */
1357
+ /* 19 */
813
1358
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
814
1359
 
815
1360
  "use strict";
816
1361
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return TrackData; });
817
1362
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
818
1363
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
819
- /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
1364
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
820
1365
  /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__);
821
- /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);
1366
+ /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7);
822
1367
 
823
1368
 
824
1369
 
@@ -840,22 +1385,13 @@ class TrackData extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
840
1385
  }
841
1386
 
842
1387
  /***/ }),
843
- /* 13 */
1388
+ /* 20 */
844
1389
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
845
1390
 
846
1391
  "use strict";
847
1392
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
848
1393
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
849
- /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
850
- /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__);
851
- /* harmony import */ var _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
852
- /* harmony import */ var _intercept_context_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7);
853
- /* harmony import */ var _hooks_use_server_effect_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(8);
854
- /* harmony import */ var _util_result_from_cache_response_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(17);
855
-
856
-
857
-
858
-
1394
+ /* harmony import */ var _hooks_use_hydratable_effect_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11);
859
1395
 
860
1396
 
861
1397
 
@@ -868,118 +1404,26 @@ const Data = ({
868
1404
  requestId,
869
1405
  handler,
870
1406
  children,
871
- hydrate,
872
- showOldDataWhileLoading,
873
- alwaysRequestOnHydration
1407
+ retainResultOnChange = false,
1408
+ clientBehavior = _hooks_use_hydratable_effect_js__WEBPACK_IMPORTED_MODULE_1__[/* WhenClientSide */ "a"].ExecuteWhenNoSuccessResult
874
1409
  }) => {
875
- // Lookup to see if there's an interceptor for the handler.
876
- // If we have one, we need to replace the handler with one that
877
- // uses the interceptor.
878
- const interceptorMap = react__WEBPACK_IMPORTED_MODULE_0__["useContext"](_intercept_context_js__WEBPACK_IMPORTED_MODULE_3__[/* default */ "a"]); // If we have an interceptor, we need to replace the handler with one
879
- // that uses the interceptor. This helper function generates a new
880
- // handler.
881
-
882
- const maybeInterceptedHandler = react__WEBPACK_IMPORTED_MODULE_0__["useMemo"](() => {
883
- const interceptor = interceptorMap[requestId];
884
-
885
- if (interceptor == null) {
886
- return handler;
887
- }
888
-
889
- return () => {
890
- var _interceptor;
891
-
892
- return (_interceptor = interceptor()) != null ? _interceptor : handler();
893
- };
894
- }, [handler, interceptorMap, requestId]);
895
- const hydrateResult = Object(_hooks_use_server_effect_js__WEBPACK_IMPORTED_MODULE_4__[/* useServerEffect */ "a"])(requestId, maybeInterceptedHandler, hydrate);
896
- const [currentResult, setResult] = react__WEBPACK_IMPORTED_MODULE_0__["useState"](hydrateResult); // Here we make sure the request still occurs client-side as needed.
897
- // This is for legacy usage that expects this. Eventually we will want
898
- // to deprecate.
899
-
900
- react__WEBPACK_IMPORTED_MODULE_0__["useEffect"](() => {
901
- // This is here until I can do a better documentation example for
902
- // the TrackData docs.
903
- // istanbul ignore next
904
- if (_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_1__["Server"].isServerSide()) {
905
- return;
906
- } // We don't bother with this if we have hydration data and we're not
907
- // forcing a request on hydration.
908
- // We don't care if these things change after the first render,
909
- // so we don't want them in the inputs array.
910
-
911
-
912
- if (!alwaysRequestOnHydration && (hydrateResult == null ? void 0 : hydrateResult.data) != null) {
913
- return;
914
- } // If we're not hydrating a result and we're not going to render
915
- // with old data until we're loaded, we want to make sure we set our
916
- // result to null so that we're in the loading state.
917
-
918
-
919
- if (!showOldDataWhileLoading) {
920
- // Mark ourselves as loading.
921
- setResult(null);
922
- } // We aren't server-side, so let's make the request.
923
- // We don't need to use our built-in request fulfillment here if we
924
- // don't want, but it does mean we'll share inflight requests for the
925
- // same ID and the result will be in the same format as the
926
- // hydrated value.
927
-
928
-
929
- let cancel = false;
930
- _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestFulfillment */ "a"].Default.fulfill(requestId, {
931
- handler: maybeInterceptedHandler
932
- }).then(result => {
933
- if (cancel) {
934
- return;
935
- }
936
-
937
- setResult(result);
938
- return;
939
- }).catch(e => {
940
- if (cancel) {
941
- return;
942
- }
943
- /**
944
- * We should never get here as errors in fulfillment are part
945
- * of the `then`, but if we do.
946
- */
947
- // eslint-disable-next-line no-console
948
-
949
-
950
- console.error(`Unexpected error occurred during data fulfillment: ${e}`);
951
- setResult({
952
- error: typeof e === "string" ? e : e.message
953
- });
954
- return;
955
- });
956
- return () => {
957
- cancel = true;
958
- }; // If the handler changes, we don't care. The ID is what indicates
959
- // the request that should be made and folks shouldn't be changing the
960
- // handler without changing the ID as well.
961
- // In addition, we don't want to include hydrateResult nor
962
- // alwaysRequestOnHydration as them changinng after the first pass
963
- // is irrelevant.
964
- // Finally, we don't want to include showOldDataWhileLoading as that
965
- // changing on its own is also not relevant. It only matters if the
966
- // request itself changes. All of which is to say that we only
967
- // run this effect for the ID changing.
968
- // eslint-disable-next-line react-hooks/exhaustive-deps
969
- }, [requestId]);
970
- return children(Object(_util_result_from_cache_response_js__WEBPACK_IMPORTED_MODULE_5__[/* resultFromCachedResponse */ "a"])(currentResult));
1410
+ const result = Object(_hooks_use_hydratable_effect_js__WEBPACK_IMPORTED_MODULE_1__[/* useHydratableEffect */ "b"])(requestId, handler, {
1411
+ retainResultOnChange,
1412
+ clientBehavior
1413
+ });
1414
+ return children(result);
971
1415
  };
972
1416
 
973
1417
  /* harmony default export */ __webpack_exports__["a"] = (Data);
974
1418
 
975
1419
  /***/ }),
976
- /* 14 */
1420
+ /* 21 */
977
1421
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
978
1422
 
979
1423
  "use strict";
980
1424
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
981
1425
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
982
- /* harmony import */ var _intercept_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7);
1426
+ /* harmony import */ var _intercept_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(13);
983
1427
 
984
1428
 
985
1429
 
@@ -993,36 +1437,35 @@ const Data = ({
993
1437
  * be picked up by `Data` component requests from outside the children of this
994
1438
  * component.
995
1439
  *
996
- * These components do not chain. If a different `InterceptData` instance is
997
- * rendered within this one that intercepts the same id, then that
998
- * new instance will replace this interceptor for its children. All methods
999
- * will be replaced.
1440
+ * Interceptions within the same component tree are chained such that the
1441
+ * interceptor closest to the intercepted request is called first, and the
1442
+ * furthest interceptor is called last.
1000
1443
  */
1001
- const InterceptData = ({
1002
- requestId,
1003
- handler,
1444
+ const InterceptRequests = ({
1445
+ interceptor,
1004
1446
  children
1005
1447
  }) => {
1006
- const interceptMap = react__WEBPACK_IMPORTED_MODULE_0__["useContext"](_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"]);
1007
- const updatedInterceptMap = react__WEBPACK_IMPORTED_MODULE_0__["useMemo"](() => ({ ...interceptMap,
1008
- [requestId]: handler
1009
- }), [interceptMap, requestId, handler]);
1448
+ const interceptors = react__WEBPACK_IMPORTED_MODULE_0__["useContext"](_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"]);
1449
+ const updatedInterceptors = react__WEBPACK_IMPORTED_MODULE_0__["useMemo"]( // We could build this in reverse order so that our hook that does
1450
+ // the interception didn't have to use reduceRight, but I think it
1451
+ // is easier to think about if we do this in component tree order.
1452
+ () => [].concat(interceptors, [interceptor]), [interceptors, interceptor]);
1010
1453
  return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_intercept_context_js__WEBPACK_IMPORTED_MODULE_1__[/* default */ "a"].Provider, {
1011
- value: updatedInterceptMap
1454
+ value: updatedInterceptors
1012
1455
  }, children);
1013
1456
  };
1014
1457
 
1015
- /* harmony default export */ __webpack_exports__["a"] = (InterceptData);
1458
+ /* harmony default export */ __webpack_exports__["a"] = (InterceptRequests);
1016
1459
 
1017
1460
  /***/ }),
1018
- /* 15 */
1461
+ /* 22 */
1019
1462
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1020
1463
 
1021
1464
  "use strict";
1022
1465
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return GqlRouter; });
1023
1466
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
1024
1467
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
1025
- /* harmony import */ var _util_gql_router_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(10);
1468
+ /* harmony import */ var _util_gql_router_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(17);
1026
1469
 
1027
1470
 
1028
1471
 
@@ -1059,16 +1502,16 @@ const GqlRouter = ({
1059
1502
  };
1060
1503
 
1061
1504
  /***/ }),
1062
- /* 16 */
1505
+ /* 23 */
1063
1506
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1064
1507
 
1065
1508
  "use strict";
1066
1509
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useGql; });
1067
1510
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
1068
1511
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
1069
- /* harmony import */ var _util_gql_router_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(10);
1070
- /* harmony import */ var _util_get_gql_data_from_response_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(18);
1071
- /* harmony import */ var _util_gql_error_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2);
1512
+ /* harmony import */ var _util_merge_gql_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(18);
1513
+ /* harmony import */ var _use_gql_router_context_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(26);
1514
+ /* harmony import */ var _util_get_gql_data_from_response_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(27);
1072
1515
 
1073
1516
 
1074
1517
 
@@ -1084,74 +1527,69 @@ const GqlRouter = ({
1084
1527
  * Values in the partial context given to the returned fetch function will
1085
1528
  * only be included if they have a value other than undefined.
1086
1529
  */
1087
- const useGql = () => {
1530
+ const useGql = (context = {}) => {
1088
1531
  // This hook only works if the `GqlRouter` has been used to setup context.
1089
- const gqlRouterContext = Object(react__WEBPACK_IMPORTED_MODULE_0__["useContext"])(_util_gql_router_context_js__WEBPACK_IMPORTED_MODULE_1__[/* GqlRouterContext */ "a"]);
1090
-
1091
- if (gqlRouterContext == null) {
1092
- throw new _util_gql_error_js__WEBPACK_IMPORTED_MODULE_3__[/* GqlError */ "a"]("No GqlRouter", _util_gql_error_js__WEBPACK_IMPORTED_MODULE_3__[/* GqlErrors */ "b"].Internal);
1093
- }
1094
-
1095
- const {
1096
- fetch,
1097
- defaultContext
1098
- } = gqlRouterContext; // Let's memoize the gqlFetch function we create based off our context.
1532
+ const gqlRouterContext = Object(_use_gql_router_context_js__WEBPACK_IMPORTED_MODULE_2__[/* useGqlRouterContext */ "a"])(context); // Let's memoize the gqlFetch function we create based off our context.
1099
1533
  // That way, even if the context happens to change, if its values don't
1100
1534
  // we give the same function instance back to our callers instead of
1101
1535
  // making a new one. That then means they can safely use the return value
1102
1536
  // in hooks deps without fear of it triggering extra renders.
1103
1537
 
1104
- const gqlFetch = Object(react__WEBPACK_IMPORTED_MODULE_0__["useMemo"])(() => (operation, options = Object.freeze({})) => {
1538
+ const gqlFetch = Object(react__WEBPACK_IMPORTED_MODULE_0__["useCallback"])((operation, options = Object.freeze({})) => {
1539
+ const {
1540
+ fetch,
1541
+ defaultContext
1542
+ } = gqlRouterContext;
1105
1543
  const {
1106
1544
  variables,
1107
1545
  context = {}
1108
- } = options; // Let's merge the partial context of the fetch with the
1109
- // default context. We deliberately don't spread because
1110
- // spreading would overwrite default context values with
1111
- // undefined if the partial context includes a value explicitly
1112
- // set to undefined. Instead, we use a map/reduce of keys.
1113
-
1114
- const mergedContext = Object.keys(context).reduce((acc, key) => {
1115
- if (context[key] !== undefined) {
1116
- acc[key] = context[key];
1117
- }
1118
-
1119
- return acc;
1120
- }, { ...defaultContext
1121
- }); // Invoke the fetch and extract the data.
1122
-
1123
- return fetch(operation, variables, mergedContext).then(_util_get_gql_data_from_response_js__WEBPACK_IMPORTED_MODULE_2__[/* getGqlDataFromResponse */ "a"], error => {
1124
- // Return null if the request was aborted.
1125
- // The only way to detect this reliably, it seems, is to
1126
- // check the error name and see if it's "AbortError" (this
1127
- // is also what Apollo does).
1128
- // Even then, it's reliant on the fetch supporting aborts.
1129
- if (error.name === "AbortError") {
1130
- return null;
1131
- } // Need to make sure we pass other errors along.
1132
-
1546
+ } = options;
1547
+ const finalContext = Object(_util_merge_gql_context_js__WEBPACK_IMPORTED_MODULE_1__[/* mergeGqlContext */ "a"])(defaultContext, context); // Invoke the fetch and extract the data.
1133
1548
 
1134
- throw error;
1135
- });
1136
- }, [fetch, defaultContext]);
1549
+ return fetch(operation, variables, finalContext).then(_util_get_gql_data_from_response_js__WEBPACK_IMPORTED_MODULE_3__[/* getGqlDataFromResponse */ "a"]);
1550
+ }, [gqlRouterContext]);
1137
1551
  return gqlFetch;
1138
1552
  };
1139
1553
 
1140
1554
  /***/ }),
1141
- /* 17 */
1555
+ /* 24 */
1556
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
1557
+
1558
+ "use strict";
1559
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return AbortError; });
1560
+ /**
1561
+ * Simple implementation to represent aborting.
1562
+ *
1563
+ * Other frameworks may provide this too, so we won't be sharing this with
1564
+ * the outside world. It's just a utility for test and internal use whenever
1565
+ * we need to represent the concept of aborted things.
1566
+ */
1567
+ class AbortError extends Error {
1568
+ constructor(message) {
1569
+ super(message);
1570
+ this.name = "AbortError";
1571
+ }
1572
+
1573
+ }
1574
+
1575
+ /***/ }),
1576
+ /* 25 */
1142
1577
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1143
1578
 
1144
1579
  "use strict";
1145
1580
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return resultFromCachedResponse; });
1581
+ /* harmony import */ var _status_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
1582
+ /* harmony import */ var _data_error_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1);
1583
+
1584
+
1585
+
1146
1586
  /**
1147
1587
  * Turns a cache entry into a stateful result.
1148
1588
  */
1149
1589
  const resultFromCachedResponse = cacheEntry => {
1150
- // No cache entry means we didn't load one yet.
1590
+ // No cache entry means no result to be hydrated.
1151
1591
  if (cacheEntry == null) {
1152
- return {
1153
- status: "loading"
1154
- };
1592
+ return null;
1155
1593
  }
1156
1594
 
1157
1595
  const {
@@ -1160,31 +1598,81 @@ const resultFromCachedResponse = cacheEntry => {
1160
1598
  } = cacheEntry;
1161
1599
 
1162
1600
  if (error != null) {
1163
- return {
1164
- status: "error",
1165
- error
1166
- };
1601
+ // Let's hydrate the error. We don't persist everything about the
1602
+ // original error on the server, hence why we only superficially
1603
+ // hydrate it to a GqlHydratedError.
1604
+ return _status_js__WEBPACK_IMPORTED_MODULE_0__[/* Status */ "a"].error(new _data_error_js__WEBPACK_IMPORTED_MODULE_1__[/* DataError */ "a"](error, _data_error_js__WEBPACK_IMPORTED_MODULE_1__[/* DataErrors */ "b"].Hydrated));
1167
1605
  }
1168
1606
 
1169
1607
  if (data != null) {
1170
- return {
1171
- status: "success",
1172
- data
1173
- };
1608
+ return _status_js__WEBPACK_IMPORTED_MODULE_0__[/* Status */ "a"].success(data);
1609
+ } // We shouldn't get here since we don't actually cache null data.
1610
+
1611
+
1612
+ return _status_js__WEBPACK_IMPORTED_MODULE_0__[/* Status */ "a"].aborted();
1613
+ };
1614
+
1615
+ /***/ }),
1616
+ /* 26 */
1617
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
1618
+
1619
+ "use strict";
1620
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useGqlRouterContext; });
1621
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
1622
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
1623
+ /* harmony import */ var _util_merge_gql_context_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(18);
1624
+ /* harmony import */ var _util_gql_router_context_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(17);
1625
+ /* harmony import */ var _util_gql_error_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3);
1626
+
1627
+
1628
+
1629
+
1630
+
1631
+ /**
1632
+ * Construct a GqlRouterContext from the current one and partial context.
1633
+ */
1634
+ const useGqlRouterContext = (contextOverrides = {}) => {
1635
+ // This hook only works if the `GqlRouter` has been used to setup context.
1636
+ const gqlRouterContext = Object(react__WEBPACK_IMPORTED_MODULE_0__["useContext"])(_util_gql_router_context_js__WEBPACK_IMPORTED_MODULE_2__[/* GqlRouterContext */ "a"]);
1637
+
1638
+ if (gqlRouterContext == null) {
1639
+ throw new _util_gql_error_js__WEBPACK_IMPORTED_MODULE_3__[/* GqlError */ "a"]("No GqlRouter", _util_gql_error_js__WEBPACK_IMPORTED_MODULE_3__[/* GqlErrors */ "b"].Internal);
1174
1640
  }
1175
1641
 
1176
- return {
1177
- status: "aborted"
1178
- };
1642
+ const {
1643
+ fetch,
1644
+ defaultContext
1645
+ } = gqlRouterContext;
1646
+ const contextRef = Object(react__WEBPACK_IMPORTED_MODULE_0__["useRef"])(defaultContext);
1647
+ const mergedContext = Object(_util_merge_gql_context_js__WEBPACK_IMPORTED_MODULE_1__[/* mergeGqlContext */ "a"])(defaultContext, contextOverrides); // Now, we can see if this represents a new context and if so,
1648
+ // update our ref and return the merged value.
1649
+
1650
+ const refKeys = Object.keys(contextRef.current);
1651
+ const mergedKeys = Object.keys(mergedContext);
1652
+ const shouldWeUpdateRef = refKeys.length !== mergedKeys.length || mergedKeys.every(key => contextRef.current[key] !== mergedContext[key]);
1653
+
1654
+ if (shouldWeUpdateRef) {
1655
+ contextRef.current = mergedContext;
1656
+ } // OK, now we're up-to-date, let's memoize our final result.
1657
+
1658
+
1659
+ const finalContext = contextRef.current;
1660
+ const finalRouterContext = Object(react__WEBPACK_IMPORTED_MODULE_0__["useMemo"])(() => ({
1661
+ fetch,
1662
+ defaultContext: finalContext
1663
+ }), [fetch, finalContext]);
1664
+ return finalRouterContext;
1179
1665
  };
1180
1666
 
1181
1667
  /***/ }),
1182
- /* 18 */
1668
+ /* 27 */
1183
1669
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1184
1670
 
1185
1671
  "use strict";
1186
1672
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return getGqlDataFromResponse; });
1187
- /* harmony import */ var _gql_error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
1673
+ /* harmony import */ var _data_error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
1674
+ /* harmony import */ var _gql_error_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
1675
+
1188
1676
 
1189
1677
  /**
1190
1678
  * Validate a GQL operation response and extract the data.
@@ -1199,7 +1687,7 @@ const getGqlDataFromResponse = async response => {
1199
1687
  try {
1200
1688
  result = JSON.parse(bodyText);
1201
1689
  } catch (e) {
1202
- throw new _gql_error_js__WEBPACK_IMPORTED_MODULE_0__[/* GqlError */ "a"]("Failed to parse response", _gql_error_js__WEBPACK_IMPORTED_MODULE_0__[/* GqlErrors */ "b"].Parse, {
1690
+ throw new _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataError */ "a"]("Failed to parse response", _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataErrors */ "b"].Parse, {
1203
1691
  metadata: {
1204
1692
  statusCode: response.status,
1205
1693
  bodyText
@@ -1210,7 +1698,7 @@ const getGqlDataFromResponse = async response => {
1210
1698
 
1211
1699
 
1212
1700
  if (response.status >= 300) {
1213
- throw new _gql_error_js__WEBPACK_IMPORTED_MODULE_0__[/* GqlError */ "a"]("Response unsuccessful", _gql_error_js__WEBPACK_IMPORTED_MODULE_0__[/* GqlErrors */ "b"].Network, {
1701
+ throw new _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataError */ "a"]("Response unsuccessful", _data_error_js__WEBPACK_IMPORTED_MODULE_0__[/* DataErrors */ "b"].Network, {
1214
1702
  metadata: {
1215
1703
  statusCode: response.status,
1216
1704
  result
@@ -1224,7 +1712,7 @@ const getGqlDataFromResponse = async response => {
1224
1712
  !Object.prototype.hasOwnProperty.call(result, "data") && // Flow shouldn't be warning about this.
1225
1713
  // $FlowIgnore[method-unbinding]
1226
1714
  !Object.prototype.hasOwnProperty.call(result, "errors")) {
1227
- throw new _gql_error_js__WEBPACK_IMPORTED_MODULE_0__[/* GqlError */ "a"]("Server response missing", _gql_error_js__WEBPACK_IMPORTED_MODULE_0__[/* GqlErrors */ "b"].BadResponse, {
1715
+ throw new _gql_error_js__WEBPACK_IMPORTED_MODULE_1__[/* GqlError */ "a"]("Server response missing", _gql_error_js__WEBPACK_IMPORTED_MODULE_1__[/* GqlErrors */ "b"].BadResponse, {
1228
1716
  metadata: {
1229
1717
  statusCode: response.status,
1230
1718
  result
@@ -1234,7 +1722,7 @@ const getGqlDataFromResponse = async response => {
1234
1722
 
1235
1723
 
1236
1724
  if (result.errors != null && Array.isArray(result.errors) && result.errors.length > 0) {
1237
- throw new _gql_error_js__WEBPACK_IMPORTED_MODULE_0__[/* GqlError */ "a"]("GraphQL errors", _gql_error_js__WEBPACK_IMPORTED_MODULE_0__[/* GqlErrors */ "b"].ErrorResult, {
1725
+ throw new _gql_error_js__WEBPACK_IMPORTED_MODULE_1__[/* GqlError */ "a"]("GraphQL errors", _gql_error_js__WEBPACK_IMPORTED_MODULE_1__[/* GqlErrors */ "b"].ErrorResult, {
1238
1726
  metadata: {
1239
1727
  statusCode: response.status,
1240
1728
  result
@@ -1247,7 +1735,7 @@ const getGqlDataFromResponse = async response => {
1247
1735
  };
1248
1736
 
1249
1737
  /***/ }),
1250
- /* 19 */
1738
+ /* 28 */
1251
1739
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1252
1740
 
1253
1741
  "use strict";
@@ -1257,45 +1745,85 @@ __webpack_require__.r(__webpack_exports__);
1257
1745
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "hasUnfulfilledRequests", function() { return hasUnfulfilledRequests; });
1258
1746
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "removeFromCache", function() { return removeFromCache; });
1259
1747
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "removeAllFromCache", function() { return removeAllFromCache; });
1260
- /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3);
1748
+ /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
1261
1749
  /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__);
1262
- /* harmony import */ var _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
1263
- /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);
1264
- /* harmony import */ var _components_track_data_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(12);
1750
+ /* harmony import */ var _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
1751
+ /* harmony import */ var _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7);
1752
+ /* harmony import */ var _components_track_data_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(19);
1265
1753
  /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "TrackData", function() { return _components_track_data_js__WEBPACK_IMPORTED_MODULE_3__["a"]; });
1266
1754
 
1267
- /* harmony import */ var _components_data_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(13);
1755
+ /* harmony import */ var _components_data_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20);
1268
1756
  /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Data", function() { return _components_data_js__WEBPACK_IMPORTED_MODULE_4__["a"]; });
1269
1757
 
1270
- /* harmony import */ var _components_intercept_data_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(14);
1271
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "InterceptData", function() { return _components_intercept_data_js__WEBPACK_IMPORTED_MODULE_5__["a"]; });
1758
+ /* harmony import */ var _components_intercept_requests_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(21);
1759
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "InterceptRequests", function() { return _components_intercept_requests_js__WEBPACK_IMPORTED_MODULE_5__["a"]; });
1760
+
1761
+ /* harmony import */ var _util_data_error_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(1);
1762
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "DataError", function() { return _util_data_error_js__WEBPACK_IMPORTED_MODULE_6__["a"]; });
1763
+
1764
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "DataErrors", function() { return _util_data_error_js__WEBPACK_IMPORTED_MODULE_6__["b"]; });
1765
+
1766
+ /* harmony import */ var _hooks_use_server_effect_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(14);
1767
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useServerEffect", function() { return _hooks_use_server_effect_js__WEBPACK_IMPORTED_MODULE_7__["a"]; });
1768
+
1769
+ /* harmony import */ var _hooks_use_cached_effect_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(15);
1770
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useCachedEffect", function() { return _hooks_use_cached_effect_js__WEBPACK_IMPORTED_MODULE_8__["a"]; });
1771
+
1772
+ /* harmony import */ var _hooks_use_shared_cache_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(8);
1773
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useSharedCache", function() { return _hooks_use_shared_cache_js__WEBPACK_IMPORTED_MODULE_9__["b"]; });
1774
+
1775
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "clearSharedCache", function() { return _hooks_use_shared_cache_js__WEBPACK_IMPORTED_MODULE_9__["a"]; });
1272
1776
 
1273
- /* harmony import */ var _hooks_use_server_effect_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(8);
1274
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useServerEffect", function() { return _hooks_use_server_effect_js__WEBPACK_IMPORTED_MODULE_6__["a"]; });
1777
+ /* harmony import */ var _hooks_use_hydratable_effect_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(11);
1778
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useHydratableEffect", function() { return _hooks_use_hydratable_effect_js__WEBPACK_IMPORTED_MODULE_10__["b"]; });
1275
1779
 
1276
- /* harmony import */ var _hooks_use_shared_cache_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(11);
1277
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useSharedCache", function() { return _hooks_use_shared_cache_js__WEBPACK_IMPORTED_MODULE_7__["b"]; });
1780
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "WhenClientSide", function() { return _hooks_use_hydratable_effect_js__WEBPACK_IMPORTED_MODULE_10__["a"]; });
1278
1781
 
1279
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "clearSharedCache", function() { return _hooks_use_shared_cache_js__WEBPACK_IMPORTED_MODULE_7__["a"]; });
1782
+ /* harmony import */ var _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(9);
1783
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ScopedInMemoryCache", function() { return _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_11__["a"]; });
1280
1784
 
1281
- /* harmony import */ var _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(5);
1282
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ScopedInMemoryCache", function() { return _util_scoped_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_8__["a"]; });
1785
+ /* harmony import */ var _util_serializable_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(12);
1786
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "SerializableInMemoryCache", function() { return _util_serializable_in_memory_cache_js__WEBPACK_IMPORTED_MODULE_12__["a"]; });
1283
1787
 
1284
- /* harmony import */ var _components_gql_router_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(15);
1285
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlRouter", function() { return _components_gql_router_js__WEBPACK_IMPORTED_MODULE_9__["a"]; });
1788
+ /* harmony import */ var _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(10);
1789
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "RequestFulfillment", function() { return _util_request_fulfillment_js__WEBPACK_IMPORTED_MODULE_13__["a"]; });
1286
1790
 
1287
- /* harmony import */ var _hooks_use_gql_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(16);
1288
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useGql", function() { return _hooks_use_gql_js__WEBPACK_IMPORTED_MODULE_10__["a"]; });
1791
+ /* harmony import */ var _util_status_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(4);
1792
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Status", function() { return _util_status_js__WEBPACK_IMPORTED_MODULE_14__["a"]; });
1289
1793
 
1290
- /* harmony import */ var _util_gql_error_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(2);
1291
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlErrors", function() { return _util_gql_error_js__WEBPACK_IMPORTED_MODULE_11__["b"]; });
1794
+ /* harmony import */ var _components_gql_router_js__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(22);
1795
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlRouter", function() { return _components_gql_router_js__WEBPACK_IMPORTED_MODULE_15__["a"]; });
1292
1796
 
1293
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlError", function() { return _util_gql_error_js__WEBPACK_IMPORTED_MODULE_11__["a"]; });
1797
+ /* harmony import */ var _hooks_use_gql_js__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(23);
1798
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useGql", function() { return _hooks_use_gql_js__WEBPACK_IMPORTED_MODULE_16__["a"]; });
1294
1799
 
1800
+ /* harmony import */ var _util_gql_error_js__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(3);
1801
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlError", function() { return _util_gql_error_js__WEBPACK_IMPORTED_MODULE_17__["a"]; });
1295
1802
 
1803
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GqlErrors", function() { return _util_gql_error_js__WEBPACK_IMPORTED_MODULE_17__["b"]; });
1296
1804
 
1297
1805
 
1806
+
1807
+
1808
+
1809
+ /**
1810
+ * Initialize the hydration cache.
1811
+ *
1812
+ * @param {ResponseCache} source The cache content to use for initializing the
1813
+ * cache.
1814
+ * @throws {Error} If the cache is already initialized.
1815
+ */
1298
1816
  const initializeCache = source => _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* SsrCache */ "a"].Default.initialize(source);
1817
+ /**
1818
+ * Fulfill all tracked data requests.
1819
+ *
1820
+ * This is for use with the `TrackData` component during server-side rendering.
1821
+ *
1822
+ * @throws {Error} If executed outside of server-side rendering.
1823
+ * @returns {Promise<void>} A promise that resolves when all tracked requests
1824
+ * have been fulfilled.
1825
+ */
1826
+
1299
1827
  const fulfillAllDataRequests = () => {
1300
1828
  if (!_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
1301
1829
  return Promise.reject(new Error("Data requests are not tracked when client-side"));
@@ -1303,6 +1831,16 @@ const fulfillAllDataRequests = () => {
1303
1831
 
1304
1832
  return _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestTracker */ "a"].Default.fulfillTrackedRequests();
1305
1833
  };
1834
+ /**
1835
+ * Indicate if there are unfulfilled tracked requests.
1836
+ *
1837
+ * This is used in conjunction with `TrackData`.
1838
+ *
1839
+ * @throws {Error} If executed outside of server-side rendering.
1840
+ * @returns {boolean} `true` if there are unfulfilled tracked requests;
1841
+ * otherwise, `false`.
1842
+ */
1843
+
1306
1844
  const hasUnfulfilledRequests = () => {
1307
1845
  if (!_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
1308
1846
  throw new Error("Data requests are not tracked when client-side");
@@ -1310,18 +1848,160 @@ const hasUnfulfilledRequests = () => {
1310
1848
 
1311
1849
  return _util_request_tracking_js__WEBPACK_IMPORTED_MODULE_2__[/* RequestTracker */ "a"].Default.hasUnfulfilledRequests;
1312
1850
  };
1851
+ /**
1852
+ * Remove the request identified from the cached hydration responses.
1853
+ *
1854
+ * @param {string} id The request ID of the response to remove from the cache.
1855
+ */
1856
+
1313
1857
  const removeFromCache = id => _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* SsrCache */ "a"].Default.remove(id);
1858
+ /**
1859
+ * Remove all cached hydration responses that match the given predicate.
1860
+ *
1861
+ * @param {(id: string) => boolean} [predicate] The predicate to match against
1862
+ * the cached hydration responses. If no predicate is provided, all cached
1863
+ * hydration responses will be removed.
1864
+ */
1865
+
1314
1866
  const removeAllFromCache = predicate => _util_ssr_cache_js__WEBPACK_IMPORTED_MODULE_1__[/* SsrCache */ "a"].Default.removeAll(predicate);
1315
1867
 
1316
1868
 
1317
1869
 
1318
1870
 
1319
1871
 
1872
+
1873
+
1874
+
1875
+
1876
+
1877
+
1320
1878
  // GraphQL
1321
1879
 
1322
1880
 
1323
1881
 
1324
1882
 
1325
1883
 
1884
+ /***/ }),
1885
+ /* 29 */
1886
+ /***/ (function(module, exports, __webpack_require__) {
1887
+
1888
+ "use strict";
1889
+ /**
1890
+ * Copyright (c) Facebook, Inc. and its affiliates.
1891
+ *
1892
+ * This source code is licensed under the MIT license found in the
1893
+ * LICENSE file in the root directory of this source tree.
1894
+ */
1895
+
1896
+
1897
+
1898
+ // Below we want to use `hasOwnProperty` on an object that doesn't have
1899
+ // `Object.prototype` in its proto chain, so we must extract it here.
1900
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
1901
+
1902
+ // Map from an enum object to a reverse map of its values to names
1903
+ var reverseMapCache = typeof WeakMap === 'function' ? new WeakMap() : new Map();
1904
+
1905
+ // Computes the reverse mapping of the enum object: from value to name.
1906
+ // Flow Enum values are unique (enforced by the parser), so this is a
1907
+ // one to one mapping.
1908
+ function getReverseMap(enumObject) {
1909
+ var reverseMap = reverseMapCache.get(enumObject);
1910
+ if (reverseMap !== undefined) {
1911
+ return reverseMap;
1912
+ }
1913
+ // We aren't using `Object.values` because that gets enumerable
1914
+ // properties, and our properties aren't enumerable.
1915
+ var newReverseMap = new Map();
1916
+ Object.getOwnPropertyNames(enumObject).forEach(function (name) {
1917
+ newReverseMap.set(enumObject[name], name);
1918
+ });
1919
+ reverseMapCache.set(enumObject, newReverseMap);
1920
+ return newReverseMap;
1921
+ }
1922
+
1923
+ var EnumPrototype = Object.freeze(
1924
+ Object.defineProperties(Object.create(null), {
1925
+ isValid: {
1926
+ value: function (x) {
1927
+ return getReverseMap(this).has(x);
1928
+ },
1929
+ },
1930
+ cast: {
1931
+ value: function (x) {
1932
+ return this.isValid(x) ? x : undefined;
1933
+ },
1934
+ },
1935
+ members: {
1936
+ value: function () {
1937
+ return getReverseMap(this).keys();
1938
+ },
1939
+ },
1940
+ getName: {
1941
+ value: function (value) {
1942
+ return getReverseMap(this).get(value);
1943
+ }
1944
+ }
1945
+ })
1946
+ );
1947
+
1948
+ // `members` is an object mapping name to value.
1949
+ function Enum(members) {
1950
+ var o = Object.create(EnumPrototype);
1951
+ for (var k in members) {
1952
+ if (hasOwnProperty.call(members, k)) {
1953
+ // Create non-enumerable properties.
1954
+ Object.defineProperty(o, k, {value: members[k]});
1955
+ }
1956
+ }
1957
+ return Object.freeze(o);
1958
+ }
1959
+
1960
+ // Mirrored enum (string enum with no member initializers).
1961
+ // Optimized implementation, taking advantage of the fact that
1962
+ // keys and values are identical.
1963
+ var EnumMirroredPrototype = Object.freeze(
1964
+ Object.defineProperties(Object.create(null), {
1965
+ isValid: {
1966
+ value: function (x) {
1967
+ if (typeof x === 'string') {
1968
+ return hasOwnProperty.call(this, x);
1969
+ }
1970
+ return false;
1971
+ },
1972
+ },
1973
+ cast: {
1974
+ value: EnumPrototype.cast,
1975
+ },
1976
+ members: {
1977
+ value: function () {
1978
+ // We aren't using `Object.values` because that gets enumerable
1979
+ // properties, and our properties aren't enumerable.
1980
+ return Object.getOwnPropertyNames(this).values();
1981
+ },
1982
+ },
1983
+ getName: {
1984
+ value: function (value) {
1985
+ return value;
1986
+ }
1987
+ }
1988
+ })
1989
+ );
1990
+
1991
+ // `members` is an array of names (which, are also the values).
1992
+ Enum.Mirrored = function EnumMirrored(members) {
1993
+ var o = Object.create(EnumMirroredPrototype);
1994
+ for (var i = 0, len = members.length; i < len; ++i) {
1995
+ // Value is same as key. Also, non-enumerable.
1996
+ Object.defineProperty(o, members[i], {value: members[i]});
1997
+ }
1998
+ return Object.freeze(o);
1999
+ };
2000
+
2001
+ Object.freeze(Enum.Mirrored);
2002
+
2003
+ module.exports = Object.freeze(Enum);
2004
+
2005
+
1326
2006
  /***/ })
1327
2007
  /******/ ]);