@khanacademy/wonder-blocks-data 12.0.0 → 13.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 (33) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/components/data.d.ts +2 -2
  3. package/dist/es/index.js +10 -6
  4. package/dist/hooks/use-gql.d.ts +5 -1
  5. package/dist/index.js +10 -6
  6. package/dist/util/status.d.ts +4 -3
  7. package/dist/util/types.d.ts +2 -0
  8. package/package.json +2 -2
  9. package/src/components/__tests__/data.test.tsx +6 -13
  10. package/src/components/data.ts +2 -4
  11. package/src/hooks/__tests__/use-cached-effect.test.tsx +79 -40
  12. package/src/hooks/__tests__/use-gql-router-context.test.tsx +1 -2
  13. package/src/hooks/__tests__/use-hydratable-effect.test.ts +1 -2
  14. package/src/hooks/__tests__/use-request-interception.test.tsx +2 -5
  15. package/src/hooks/__tests__/use-server-effect.test.ts +3 -6
  16. package/src/hooks/__tests__/use-shared-cache.test.ts +17 -13
  17. package/src/hooks/use-cached-effect.ts +24 -20
  18. package/src/hooks/use-gql.ts +12 -9
  19. package/src/hooks/use-request-interception.ts +13 -11
  20. package/src/hooks/use-shared-cache.ts +4 -2
  21. package/src/util/__tests__/request-api.test.ts +2 -1
  22. package/src/util/__tests__/request-tracking.test.tsx +5 -9
  23. package/src/util/__tests__/result-from-cache-response.test.ts +2 -2
  24. package/src/util/__tests__/serializable-in-memory-cache.test.ts +1 -2
  25. package/src/util/__tests__/ssr-cache.test.ts +2 -4
  26. package/src/util/__tests__/to-gql-operation.test.ts +2 -4
  27. package/src/util/graphql-document-node-parser.ts +6 -6
  28. package/src/util/merge-gql-context.ts +2 -1
  29. package/src/util/request-tracking.ts +6 -2
  30. package/src/util/ssr-cache.ts +11 -8
  31. package/src/util/status.ts +6 -0
  32. package/src/util/types.ts +3 -0
  33. package/tsconfig-build.tsbuildinfo +1 -1
