@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.
- package/CHANGELOG.md +31 -0
- package/dist/es/index.js +793 -375
- package/dist/index.js +1203 -523
- package/legacy-docs.md +3 -0
- package/package.json +2 -2
- package/src/__docs__/_overview_.stories.mdx +18 -0
- package/src/__docs__/_overview_graphql.stories.mdx +35 -0
- package/src/__docs__/_overview_ssr_.stories.mdx +185 -0
- package/src/__docs__/_overview_testing_.stories.mdx +123 -0
- package/src/__docs__/exports.clear-shared-cache.stories.mdx +20 -0
- package/src/__docs__/exports.data-error.stories.mdx +23 -0
- package/src/__docs__/exports.data-errors.stories.mdx +23 -0
- package/src/{components/data.md → __docs__/exports.data.stories.mdx} +15 -18
- package/src/__docs__/exports.fulfill-all-data-requests.stories.mdx +24 -0
- package/src/__docs__/exports.gql-error.stories.mdx +23 -0
- package/src/__docs__/exports.gql-errors.stories.mdx +20 -0
- package/src/__docs__/exports.gql-router.stories.mdx +29 -0
- package/src/__docs__/exports.has-unfulfilled-requests.stories.mdx +20 -0
- package/src/__docs__/exports.intercept-requests.stories.mdx +69 -0
- package/src/__docs__/exports.intialize-cache.stories.mdx +29 -0
- package/src/__docs__/exports.remove-all-from-cache.stories.mdx +24 -0
- package/src/__docs__/exports.remove-from-cache.stories.mdx +25 -0
- package/src/__docs__/exports.request-fulfillment.stories.mdx +36 -0
- package/src/__docs__/exports.scoped-in-memory-cache.stories.mdx +92 -0
- package/src/__docs__/exports.serializable-in-memory-cache.stories.mdx +112 -0
- package/src/__docs__/exports.status.stories.mdx +31 -0
- package/src/{components/track-data.md → __docs__/exports.track-data.stories.mdx} +15 -0
- package/src/__docs__/exports.use-cached-effect.stories.mdx +41 -0
- package/src/__docs__/exports.use-gql.stories.mdx +73 -0
- package/src/__docs__/exports.use-hydratable-effect.stories.mdx +43 -0
- package/src/__docs__/exports.use-server-effect.stories.mdx +38 -0
- package/src/__docs__/exports.use-shared-cache.stories.mdx +30 -0
- package/src/__docs__/exports.when-client-side.stories.mdx +33 -0
- package/src/__docs__/types.cached-response.stories.mdx +29 -0
- package/src/__docs__/types.error-options.stories.mdx +21 -0
- package/src/__docs__/types.gql-context.stories.mdx +20 -0
- package/src/__docs__/types.gql-fetch-fn.stories.mdx +24 -0
- package/src/__docs__/types.gql-fetch-options.stories.mdx +24 -0
- package/src/__docs__/types.gql-operation-type.stories.mdx +24 -0
- package/src/__docs__/types.gql-operation.stories.mdx +67 -0
- package/src/__docs__/types.response-cache.stories.mdx +33 -0
- package/src/__docs__/types.result.stories.mdx +39 -0
- package/src/__docs__/types.scoped-cache.stories.mdx +27 -0
- package/src/__docs__/types.valid-cache-data.stories.mdx +23 -0
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +0 -80
- package/src/__tests__/generated-snapshot.test.js +7 -31
- package/src/components/__tests__/data.test.js +160 -154
- package/src/components/__tests__/intercept-requests.test.js +58 -0
- package/src/components/data.js +22 -126
- package/src/components/intercept-context.js +4 -5
- package/src/components/intercept-requests.js +69 -0
- package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +8 -8
- package/src/hooks/__tests__/use-cached-effect.test.js +507 -0
- package/src/hooks/__tests__/use-gql-router-context.test.js +133 -0
- package/src/hooks/__tests__/use-gql.test.js +1 -30
- package/src/hooks/__tests__/use-hydratable-effect.test.js +708 -0
- package/src/hooks/__tests__/use-request-interception.test.js +255 -0
- package/src/hooks/__tests__/use-server-effect.test.js +39 -11
- package/src/hooks/use-cached-effect.js +225 -0
- package/src/hooks/use-gql-router-context.js +50 -0
- package/src/hooks/use-gql.js +22 -52
- package/src/hooks/use-hydratable-effect.js +206 -0
- package/src/hooks/use-request-interception.js +51 -0
- package/src/hooks/use-server-effect.js +14 -7
- package/src/hooks/use-shared-cache.js +13 -11
- package/src/index.js +54 -2
- package/src/util/__tests__/__snapshots__/serializable-in-memory-cache.test.js.snap +19 -0
- package/src/util/__tests__/merge-gql-context.test.js +74 -0
- package/src/util/__tests__/request-fulfillment.test.js +23 -42
- package/src/util/__tests__/request-tracking.test.js +26 -7
- package/src/util/__tests__/result-from-cache-response.test.js +19 -5
- package/src/util/__tests__/scoped-in-memory-cache.test.js +6 -85
- package/src/util/__tests__/serializable-in-memory-cache.test.js +398 -0
- package/src/util/__tests__/ssr-cache.test.js +52 -52
- package/src/util/abort-error.js +15 -0
- package/src/util/data-error.js +58 -0
- package/src/util/get-gql-data-from-response.js +3 -2
- package/src/util/gql-error.js +19 -11
- package/src/util/merge-gql-context.js +34 -0
- package/src/util/request-fulfillment.js +49 -46
- package/src/util/request-tracking.js +69 -15
- package/src/util/result-from-cache-response.js +12 -16
- package/src/util/scoped-in-memory-cache.js +24 -47
- package/src/util/serializable-in-memory-cache.js +49 -0
- package/src/util/ssr-cache.js +9 -8
- package/src/util/status.js +30 -0
- package/src/util/types.js +18 -1
- package/docs.md +0 -122
- package/src/components/__tests__/intercept-data.test.js +0 -63
- package/src/components/intercept-data.js +0 -66
- package/src/components/intercept-data.md +0 -51
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import {Server} from "@khanacademy/wonder-blocks-core";
|
|
3
3
|
import {SsrCache} from "../ssr-cache.js";
|
|
4
|
-
import {
|
|
4
|
+
import {SerializableInMemoryCache} from "../serializable-in-memory-cache.js";
|
|
5
5
|
|
|
6
6
|
describe("../ssr-cache.js", () => {
|
|
7
7
|
afterEach(() => {
|
|
@@ -56,7 +56,7 @@ describe("../ssr-cache.js", () => {
|
|
|
56
56
|
|
|
57
57
|
it("should throw if the cache is already intialized", () => {
|
|
58
58
|
// Arrange
|
|
59
|
-
const internalCache = new
|
|
59
|
+
const internalCache = new SerializableInMemoryCache({
|
|
60
60
|
MY_KEY: {data: "THE_DATA"},
|
|
61
61
|
});
|
|
62
62
|
const cache = new SsrCache(internalCache);
|
|
@@ -95,7 +95,7 @@ describe("../ssr-cache.js", () => {
|
|
|
95
95
|
describe("when client-side", () => {
|
|
96
96
|
it("should not store the entry in the hydration cache", () => {
|
|
97
97
|
// Arrange
|
|
98
|
-
const hydrationCache = new
|
|
98
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
99
99
|
const cache = new SsrCache(hydrationCache);
|
|
100
100
|
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
101
101
|
|
|
@@ -108,8 +108,8 @@ describe("../ssr-cache.js", () => {
|
|
|
108
108
|
|
|
109
109
|
it("should not store the entry in the ssrOnly cache", () => {
|
|
110
110
|
// Arrange
|
|
111
|
-
const hydrationCache = new
|
|
112
|
-
const ssrOnlyCache = new
|
|
111
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
112
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
113
113
|
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
114
114
|
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
115
115
|
|
|
@@ -129,7 +129,7 @@ describe("../ssr-cache.js", () => {
|
|
|
129
129
|
describe("when hydrate is true", () => {
|
|
130
130
|
it("should store the entry in the hydration cache", () => {
|
|
131
131
|
// Arrange
|
|
132
|
-
const hydrationCache = new
|
|
132
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
133
133
|
const cache = new SsrCache(hydrationCache);
|
|
134
134
|
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
135
135
|
|
|
@@ -148,8 +148,8 @@ describe("../ssr-cache.js", () => {
|
|
|
148
148
|
|
|
149
149
|
it("should not store the entry in the ssrOnly cache", () => {
|
|
150
150
|
// Arrange
|
|
151
|
-
const hydrationCache = new
|
|
152
|
-
const ssrOnlyCache = new
|
|
151
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
152
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
153
153
|
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
154
154
|
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
155
155
|
|
|
@@ -164,8 +164,8 @@ describe("../ssr-cache.js", () => {
|
|
|
164
164
|
describe("when hydrate is false", () => {
|
|
165
165
|
it("should store the entry in the ssr-only cache", () => {
|
|
166
166
|
// Arrange
|
|
167
|
-
const hydrationCache = new
|
|
168
|
-
const ssrOnlyCache = new
|
|
167
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
168
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
169
169
|
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
170
170
|
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
171
171
|
|
|
@@ -184,8 +184,8 @@ describe("../ssr-cache.js", () => {
|
|
|
184
184
|
|
|
185
185
|
it("should not store the entry in the hydration cache", () => {
|
|
186
186
|
// Arrange
|
|
187
|
-
const hydrationCache = new
|
|
188
|
-
const ssrOnlyCache = new
|
|
187
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
188
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
189
189
|
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
190
190
|
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
191
191
|
|
|
@@ -203,7 +203,7 @@ describe("../ssr-cache.js", () => {
|
|
|
203
203
|
describe("when client-side", () => {
|
|
204
204
|
it("should not store the entry in the hydration cache", () => {
|
|
205
205
|
// Arrange
|
|
206
|
-
const hydrationCache = new
|
|
206
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
207
207
|
const cache = new SsrCache(hydrationCache);
|
|
208
208
|
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
209
209
|
|
|
@@ -216,8 +216,8 @@ describe("../ssr-cache.js", () => {
|
|
|
216
216
|
|
|
217
217
|
it("should not store the entry in the ssrOnly cache", () => {
|
|
218
218
|
// Arrange
|
|
219
|
-
const hydrationCache = new
|
|
220
|
-
const ssrOnlyCache = new
|
|
219
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
220
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
221
221
|
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
222
222
|
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
223
223
|
|
|
@@ -237,7 +237,7 @@ describe("../ssr-cache.js", () => {
|
|
|
237
237
|
describe("when hydrate is true", () => {
|
|
238
238
|
it("should store the entry in the hydration cache", () => {
|
|
239
239
|
// Arrange
|
|
240
|
-
const hydrationCache = new
|
|
240
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
241
241
|
const cache = new SsrCache(hydrationCache);
|
|
242
242
|
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
243
243
|
|
|
@@ -256,8 +256,8 @@ describe("../ssr-cache.js", () => {
|
|
|
256
256
|
|
|
257
257
|
it("should not store the entry in the ssrOnly cache", () => {
|
|
258
258
|
// Arrange
|
|
259
|
-
const hydrationCache = new
|
|
260
|
-
const ssrOnlyCache = new
|
|
259
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
260
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
261
261
|
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
262
262
|
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
263
263
|
|
|
@@ -272,8 +272,8 @@ describe("../ssr-cache.js", () => {
|
|
|
272
272
|
describe("when hydrate is false", () => {
|
|
273
273
|
it("should store the entry in the ssr-only cache", () => {
|
|
274
274
|
// Arrange
|
|
275
|
-
const hydrationCache = new
|
|
276
|
-
const ssrOnlyCache = new
|
|
275
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
276
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
277
277
|
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
278
278
|
const ssrOnlyStoreSpy = jest.spyOn(ssrOnlyCache, "set");
|
|
279
279
|
|
|
@@ -292,8 +292,8 @@ describe("../ssr-cache.js", () => {
|
|
|
292
292
|
|
|
293
293
|
it("should not store the entry in the hydration cache", () => {
|
|
294
294
|
// Arrange
|
|
295
|
-
const hydrationCache = new
|
|
296
|
-
const ssrOnlyCache = new
|
|
295
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
296
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
297
297
|
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
298
298
|
const hydrationStoreSpy = jest.spyOn(hydrationCache, "set");
|
|
299
299
|
|
|
@@ -315,7 +315,7 @@ describe("../ssr-cache.js", () => {
|
|
|
315
315
|
|
|
316
316
|
it("should return null if not in the hydration cache", () => {
|
|
317
317
|
// Arrange
|
|
318
|
-
const hydrationCache = new
|
|
318
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
319
319
|
jest.spyOn(hydrationCache, "get").mockReturnValue(null);
|
|
320
320
|
const cache = new SsrCache(hydrationCache);
|
|
321
321
|
|
|
@@ -328,7 +328,7 @@ describe("../ssr-cache.js", () => {
|
|
|
328
328
|
|
|
329
329
|
it("should return the cached entry if in the hydration cache", () => {
|
|
330
330
|
// Arrange
|
|
331
|
-
const hydrationCache = new
|
|
331
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
332
332
|
jest.spyOn(hydrationCache, "get").mockReturnValue({
|
|
333
333
|
data: "data!",
|
|
334
334
|
});
|
|
@@ -349,8 +349,8 @@ describe("../ssr-cache.js", () => {
|
|
|
349
349
|
|
|
350
350
|
it("should return null in any cache", () => {
|
|
351
351
|
// Arrange
|
|
352
|
-
const hydrationCache = new
|
|
353
|
-
const ssrOnlyCache = new
|
|
352
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
353
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
354
354
|
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
355
355
|
|
|
356
356
|
// Act
|
|
@@ -362,7 +362,7 @@ describe("../ssr-cache.js", () => {
|
|
|
362
362
|
|
|
363
363
|
it("should return the cached entry if in the hydration cache", () => {
|
|
364
364
|
// Arrange
|
|
365
|
-
const hydrationCache = new
|
|
365
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
366
366
|
jest.spyOn(hydrationCache, "get").mockReturnValue({
|
|
367
367
|
data: "data!",
|
|
368
368
|
});
|
|
@@ -377,8 +377,8 @@ describe("../ssr-cache.js", () => {
|
|
|
377
377
|
|
|
378
378
|
it("should return the cached entry if in the ssr-only cache", () => {
|
|
379
379
|
// Arrange
|
|
380
|
-
const hydrationCache = new
|
|
381
|
-
const ssrOnlyCache = new
|
|
380
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
381
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
382
382
|
jest.spyOn(ssrOnlyCache, "get").mockReturnValue({
|
|
383
383
|
data: "data!",
|
|
384
384
|
});
|
|
@@ -396,8 +396,8 @@ describe("../ssr-cache.js", () => {
|
|
|
396
396
|
describe("#remove", () => {
|
|
397
397
|
it("should return false if nothing was removed", () => {
|
|
398
398
|
// Arrange
|
|
399
|
-
const hydrationCache = new
|
|
400
|
-
const ssrOnlycache = new
|
|
399
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
400
|
+
const ssrOnlycache = new SerializableInMemoryCache();
|
|
401
401
|
jest.spyOn(hydrationCache, "purge").mockReturnValue(false);
|
|
402
402
|
jest.spyOn(ssrOnlycache, "purge").mockReturnValue(false);
|
|
403
403
|
const cache = new SsrCache(hydrationCache, ssrOnlycache);
|
|
@@ -411,7 +411,7 @@ describe("../ssr-cache.js", () => {
|
|
|
411
411
|
|
|
412
412
|
it("should return true if something was removed from hydration cache", () => {
|
|
413
413
|
// Arrange
|
|
414
|
-
const hydrationCache = new
|
|
414
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
415
415
|
jest.spyOn(hydrationCache, "purge").mockReturnValue(true);
|
|
416
416
|
const cache = new SsrCache(hydrationCache);
|
|
417
417
|
|
|
@@ -429,8 +429,8 @@ describe("../ssr-cache.js", () => {
|
|
|
429
429
|
|
|
430
430
|
it("should return true if something was removed from ssr-only cache", () => {
|
|
431
431
|
// Arrange
|
|
432
|
-
const hydrationCache = new
|
|
433
|
-
const ssrOnlyCache = new
|
|
432
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
433
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
434
434
|
jest.spyOn(ssrOnlyCache, "purge").mockReturnValue(true);
|
|
435
435
|
const cache = new SsrCache(hydrationCache, ssrOnlyCache);
|
|
436
436
|
|
|
@@ -446,7 +446,7 @@ describe("../ssr-cache.js", () => {
|
|
|
446
446
|
describe("#cloneHydratableData", () => {
|
|
447
447
|
it("should clone the hydration cache", () => {
|
|
448
448
|
// Arrange
|
|
449
|
-
const hydrationCache = new
|
|
449
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
450
450
|
const cloneSpy = jest
|
|
451
451
|
.spyOn(hydrationCache, "clone")
|
|
452
452
|
.mockReturnValue({
|
|
@@ -475,7 +475,7 @@ describe("../ssr-cache.js", () => {
|
|
|
475
475
|
|
|
476
476
|
it("should remove all entries from the hydration cache when client-side without predicate", () => {
|
|
477
477
|
// Arrange
|
|
478
|
-
const hydrationCache = new
|
|
478
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
479
479
|
const purgeAllSpy = jest.spyOn(hydrationCache, "purgeAll");
|
|
480
480
|
const cache = new SsrCache(hydrationCache);
|
|
481
481
|
|
|
@@ -488,11 +488,11 @@ describe("../ssr-cache.js", () => {
|
|
|
488
488
|
|
|
489
489
|
it("should pass a predicate to hydration cache purge if a predicate is passed", () => {
|
|
490
490
|
// Arrange
|
|
491
|
-
const hydrationCache = new
|
|
491
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
492
492
|
const purgeAllSpy = jest.spyOn(hydrationCache, "purgeAll");
|
|
493
493
|
const cache = new SsrCache(
|
|
494
494
|
hydrationCache,
|
|
495
|
-
new
|
|
495
|
+
new SerializableInMemoryCache(),
|
|
496
496
|
);
|
|
497
497
|
|
|
498
498
|
// Act
|
|
@@ -504,7 +504,7 @@ describe("../ssr-cache.js", () => {
|
|
|
504
504
|
|
|
505
505
|
it("should pass a predicate to the hydration cache that calls the predicate it was given", () => {
|
|
506
506
|
// Arrange
|
|
507
|
-
const hydrationCache = new
|
|
507
|
+
const hydrationCache = new SerializableInMemoryCache({
|
|
508
508
|
default: {
|
|
509
509
|
KEY1: {
|
|
510
510
|
data: "DATA",
|
|
@@ -513,7 +513,7 @@ describe("../ssr-cache.js", () => {
|
|
|
513
513
|
});
|
|
514
514
|
const cache = new SsrCache(
|
|
515
515
|
hydrationCache,
|
|
516
|
-
new
|
|
516
|
+
new SerializableInMemoryCache(),
|
|
517
517
|
);
|
|
518
518
|
const predicate = jest.fn().mockReturnValue(false);
|
|
519
519
|
|
|
@@ -532,14 +532,14 @@ describe("../ssr-cache.js", () => {
|
|
|
532
532
|
|
|
533
533
|
it("should remove all entries from hydration cache when server-side without predicate", () => {
|
|
534
534
|
// Arrange
|
|
535
|
-
const hydrationCache = new
|
|
535
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
536
536
|
const hydrationPurgeAllSpy = jest.spyOn(
|
|
537
537
|
hydrationCache,
|
|
538
538
|
"purgeAll",
|
|
539
539
|
);
|
|
540
540
|
const cache = new SsrCache(
|
|
541
541
|
hydrationCache,
|
|
542
|
-
new
|
|
542
|
+
new SerializableInMemoryCache(),
|
|
543
543
|
);
|
|
544
544
|
|
|
545
545
|
// Act
|
|
@@ -551,10 +551,10 @@ describe("../ssr-cache.js", () => {
|
|
|
551
551
|
|
|
552
552
|
it("should remove all entries from ssr cache when server-side without predicate", () => {
|
|
553
553
|
// Arrange
|
|
554
|
-
const ssrOnlyCache = new
|
|
554
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
555
555
|
const ssrPurgeAllSpy = jest.spyOn(ssrOnlyCache, "purgeAll");
|
|
556
556
|
const cache = new SsrCache(
|
|
557
|
-
new
|
|
557
|
+
new SerializableInMemoryCache(),
|
|
558
558
|
ssrOnlyCache,
|
|
559
559
|
);
|
|
560
560
|
|
|
@@ -567,11 +567,11 @@ describe("../ssr-cache.js", () => {
|
|
|
567
567
|
|
|
568
568
|
it("should pass a predicate to hydration cache purge if a predicate is passed", () => {
|
|
569
569
|
// Arrange
|
|
570
|
-
const hydrationCache = new
|
|
570
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
571
571
|
const purgeAllSpy = jest.spyOn(hydrationCache, "purgeAll");
|
|
572
572
|
const cache = new SsrCache(
|
|
573
573
|
hydrationCache,
|
|
574
|
-
new
|
|
574
|
+
new SerializableInMemoryCache(),
|
|
575
575
|
);
|
|
576
576
|
|
|
577
577
|
// Act
|
|
@@ -583,10 +583,10 @@ describe("../ssr-cache.js", () => {
|
|
|
583
583
|
|
|
584
584
|
it("should pass a predicate to srr cache purge if a predicate is passed", () => {
|
|
585
585
|
// Arrange
|
|
586
|
-
const ssrOnlyCache = new
|
|
586
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
587
587
|
const purgeAllSpy = jest.spyOn(ssrOnlyCache, "purgeAll");
|
|
588
588
|
const cache = new SsrCache(
|
|
589
|
-
new
|
|
589
|
+
new SerializableInMemoryCache(),
|
|
590
590
|
ssrOnlyCache,
|
|
591
591
|
);
|
|
592
592
|
|
|
@@ -599,10 +599,10 @@ describe("../ssr-cache.js", () => {
|
|
|
599
599
|
|
|
600
600
|
it("should pass a predicate to the hydration cache that calls the predicate it was given", () => {
|
|
601
601
|
// Arrange
|
|
602
|
-
const hydrationCache = new
|
|
602
|
+
const hydrationCache = new SerializableInMemoryCache();
|
|
603
603
|
const cache = new SsrCache(
|
|
604
604
|
hydrationCache,
|
|
605
|
-
new
|
|
605
|
+
new SerializableInMemoryCache(),
|
|
606
606
|
);
|
|
607
607
|
cache.cacheData("KEY1", "DATA", true);
|
|
608
608
|
const predicate = jest.fn().mockReturnValue(false);
|
|
@@ -616,9 +616,9 @@ describe("../ssr-cache.js", () => {
|
|
|
616
616
|
|
|
617
617
|
it("should pass a predicate to the ssr cache that calls the predicate it was given", () => {
|
|
618
618
|
// Arrange
|
|
619
|
-
const ssrOnlyCache = new
|
|
619
|
+
const ssrOnlyCache = new SerializableInMemoryCache();
|
|
620
620
|
const cache = new SsrCache(
|
|
621
|
-
new
|
|
621
|
+
new SerializableInMemoryCache(),
|
|
622
622
|
ssrOnlyCache,
|
|
623
623
|
);
|
|
624
624
|
cache.cacheData(
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simple implementation to represent aborting.
|
|
5
|
+
*
|
|
6
|
+
* Other frameworks may provide this too, so we won't be sharing this with
|
|
7
|
+
* the outside world. It's just a utility for test and internal use whenever
|
|
8
|
+
* we need to represent the concept of aborted things.
|
|
9
|
+
*/
|
|
10
|
+
export class AbortError extends Error {
|
|
11
|
+
constructor(message: string) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "AbortError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {KindError} from "@khanacademy/wonder-stuff-core";
|
|
3
|
+
import type {ErrorOptions} from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Error kinds for DataError.
|
|
7
|
+
*/
|
|
8
|
+
export const DataErrors = Object.freeze({
|
|
9
|
+
/**
|
|
10
|
+
* The kind of error is not known.
|
|
11
|
+
*/
|
|
12
|
+
Unknown: "Unknown",
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The error is internal to the executing code.
|
|
16
|
+
*/
|
|
17
|
+
Internal: "Internal",
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* There was a problem with the provided input.
|
|
21
|
+
*/
|
|
22
|
+
InvalidInput: "InvalidInput",
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A network error occurred.
|
|
26
|
+
*/
|
|
27
|
+
Network: "Network",
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Response could not be parsed.
|
|
31
|
+
*/
|
|
32
|
+
Parse: "Parse",
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* An error that occurred during SSR and was hydrated from cache
|
|
36
|
+
*/
|
|
37
|
+
Hydrated: "Hydrated",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* An error from the Wonder Blocks Data API.
|
|
42
|
+
*
|
|
43
|
+
* Errors of this type will have names of the format:
|
|
44
|
+
* `${kind}DataError`
|
|
45
|
+
*/
|
|
46
|
+
export class DataError extends KindError {
|
|
47
|
+
constructor(
|
|
48
|
+
message: string,
|
|
49
|
+
kind: $Values<typeof DataErrors>,
|
|
50
|
+
{metadata, cause}: ErrorOptions = ({}: $Shape<ErrorOptions>),
|
|
51
|
+
) {
|
|
52
|
+
super(message, kind, {
|
|
53
|
+
metadata,
|
|
54
|
+
cause,
|
|
55
|
+
name: "Data",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @flow
|
|
2
|
+
import {DataError, DataErrors} from "./data-error.js";
|
|
2
3
|
import {GqlError, GqlErrors} from "./gql-error.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -14,7 +15,7 @@ export const getGqlDataFromResponse = async <TData>(
|
|
|
14
15
|
try {
|
|
15
16
|
result = JSON.parse(bodyText);
|
|
16
17
|
} catch (e) {
|
|
17
|
-
throw new
|
|
18
|
+
throw new DataError("Failed to parse response", DataErrors.Parse, {
|
|
18
19
|
metadata: {
|
|
19
20
|
statusCode: response.status,
|
|
20
21
|
bodyText,
|
|
@@ -25,7 +26,7 @@ export const getGqlDataFromResponse = async <TData>(
|
|
|
25
26
|
|
|
26
27
|
// Check for a bad status code.
|
|
27
28
|
if (response.status >= 300) {
|
|
28
|
-
throw new
|
|
29
|
+
throw new DataError("Response unsuccessful", DataErrors.Network, {
|
|
29
30
|
metadata: {
|
|
30
31
|
statusCode: response.status,
|
|
31
32
|
result,
|
package/src/util/gql-error.js
CHANGED
|
@@ -1,36 +1,44 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
import {KindError
|
|
3
|
-
import type {Metadata} from "@khanacademy/wonder-stuff-core";
|
|
2
|
+
import {KindError} from "@khanacademy/wonder-stuff-core";
|
|
4
3
|
|
|
5
|
-
type
|
|
6
|
-
metadata?: ?Metadata,
|
|
7
|
-
cause?: ?Error,
|
|
8
|
-
|};
|
|
4
|
+
import type {ErrorOptions} from "./types.js";
|
|
9
5
|
|
|
10
6
|
/**
|
|
11
7
|
* Error kinds for GqlError.
|
|
12
8
|
*/
|
|
13
9
|
export const GqlErrors = Object.freeze({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
/**
|
|
11
|
+
* An internal framework error.
|
|
12
|
+
*/
|
|
13
|
+
Internal: "Internal",
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Response does not have the correct structure for a GraphQL response.
|
|
17
|
+
*/
|
|
17
18
|
BadResponse: "BadResponse",
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A valid GraphQL result with errors field in the payload.
|
|
22
|
+
*/
|
|
18
23
|
ErrorResult: "ErrorResult",
|
|
19
24
|
});
|
|
20
25
|
|
|
21
26
|
/**
|
|
22
27
|
* An error from the GQL API.
|
|
28
|
+
*
|
|
29
|
+
* Errors of this type will have names of the format:
|
|
30
|
+
* `${kind}GqlError`
|
|
23
31
|
*/
|
|
24
32
|
export class GqlError extends KindError {
|
|
25
33
|
constructor(
|
|
26
34
|
message: string,
|
|
27
35
|
kind: $Values<typeof GqlErrors>,
|
|
28
|
-
{metadata, cause}:
|
|
36
|
+
{metadata, cause}: ErrorOptions = ({}: $Shape<ErrorOptions>),
|
|
29
37
|
) {
|
|
30
38
|
super(message, kind, {
|
|
31
39
|
metadata,
|
|
32
40
|
cause,
|
|
33
|
-
|
|
41
|
+
name: "Gql",
|
|
34
42
|
});
|
|
35
43
|
}
|
|
36
44
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type {GqlContext} from "./gql-types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Construct a complete GqlContext from current defaults and a partial context.
|
|
6
|
+
*
|
|
7
|
+
* Values in the partial context that are `undefined` will be ignored.
|
|
8
|
+
* Values in the partial context that are `null` will be deleted.
|
|
9
|
+
*/
|
|
10
|
+
export const mergeGqlContext = <TContext: GqlContext>(
|
|
11
|
+
defaultContext: TContext,
|
|
12
|
+
overrides: Partial<TContext>,
|
|
13
|
+
): TContext => {
|
|
14
|
+
// Let's merge the partial context default context. We deliberately
|
|
15
|
+
// don't spread because spreading would overwrite default context
|
|
16
|
+
// values with undefined or null if the partial context includes a value
|
|
17
|
+
// explicitly set to undefined or null.
|
|
18
|
+
return Object.keys(overrides).reduce(
|
|
19
|
+
(acc, key) => {
|
|
20
|
+
// Undefined values are ignored.
|
|
21
|
+
if (overrides[key] !== undefined) {
|
|
22
|
+
if (overrides[key] === null) {
|
|
23
|
+
// Null indicates we delete this context value.
|
|
24
|
+
delete acc[key];
|
|
25
|
+
} else {
|
|
26
|
+
// Otherwise, we set it.
|
|
27
|
+
acc[key] = overrides[key];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return acc;
|
|
31
|
+
},
|
|
32
|
+
{...defaultContext},
|
|
33
|
+
);
|
|
34
|
+
};
|