@@ -68,7 +68,7 @@ describe("#useRequestInterception", () => {
68
68
  const handler = jest.fn();
69
69
  const interceptor1 = jest.fn();
70
70
  const interceptor2 = jest.fn();
71
- const Wrapper = ({children, interceptor}: any) => (
71
+ const Wrapper = ({children, interceptor}: any): React.ReactElement => (
72
72
  <InterceptRequests interceptor={interceptor}>
73
73
  {children}
74
74
  </InterceptRequests>
@@ -80,8 +80,7 @@ describe("#useRequestInterception", () => {
80
80
  {wrapper: Wrapper, initialProps: {interceptor: interceptor1}},
81
81
  );
82
82
  const result1 = wrapper.result.current;
83
- // @ts-expect-error [FEI-5019] - TS2345 - Argument of type '{ wrapper: ({ children, interceptor, }: any) => JSX.Element; interceptor: jest.Mock<any, any, any>; }' is not assignable to parameter of type '{ interceptor: jest.Mock<any, any, any>; }'.
84
- wrapper.rerender({wrapper: Wrapper, interceptor: interceptor2});
83
+ wrapper.rerender({interceptor: interceptor2});
85
84
  const result2 = wrapper.result.current;
86
85
 
87
86
  // Assert
@@ -126,7 +125,6 @@ describe("#useRequestInterception", () => {
126
125
  interceptedHandler();
127
126
 
128
127
  // Assert
129
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'toHaveBeenCalledBefore' does not exist on type 'JestMatchers<Mock<null, [], any>>'.
130
128
  expect(interceptorNearest).toHaveBeenCalledBefore(
131
129
  interceptorFurthest,
132
130
  );
@@ -154,7 +152,6 @@ describe("#useRequestInterception", () => {
154
152
  interceptedHandler();
155
153
 
156
154
  // Assert
157
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'toHaveBeenCalledBefore' does not exist on type 'JestMatchers<Mock<null, [], any>>'.
158
155
  expect(interceptorFurthest).toHaveBeenCalledBefore(handler);
159
156
  });
160
157
 
@@ -139,8 +139,7 @@ describe("#useServerEffect", () => {
139
139
  const interceptedHandler = jest.fn();
140
140
  jest.spyOn(SsrCache.Default, "getEntry").mockReturnValueOnce({
141
141
  data: "DATA",
142
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'null' is not assignable to type 'undefined'.
143
- error: null,
142
+ error: undefined,
144
143
  });
145
144
  jest.spyOn(
146
145
  UseRequestInterception,
@@ -165,8 +164,7 @@ describe("#useServerEffect", () => {
165
164
  const fakeHandler = jest.fn();
166
165
  jest.spyOn(SsrCache.Default, "getEntry").mockReturnValueOnce({
167
166
  data: "DATA",
168
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'null' is not assignable to type 'undefined'.
169
- error: null,
167
+ error: undefined,
170
168
  });
171
169
 
172
170
  // Act
@@ -221,8 +219,7 @@ describe("#useServerEffect", () => {
221
219
  const fakeHandler = jest.fn();
222
220
  jest.spyOn(SsrCache.Default, "getEntry").mockReturnValueOnce({
223
221
  data: "DATA",
224
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'null' is not assignable to type 'undefined'.
225
- error: null,
222
+ error: undefined,
226
223
  });
227
224
 
228
225
  // Act
@@ -1,3 +1,5 @@
1
+ // eslint-disable-next-line import/no-unassigned-import
2
+ import "jest-extended";
1
3
  import {renderHook as clientRenderHook} from "@testing-library/react-hooks";
2
4
 
3
5
  import {useSharedCache, SharedCache} from "../use-shared-cache";
@@ -48,7 +50,6 @@ describe("#useSharedCache", () => {
48
50
  } = clientRenderHook(() => useSharedCache("id", "scope"));
49
51
 
50
52
  // Assert
51
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'toBeArrayOfSize' does not exist on type 'JestMatchers<[ValidCacheData | null | undefined, CacheValueFn<ValidCacheData>]>'.
52
53
  expect(result).toBeArrayOfSize(2);
53
54
  });
54
55
 
@@ -119,12 +120,13 @@ describe("#useSharedCache", () => {
119
120
  id: "id",
120
121
  scope: "scope",
121
122
  });
122
- const result1 = wrapper.result.all[wrapper.result.all.length - 2];
123
- const result2 = wrapper.result.current;
123
+ const value1 = wrapper.result.all[wrapper.result.all.length - 2];
124
+ const value2 = wrapper.result.current;
125
+ const result1 = Array.isArray(value1) ? value1[1] : "BAD1";
126
+ const result2 = Array.isArray(value2) ? value2[1] : "BAD2";
124
127
 
125
128
  // Assert
126
- // @ts-expect-error [FEI-5019] - TS7053 - Element implicitly has an 'any' type because expression of type '1' can't be used to index type 'Error | [ValidCacheData | null | undefined, CacheValueFn<ValidCacheData>]'.
127
- expect(result1[1]).toBe(result2[1]);
129
+ expect(result1).toBe(result2);
128
130
  });
129
131
 
130
132
  it("should be a new function if the id changes", () => {
@@ -138,12 +140,13 @@ describe("#useSharedCache", () => {
138
140
 
139
141
  // Act
140
142
  wrapper.rerender({id: "new-id"});
141
- const result1 = wrapper.result.all[wrapper.result.all.length - 2];
142
- const result2 = wrapper.result.current;
143
+ const value1 = wrapper.result.all[wrapper.result.all.length - 2];
144
+ const value2 = wrapper.result.current;
145
+ const result1 = Array.isArray(value1) ? value1[1] : "BAD1";
146
+ const result2 = Array.isArray(value2) ? value2[1] : "BAD2";
143
147
 
144
148
  // Assert
145
- // @ts-expect-error [FEI-5019] - TS7053 - Element implicitly has an 'any' type because expression of type '1' can't be used to index type 'Error | [ValidCacheData | null | undefined, CacheValueFn<ValidCacheData>]'.
146
- expect(result1[1]).not.toBe(result2[1]);
149
+ expect(result1).not.toBe(result2);
147
150
  });
148
151
 
149
152
  it("should be a new function if the scope changes", () => {
@@ -157,12 +160,13 @@ describe("#useSharedCache", () => {
157
160
 
158
161
  // Act
159
162
  wrapper.rerender({scope: "new-scope"});
160
- const result1 = wrapper.result.all[wrapper.result.all.length - 2];
161
- const result2 = wrapper.result.current;
163
+ const value1 = wrapper.result.all[wrapper.result.all.length - 2];
164
+ const value2 = wrapper.result.current;
165
+ const result1 = Array.isArray(value1) ? value1[1] : "BAD1";
166
+ const result2 = Array.isArray(value2) ? value2[1] : "BAD2";
162
167
 
163
168
  // Assert
164
- // @ts-expect-error [FEI-5019] - TS7053 - Element implicitly has an 'any' type because expression of type '1' can't be used to index type 'Error | [ValidCacheData | null | undefined, CacheValueFn<ValidCacheData>]'.
165
- expect(result1[1]).not.toBe(result2[1]);
169
+ expect(result1).not.toBe(result2);
166
170
  });
167
171
 
168
172
  it("should set the value in the cache", () => {
@@ -63,6 +63,12 @@ type CachedEffectOptions<TData extends ValidCacheData> = {
63
63
  scope?: string;
64
64
  };
65
65
 
66
+ type InflightRequest<TData extends ValidCacheData> = {
67
+ requestId: string;
68
+ request: Promise<Result<TData>>;
69
+ cancel(): void;
70
+ };
71
+
66
72
  const DefaultScope = "useCachedEffect";
67
73
 
68
74
  /**
@@ -114,19 +120,16 @@ export const useCachedEffect = <TData extends ValidCacheData>(
114
120
  const forceUpdate = useForceUpdate();
115
121
  // For the NetworkOnly fetch policy, we ignore the cached value.
116
122
  // So we need somewhere else to store the network value.
117
- const networkResultRef = React.useRef();
123
+ const networkResultRef = React.useRef<Result<TData> | null>();
118
124
 
119
125
  // Set up the function that will do the fetching.
120
- const currentRequestRef = React.useRef();
126
+ const currentRequestRef = React.useRef<InflightRequest<TData> | null>();
121
127
  const fetchRequest = React.useMemo(() => {
122
128
  // We aren't using useCallback here because we need to make sure that
123
129
  // if we are rememo-izing, we cancel any inflight request for the old
124
130
  // callback.
125
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'cancel' does not exist on type 'never'.
126
131
  currentRequestRef.current?.cancel();
127
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'null' is not assignable to type 'undefined'.
128
132
  currentRequestRef.current = null;
129
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'null' is not assignable to type 'undefined'.
130
133
  networkResultRef.current = null;
131
134
 
132
135
  const fetchFn = () => {
@@ -153,7 +156,6 @@ export const useCachedEffect = <TData extends ValidCacheData>(
153
156
  },
154
157
  );
155
158
 
156
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'request' does not exist on type 'never'.
157
159
  if (request === currentRequestRef.current?.request) {
158
160
  // The request inflight is the same, so do nothing.
159
161
  // NOTE: Perhaps if invoked via a refetch, we will want to
@@ -162,11 +164,9 @@ export const useCachedEffect = <TData extends ValidCacheData>(
162
164
  }
163
165
 
164
166
  // Clear the last network result.
165
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'null' is not assignable to type 'undefined'.
166
167
  networkResultRef.current = null;
167
168
 
168
169
  // Cancel the previous request.
169
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'cancel' does not exist on type 'never'.
170
170
  currentRequestRef.current?.cancel();
171
171
 
172
172
  // TODO(somewhatabstract, FEI-4276):
@@ -178,7 +178,6 @@ export const useCachedEffect = <TData extends ValidCacheData>(
178
178
  // Catching shouldn't serve a purpose.
179
179
  // eslint-disable-next-line promise/catch-or-return
180
180
  request.then((result) => {
181
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'null' is not assignable to type 'undefined'.
182
181
  currentRequestRef.current = null;
183
182
  if (cancel) {
184
183
  // We don't modify our result if the request was cancelled
@@ -189,7 +188,6 @@ export const useCachedEffect = <TData extends ValidCacheData>(
189
188
 
190
189
  // Now we need to update the cache and notify or force a rerender.
191
190
  setMostRecentResult(result);
192
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'Result<TData>' is not assignable to type 'undefined'.
193
191
  networkResultRef.current = result;
194
192
 
195
193
  if (onResultChanged != null) {
@@ -204,7 +202,6 @@ export const useCachedEffect = <TData extends ValidCacheData>(
204
202
  return; // Shut up eslint always-return rule.
205
203
  });
206
204
 
207
- // @ts-expect-error [FEI-5019] - TS2322 - Type '{ requestId: string; request: Promise<Result<TData>>; cancel(): void; }' is not assignable to type 'undefined'.
208
205
  currentRequestRef.current = {
209
206
  requestId,
210
207
  request,
@@ -265,29 +262,36 @@ export const useCachedEffect = <TData extends ValidCacheData>(
265
262
  }
266
263
  fetchRequest();
267
264
  return () => {
268
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'cancel' does not exist on type 'never'.
269
265
  currentRequestRef.current?.cancel();
270
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'null' is not assignable to type 'undefined'.
271
266
  currentRequestRef.current = null;
272
267
  };
273
268
  }, [shouldFetch, fetchRequest]);
274
269
 
275
270
  // We track the last result we returned in order to support the
276
- // "retainResultOnChange" option.
277
- const lastResultAgnosticOfIdRef = React.useRef(Status.loading());
271
+ // "retainResultOnChange" option. To begin, the last result is no-data.
272
+ const lastResultAgnosticOfIdRef = React.useRef<Result<TData>>(
273
+ Status.noData<TData>(),
274
+ );
275
+ // The default return value is:
276
+ // - The last result we returned if we're retaining results on change.
277
+ // - The no-data state if shouldFetch is false, and therefore there is no
278
+ // in-flight request.
279
+ // - Otherwise, the loading state (we can assume there's an inflight
280
+ // request if skip is not true).
278
281
  const loadingResult = retainResultOnChange
279
282
  ? lastResultAgnosticOfIdRef.current
280
- : Status.loading();
283
+ : shouldFetch
284
+ ? Status.loading<TData>()
285
+ : Status.noData<TData>();
281
286
 
282
- // Loading is a transient state, so we only use it here; it's not something
283
- // we cache.
284
- const result =
287
+ // Loading and no-data are transient states, so we only use them here;
288
+ // they're not something we cache.
289
+ const result: Result<TData> =
285
290
  (fetchPolicy === FetchPolicy.NetworkOnly
286
291
  ? networkResultRef.current
287
292
  : mostRecentResult) ?? loadingResult;
288
293
  lastResultAgnosticOfIdRef.current = result;
289
294
 
290
295
  // We return the result and a function for triggering a refetch.
291
- // @ts-expect-error [FEI-5019] - TS2322 - Type '{ status: "loading"; } | { status: "error"; error: Error; } | { status: "aborted"; } | { status: "success"; data: ValidCacheData; }' is not assignable to type 'Result<TData>'.
292
296
  return [result, fetchRequest];
293
297
  };
@@ -10,6 +10,13 @@ import type {
10
10
  GqlFetchOptions,
11
11
  } from "../util/gql-types";
12
12
 
13
+ interface GqlFetchFn<TContext extends GqlContext> {
14
+ <TData, TVariables extends Record<any, any>>(
15
+ operation: GqlOperation<TData, TVariables>,
16
+ options?: GqlFetchOptions<TVariables, TContext>,
17
+ ): Promise<TData>;
18
+ }
19
+
13
20
  /**
14
21
  * Hook to obtain a gqlFetch function for performing GraphQL requests.
15
22
  *
@@ -22,10 +29,7 @@ import type {
22
29
  */
23
30
  export const useGql = <TContext extends GqlContext>(
24
31
  context: Partial<TContext> = {} as Partial<TContext>,
25
- ): (<TData, TVariables extends Record<any, any>>(
26
- operation: GqlOperation<TData, TVariables>,
27
- options?: GqlFetchOptions<TVariables, TContext>,
28
- ) => Promise<TData>) => {
32
+ ): GqlFetchFn<TContext> => {
29
33
  // This hook only works if the `GqlRouter` has been used to setup context.
30
34
  const gqlRouterContext = useGqlRouterContext(context);
31
35
 
@@ -34,22 +38,21 @@ export const useGql = <TContext extends GqlContext>(
34
38
  // we give the same function instance back to our callers instead of
35
39
  // making a new one. That then means they can safely use the return value
36
40
  // in hooks deps without fear of it triggering extra renders.
37
- const gqlFetch = useCallback(
41
+ const gqlFetch: GqlFetchFn<TContext> = useCallback(
38
42
  <TData, TVariables extends Record<any, any>>(
39
43
  operation: GqlOperation<TData, TVariables>,
40
44
  options: GqlFetchOptions<TVariables, TContext> = Object.freeze({}),
41
- ) => {
45
+ ): Promise<TData> => {
42
46
  const {fetch, defaultContext} = gqlRouterContext;
43
47
  const {variables, context = {}} = options;
44
48
  const finalContext = mergeGqlContext(defaultContext, context);
45
49
 
46
50
  // Invoke the fetch and extract the data.
47
- return fetch(operation, variables, finalContext).then(
48
- getGqlDataFromResponse,
51
+ return fetch(operation, variables, finalContext).then((response) =>
52
+ getGqlDataFromResponse<TData>(response),
49
53
  );
50
54
  },
51
55
  [gqlRouterContext],
52
56
  );
53
- // @ts-expect-error [FEI-5019] - TS2322 - Type '<TData, TVariables extends Record<any, any>>(operation: GqlOperation<TData, TVariables>, options?: GqlFetchOptions<TVariables, TContext>) => Promise<unknown>' is not assignable to type '<TData, TVariables extends Record<any, any>>(operation: GqlOperation<TData, TVariables>, options?: GqlFetchOptions<TVariables, TContext> | undefined) => Promise<...>'.
54
57
  return gqlFetch;
55
58
  };
@@ -30,20 +30,22 @@ export const useRequestInterception = <TData extends ValidCacheData>(
30
30
  const interceptedHandler = React.useCallback((): Promise<TData> => {
31
31
  // Call the interceptors from closest to furthest.
32
32
  // If one returns a non-null result, then we keep that.
33
- const interceptResponse = interceptors.reduceRight(
34
- // @ts-expect-error [FEI-5019] - TS2769 - No overload matches this call.
35
- (prev, interceptor) => {
36
- if (prev != null) {
37
- return prev;
38
- }
39
- return interceptor(requestId);
40
- },
41
- null,
42
- );
33
+ const interceptResponse: Promise<TData> | null | undefined =
34
+ interceptors.reduceRight(
35
+ (prev: Promise<TData> | null | undefined, interceptor) => {
36
+ if (prev != null) {
37
+ return prev;
38
+ }
39
+ return interceptor(requestId) as
40
+ | Promise<TData>
41
+ | null
42
+ | undefined;
43
+ },
44
+ null,
45
+ );
43
46
  // If nothing intercepted this request, invoke the original handler.
44
47
  // NOTE: We can't guarantee all interceptors return the same type
45
48
  // as our handler, so how can TypeScript know? Let's just suppress that.
46
- // @ts-expect-error [FEI-5019] - TS2739 - Type '(requestId: string) => Promise<ValidCacheData | null | undefined> | null | undefined' is missing the following properties from type 'Promise<TData>': then, catch, finally, [Symbol.toStringTag]
47
49
  return interceptResponse ?? handler();
48
50
  }, [handler, interceptors, requestId]);
49
51
 
@@ -81,8 +81,10 @@ export const useSharedCache = <TValue extends ValidCacheData>(
81
81
  // since our last run through. Also, our cache does not know what type it
82
82
  // stores, so we have to cast it to the type we're exporting. This is a
83
83
  // dev time courtesy, rather than a runtime thing.
84
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'ValidCacheData | null | undefined' is not assignable to type 'TValue | null | undefined'.
85
- let currentValue: TValue | null | undefined = cache.get(scope, id);
84
+ let currentValue: TValue | null | undefined = cache.get(scope, id) as
85
+ | TValue
86
+ | null
87
+ | undefined;
86
88
 
87
89
  // If we have an initial value, we need to add it to the cache
88
90
  // and use it as our current value.
@@ -1,3 +1,5 @@
1
+ // eslint-disable-next-line import/no-unassigned-import
2
+ import "jest-extended";
1
3
  import {Server} from "@khanacademy/wonder-blocks-core";
2
4
  import {RequestFulfillment} from "../request-fulfillment";
3
5
  import {RequestTracker} from "../request-tracking";
@@ -123,7 +125,6 @@ describe("#hasTrackedRequestsToBeFetched", () => {
123
125
  const result = hasTrackedRequestsToBeFetched();
124
126
 
125
127
  // Assert
126
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'toBeTrue' does not exist on type 'JestMatchers<boolean>'.
127
128
  expect(result).toBeTrue();
128
129
  });
129
130
  });
@@ -162,13 +162,12 @@ describe("../request-tracking.js", () => {
162
162
  it("should cache errors occurring in promises", async () => {
163
163
  // Arrange
164
164
  const requestTracker = createRequestTracker();
165
- const fakeBadRequestHandler = () =>
165
+ const fakeBadRequestHandler = (): Promise<any> =>
166
166
  new Promise((resolve: any, reject: any) =>
167
167
  reject("OH NO!"),
168
168
  );
169
169
  requestTracker.trackDataRequest(
170
170
  "ID",
171
- // @ts-expect-error [FEI-5019] - TS2345 - Argument of type '() => Promise<unknown>' is not assignable to parameter of type '() => Promise<ValidCacheData>'.
172
171
  fakeBadRequestHandler,
173
172
  true,
174
173
  );
@@ -192,23 +191,22 @@ describe("../request-tracking.js", () => {
192
191
  * - Handlers that reject the promise
193
192
  * - Handlers that resolve
194
193
  */
195
- const fakeBadRequestHandler = () =>
194
+ const fakeBadRequestHandler = (): Promise<any> =>
196
195
  new Promise((resolve: any, reject: any) =>
197
196
  reject("OH NO!"),
198
197
  );
199
- const fakeBadHandler = () => {
198
+ const fakeBadHandler = (): Promise<any> => {
200
199
  throw new Error("OH NO!");
201
200
  };
202
- const fakeValidHandler = (() => {
201
+ const fakeValidHandler = ((): (() => Promise<any>) => {
203
202
  let counter = 0;
204
- return (o: any) => {
203
+ return () => {
205
204
  counter++;
206
205
  return Promise.resolve(`DATA:${counter}`);
207
206
  };
208
207
  })();
209
208
  requestTracker.trackDataRequest(
210
209
  "BAD_REQUEST",
211
- // @ts-expect-error [FEI-5019] - TS2345 - Argument of type '() => Promise<unknown>' is not assignable to parameter of type '() => Promise<ValidCacheData>'.
212
210
  fakeBadRequestHandler,
213
211
  true,
214
212
  );
@@ -219,13 +217,11 @@ describe("../request-tracking.js", () => {
219
217
  );
220
218
  requestTracker.trackDataRequest(
221
219
  "VALID_HANDLER1",
222
- // @ts-expect-error [FEI-5019] - TS2345 - Argument of type '(o: any) => Promise<string>' is not assignable to parameter of type '() => Promise<string>'.
223
220
  fakeValidHandler,
224
221
  true,
225
222
  );
226
223
  requestTracker.trackDataRequest(
227
224
  "VALID_HANDLER2",
228
- // @ts-expect-error [FEI-5019] - TS2345 - Argument of type '(o: any) => Promise<string>' is not assignable to parameter of type '() => Promise<string>'.
229
225
  fakeValidHandler,
230
226
  true,
231
227
  );
@@ -70,8 +70,8 @@ describe("#resultFromCachedResponse", () => {
70
70
  };
71
71
 
72
72
  // Act
73
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'error' does not exist on type 'Result<ValidCacheData> | null | undefined'.
74
- const {error} = resultFromCachedResponse(cacheEntry);
73
+ const cacheResult = resultFromCachedResponse(cacheEntry);
74
+ const error = cacheResult?.status === "error" && cacheResult.error;
75
75
 
76
76
  // Assert
77
77
  expect(error).toMatchInlineSnapshot(`[HydratedDataError: ERROR]`);
@@ -9,12 +9,11 @@ describe("SerializableInMemoryCache", () => {
9
9
  scope: {
10
10
  key: "value",
11
11
  },
12
- } as const;
12
+ };
13
13
 
14
14
  // Act
15
15
  const cache = new SerializableInMemoryCache(sourceData);
16
16
  // Try to mutate the cache.
17
- // @ts-expect-error [FEI-5019] - TS2540 - Cannot assign to 'scope' because it is a read-only property.
18
17
  sourceData["scope"] = {key: "SOME_NEW_DATA"};
19
18
  const result = cache.get("scope", "key");
20
19
 
@@ -125,12 +125,11 @@ describe("../ssr-cache.js", () => {
125
125
  const cache = new SsrCache();
126
126
  const sourceData = {
127
127
  MY_KEY: {data: "THE_DATA"},
128
- } as const;
128
+ };
129
129
 
130
130
  // Act
131
131
  cache.initialize(sourceData);
132
132
  // Try to mutate the cache.
133
- // @ts-expect-error [FEI-5019] - TS2540 - Cannot assign to 'MY_KEY' because it is a read-only property.
134
133
  sourceData["MY_KEY"] = {data: "SOME_NEW_DATA"};
135
134
  const result = cache.getEntry("MY_KEY");
136
135
 
@@ -448,8 +447,7 @@ describe("../ssr-cache.js", () => {
448
447
  const cloneSpy = jest
449
448
  .spyOn(hydrationCache, "clone")
450
449
  .mockReturnValue({
451
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'string' is not assignable to type '{ [id: string]: ValidCacheData; }'.
452
- default: "CLONE!",
450
+ default: "CLONE!" as any,
453
451
  });
454
452
  const cache = new SsrCache(hydrationCache);
455
453
  // Let's add to the initialized state to check that everything
@@ -9,11 +9,10 @@ describe("#toGqlOperation", () => {
9
9
  const documentNode: any = {};
10
10
  const parserSpy = jest
11
11
  .spyOn(GDNP, "graphQLDocumentNodeParser")
12
- // @ts-expect-error [FEI-5019] - TS2345 - Argument of type '{ name: string; type: string; }' is not assignable to parameter of type 'IDocumentDefinition'.
13
12
  .mockReturnValue({
14
13
  name: "operationName",
15
14
  type: "query",
16
- });
15
+ } as any);
17
16
 
18
17
  // Act
19
18
  toGqlOperation(documentNode);
@@ -25,11 +24,10 @@ describe("#toGqlOperation", () => {
25
24
  it("should return the Wonder Blocks Data representation of the given document node", () => {
26
25
  // Arrange
27
26
  const documentNode: any = {};
28
- // @ts-expect-error [FEI-5019] - TS2345 - Argument of type '{ name: string; type: string; }' is not assignable to parameter of type 'IDocumentDefinition'.
29
27
  jest.spyOn(GDNP, "graphQLDocumentNodeParser").mockReturnValue({
30
28
  name: "operationName",
31
29
  type: "mutation",
32
- });
30
+ } as any);
33
31
 
34
32
  // Act
35
33
  const result = toGqlOperation(documentNode);
@@ -59,20 +59,20 @@ export function graphQLDocumentNodeParser(
59
59
 
60
60
  const queries = document.definitions.filter(
61
61
  (x: DefinitionNode) =>
62
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'operation' does not exist on type 'DefinitionNode'.
63
- x.kind === "OperationDefinition" && x.operation === "query",
62
+ x.kind === "OperationDefinition" &&
63
+ (x as OperationDefinitionNode).operation === "query",
64
64
  );
65
65
 
66
66
  const mutations = document.definitions.filter(
67
67
  (x: DefinitionNode) =>
68
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'operation' does not exist on type 'DefinitionNode'.
69
- x.kind === "OperationDefinition" && x.operation === "mutation",
68
+ x.kind === "OperationDefinition" &&
69
+ (x as OperationDefinitionNode).operation === "mutation",
70
70
  );
71
71
 
72
72
  const subscriptions = document.definitions.filter(
73
73
  (x: DefinitionNode) =>
74
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'operation' does not exist on type 'DefinitionNode'.
75
- x.kind === "OperationDefinition" && x.operation === "subscription",
74
+ x.kind === "OperationDefinition" &&
75
+ (x as OperationDefinitionNode).operation === "subscription",
76
76
  );
77
77
 
78
78
  if (fragments.length && !queries.length && !mutations.length) {
@@ -23,7 +23,8 @@ export const mergeGqlContext = <TContext extends GqlContext>(
23
23
  delete acc[key];
24
24
  } else {
25
25
  // Otherwise, we set it.
26
- // @ts-expect-error [FEI-5019] - TS2536 - Type 'string' cannot be used to index type 'TContext'.
26
+ // @ts-expect-error TypeScript doesn't seem to see that
27
+ // TContext can have string keys.
27
28
  acc[key] = overrides[key];
28
29
  }
29
30
  }
@@ -53,8 +53,7 @@ export class RequestTracker {
53
53
  _responseCache: SsrCache;
54
54
  _requestFulfillment: RequestFulfillment;
55
55
 
56
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'undefined' is not assignable to type 'SsrCache | null'.
57
- constructor(responseCache: SsrCache | null = undefined) {
56
+ constructor(responseCache?: SsrCache | null) {
58
57
  this._responseCache = responseCache || SsrCache.Default;
59
58
  this._requestFulfillment = new RequestFulfillment();
60
59
  }
@@ -160,6 +159,11 @@ export class RequestTracker {
160
159
  // the code wrong. Rather than bloat
161
160
  // code with useless error, just ignore.
162
161
 
162
+ // For status === "no-data":
163
+ // Could never get here unless we wrote
164
+ // the code wrong. Rather than bloat
165
+ // code with useless error, just ignore.
166
+
163
167
  // For status === "aborted":
164
168
  // We won't cache this.
165
169
  // We don't hydrate aborted requests,
@@ -124,7 +124,7 @@ export class SsrCache {
124
124
  : null;
125
125
 
126
126
  // Now we defer to the SSR value, and fallback to the hydration cache.
127
- const internalEntry =
127
+ const internalEntry: ValidCacheData | null | undefined =
128
128
  ssrEntry ?? this._hydrationCache.get(DefaultScope, id);
129
129
 
130
130
  // If we are not server-side and we hydrated something, let's clear
@@ -140,8 +140,10 @@ export class SsrCache {
140
140
  }
141
141
  // Getting the typing right between the in-memory cache and this
142
142
  // is hard. Just telling TypeScript it's OK.
143
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'string | number | boolean | Record<any, any> | null | undefined' is not assignable to type 'Readonly<CachedResponse<TData>> | null | undefined'.
144
- return internalEntry;
143
+ return internalEntry as
144
+ | Readonly<CachedResponse<TData>>
145
+ | null
146
+ | undefined;
145
147
  };
146
148
 
147
149
  /**
@@ -161,9 +163,11 @@ export class SsrCache {
161
163
  const realPredicate = predicate
162
164
  ? // We know what we're putting into the cache so let's assume it
163
165
  // conforms.
164
- // @ts-expect-error [FEI-5019] - TS7006 - Parameter 'cachedEntry' implicitly has an 'any' type.
165
- (_: string, key: string, cachedEntry) =>
166
- predicate(key, cachedEntry)
166
+ (_: string, key: string, cachedEntry: ValidCacheData) =>
167
+ predicate(
168
+ key,
169
+ cachedEntry as Readonly<CachedResponse<ValidCacheData>>,
170
+ )
167
171
  : undefined;
168
172
 
169
173
  // Apply the predicate to what we have in our caches.
@@ -184,7 +188,6 @@ export class SsrCache {
184
188
  // to an empty object.
185
189
  // We only need the default scope out of our scoped in-memory cache.
186
190
  // We know that it conforms to our expectations.
187
- // @ts-expect-error [FEI-5019] - TS2322 - Type '{ [id: string]: ValidCacheData; }' is not assignable to type 'ResponseCache'.
188
- return cache[DefaultScope] ?? {};
191
+ return (cache[DefaultScope] as ResponseCache) ?? {};
189
192
  };
190
193
  }
@@ -4,6 +4,10 @@ const loadingStatus = Object.freeze({
4
4
  status: "loading",
5
5
  });
6
6
 
7
+ const noDataStatus = Object.freeze({
8
+ status: "no-data",
9
+ });
10
+
7
11
  const abortedStatus = Object.freeze({
8
12
  status: "aborted",
9
13
  });
@@ -14,6 +18,8 @@ const abortedStatus = Object.freeze({
14
18
  export const Status = Object.freeze({
15
19
  loading: <TData extends ValidCacheData = ValidCacheData>(): Result<TData> =>
16
20
  loadingStatus,
21
+ noData: <TData extends ValidCacheData = ValidCacheData>(): Result<TData> =>
22
+ noDataStatus,
17
23
  aborted: <TData extends ValidCacheData = ValidCacheData>(): Result<TData> =>
18
24
  abortedStatus,
19
25
  success: <TData extends ValidCacheData>(data: TData): Result<TData> => ({