@khanacademy/wonder-blocks-data 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@ import TrackData from "../track-data.js";
11
11
  import {RequestFulfillment} from "../../util/request-fulfillment.js";
12
12
  import {SsrCache} from "../../util/ssr-cache.js";
13
13
  import {RequestTracker} from "../../util/request-tracking.js";
14
- import InterceptData from "../intercept-data.js";
14
+ import InterceptRequests from "../intercept-requests.js";
15
15
  import Data from "../data.js";
16
16
 
17
17
  describe("Data", () => {
@@ -362,14 +362,11 @@ describe("Data", () => {
362
362
 
363
363
  // Act
364
364
  render(
365
- <InterceptData
366
- requestId="ID"
367
- handler={interceptHandler}
368
- >
365
+ <InterceptRequests interceptor={interceptHandler}>
369
366
  <Data handler={fakeHandler} requestId="ID">
370
367
  {fakeChildrenFn}
371
368
  </Data>
372
- </InterceptData>,
369
+ </InterceptRequests>,
373
370
  );
374
371
 
375
372
  // Assert
@@ -385,14 +382,11 @@ describe("Data", () => {
385
382
 
386
383
  // Act
387
384
  render(
388
- <InterceptData
389
- handler={interceptHandler}
390
- requestId="ID"
391
- >
385
+ <InterceptRequests interceptor={interceptHandler}>
392
386
  <Data handler={fakeHandler} requestId="ID">
393
387
  {fakeChildrenFn}
394
388
  </Data>
395
- </InterceptData>,
389
+ </InterceptRequests>,
396
390
  );
397
391
 
398
392
  // Assert
@@ -662,14 +656,11 @@ describe("Data", () => {
662
656
 
663
657
  // Act
664
658
  ReactDOMServer.renderToString(
665
- <InterceptData
666
- handler={interceptedHandler}
667
- requestId="ID"
668
- >
659
+ <InterceptRequests interceptor={interceptedHandler}>
669
660
  <Data handler={fakeHandler} requestId="ID">
670
661
  {fakeChildrenFn}
671
662
  </Data>
672
- </InterceptData>,
663
+ </InterceptRequests>,
673
664
  );
674
665
 
675
666
  // Assert
@@ -692,14 +683,11 @@ describe("Data", () => {
692
683
  // Act
693
684
  ReactDOMServer.renderToString(
694
685
  <TrackData>
695
- <InterceptData
696
- requestId="ID"
697
- handler={interceptedHandler}
698
- >
686
+ <InterceptRequests interceptor={interceptedHandler}>
699
687
  <Data handler={fakeHandler} requestId="ID">
700
688
  {fakeChildrenFn}
701
689
  </Data>
702
- </InterceptData>
690
+ </InterceptRequests>
703
691
  </TrackData>,
704
692
  );
705
693
 
@@ -817,14 +805,11 @@ describe("Data", () => {
817
805
 
818
806
  // Act
819
807
  ReactDOMServer.renderToString(
820
- <InterceptData
821
- handler={interceptHandler}
822
- requestId="ID"
823
- >
808
+ <InterceptRequests interceptor={interceptHandler}>
824
809
  <Data handler={fakeHandler} requestId="ID">
825
810
  {fakeChildrenFn}
826
811
  </Data>
827
- </InterceptData>,
812
+ </InterceptRequests>,
828
813
  );
829
814
 
830
815
  // Assert
@@ -0,0 +1,58 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {render} from "@testing-library/react";
4
+
5
+ import InterceptContext from "../intercept-context.js";
6
+ import InterceptRequests from "../intercept-requests.js";
7
+
8
+ describe("InterceptRequests", () => {
9
+ afterEach(() => {
10
+ jest.resetAllMocks();
11
+ });
12
+
13
+ it("should update context with fulfillRequest method", () => {
14
+ // Arrange
15
+ const fakeHandler = (requestId): Promise<string> =>
16
+ Promise.resolve("data");
17
+ const props = {
18
+ interceptor: fakeHandler,
19
+ };
20
+ const captureContextFn = jest.fn();
21
+
22
+ // Act
23
+ render(
24
+ <InterceptRequests {...props}>
25
+ <InterceptContext.Consumer>
26
+ {captureContextFn}
27
+ </InterceptContext.Consumer>
28
+ </InterceptRequests>,
29
+ );
30
+
31
+ // Assert
32
+ expect(captureContextFn).toHaveBeenCalledWith([fakeHandler]);
33
+ });
34
+
35
+ it("should override parent InterceptRequests", () => {
36
+ // Arrange
37
+ const fakeHandler1 = jest.fn();
38
+ const fakeHandler2 = jest.fn();
39
+ const captureContextFn = jest.fn();
40
+
41
+ // Act
42
+ render(
43
+ <InterceptRequests interceptor={fakeHandler1}>
44
+ <InterceptRequests interceptor={fakeHandler2}>
45
+ <InterceptContext.Consumer>
46
+ {captureContextFn}
47
+ </InterceptContext.Consumer>
48
+ </InterceptRequests>
49
+ </InterceptRequests>,
50
+ );
51
+
52
+ // Assert
53
+ expect(captureContextFn).toHaveBeenCalledWith([
54
+ fakeHandler1,
55
+ fakeHandler2,
56
+ ]);
57
+ });
58
+ });
@@ -3,8 +3,8 @@ import * as React from "react";
3
3
 
4
4
  import {Server} from "@khanacademy/wonder-blocks-core";
5
5
  import {RequestFulfillment} from "../util/request-fulfillment.js";
6
- import InterceptContext from "./intercept-context.js";
7
6
  import {useServerEffect} from "../hooks/use-server-effect.js";
7
+ import {useRequestInterception} from "../hooks/use-request-interception.js";
8
8
  import {resultFromCachedResponse} from "../util/result-from-cache-response.js";
9
9
 
10
10
  import type {Result, ValidCacheData} from "../util/types.js";
@@ -80,25 +80,11 @@ const Data = <TData: ValidCacheData>({
80
80
  showOldDataWhileLoading,
81
81
  alwaysRequestOnHydration,
82
82
  }: Props<TData>): React.Node => {
83
- // Lookup to see if there's an interceptor for the handler.
84
- // If we have one, we need to replace the handler with one that
85
- // uses the interceptor.
86
- const interceptorMap = React.useContext(InterceptContext);
87
-
88
- // If we have an interceptor, we need to replace the handler with one
89
- // that uses the interceptor. This helper function generates a new
90
- // handler.
91
- const maybeInterceptedHandler = React.useMemo(() => {
92
- const interceptor = interceptorMap[requestId];
93
- if (interceptor == null) {
94
- return handler;
95
- }
96
- return () => interceptor() ?? handler();
97
- }, [handler, interceptorMap, requestId]);
83
+ const interceptedHandler = useRequestInterception(requestId, handler);
98
84
 
99
85
  const hydrateResult = useServerEffect(
100
86
  requestId,
101
- maybeInterceptedHandler,
87
+ interceptedHandler,
102
88
  hydrate,
103
89
  );
104
90
  const [currentResult, setResult] = React.useState(hydrateResult);
@@ -137,7 +123,7 @@ const Data = <TData: ValidCacheData>({
137
123
  // hydrated value.
138
124
  let cancel = false;
139
125
  RequestFulfillment.Default.fulfill(requestId, {
140
- handler: maybeInterceptedHandler,
126
+ handler: interceptedHandler,
141
127
  })
142
128
  .then((result) => {
143
129
  if (cancel) {
@@ -2,10 +2,9 @@
2
2
  import * as React from "react";
3
3
  import type {ValidCacheData} from "../util/types.js";
4
4
 
5
- type InterceptContextData = {
6
- [id: string]: <TData: ValidCacheData>() => ?Promise<?TData>,
7
- ...
8
- };
5
+ type InterceptContextData = $ReadOnlyArray<
6
+ (requestId: string) => ?Promise<?ValidCacheData>,
7
+ >;
9
8
 
10
9
  /**
11
10
  * InterceptContext defines a map from request ID to interception methods.
@@ -13,6 +12,6 @@ type InterceptContextData = {
13
12
  * INTERNAL USE ONLY
14
13
  */
15
14
  const InterceptContext: React.Context<InterceptContextData> =
16
- React.createContext<InterceptContextData>({});
15
+ React.createContext<InterceptContextData>([]);
17
16
 
18
17
  export default InterceptContext;
@@ -0,0 +1,69 @@
1
+ // @flow
2
+ import * as React from "react";
3
+
4
+ import InterceptContext from "./intercept-context.js";
5
+
6
+ import type {ValidCacheData} from "../util/types.js";
7
+
8
+ type Props<TData: ValidCacheData> = {|
9
+ /**
10
+ * Called to intercept and possibly handle the request.
11
+ * If this returns null, the request will be handled by ancestor
12
+ * any ancestor interceptors, and ultimately, the original request
13
+ * handler, otherwise, this interceptor is handling the request.
14
+ *
15
+ * Interceptors are called in ancestor precedence, with the closest
16
+ * interceptor ancestor being called first, and the furthest ancestor
17
+ * being called last.
18
+ *
19
+ * Beware: Interceptors do not care about what data they are intercepting,
20
+ * so make sure to only intercept requests that you recognize from the
21
+ * identifier.
22
+ */
23
+ interceptor: (requestId: string) => ?Promise<?TData>,
24
+
25
+ /**
26
+ * The children to render within this component. Any requests by `Data`
27
+ * components that use same ID as this component will be intercepted.
28
+ * If `InterceptRequests` is used within `children`, that interception will
29
+ * be given a chance to intercept first.
30
+ */
31
+ children: React.Node,
32
+ |};
33
+
34
+ /**
35
+ * This component provides a mechanism to intercept data requests.
36
+ * This is for use in testing.
37
+ *
38
+ * This component is not recommended for use in production code as it
39
+ * can prevent predictable functioning of the Wonder Blocks Data framework.
40
+ * One possible side-effect is that inflight requests from the interceptor could
41
+ * be picked up by `Data` component requests from outside the children of this
42
+ * component.
43
+ *
44
+ * Interceptions within the same component tree are chained such that the
45
+ * interceptor closest to the intercepted request is called first, and the
46
+ * furthest interceptor is called last.
47
+ */
48
+ const InterceptRequests = <TData: ValidCacheData>({
49
+ interceptor,
50
+ children,
51
+ }: Props<TData>): React.Node => {
52
+ const interceptors = React.useContext(InterceptContext);
53
+
54
+ const updatedInterceptors = React.useMemo(
55
+ // We could build this in reverse order so that our hook that does
56
+ // the interception didn't have to use reduceRight, but I think it
57
+ // is easier to think about if we do this in component tree order.
58
+ () => [...interceptors, interceptor],
59
+ [interceptors, interceptor],
60
+ );
61
+
62
+ return (
63
+ <InterceptContext.Provider value={updatedInterceptors}>
64
+ {children}
65
+ </InterceptContext.Provider>
66
+ );
67
+ };
68
+
69
+ export default InterceptRequests;
@@ -1,38 +1,41 @@
1
1
  When you want to generate tests that check the loading state and
2
2
  subsequent loaded state are working correctly for your uses of `Data` you can
3
- use the `InterceptData` component.
3
+ use the `InterceptRequests` component. You can also use this component to
4
+ register request interceptors for any code that uses the `useRequestInterception`
5
+ hook.
4
6
 
5
- This component takes three props; children to be rendered, the handler of the
6
- to fulfill the request, and the id of the request that is being intercepted.
7
+ This component takes the children to be rendered, and an interceptor function.
7
8
 
8
- Note that this component is expected to be used only within test cases and
9
- usually only as a single instance. In flight requests for a given handler
10
- type can be shared and as such, using `InterceptData` alongside non-intercepted
11
- `Data` components with the same id can have indeterminate outcomes.
9
+ Note that this component is expected to be used only within test cases or
10
+ stories. Be careful want request IDs are matched to avoid intercepting the
11
+ wrong requests and remember that in-flight requests for a given request ID
12
+ can be shared - which means a bad request ID match could share requests across
13
+ different request IDs..
12
14
 
13
- The `handler` intercept function has the form:
15
+ The `interceptor` intercept function has the form:
14
16
 
15
17
  ```js static
16
- () => ?Promise<?TData>;
18
+ (requestId: string) => ?Promise<?TData>;
17
19
  ```
18
20
 
19
- If this method returns `null`, the default behavior occurs. This
21
+ If this method returns `null`, then the next interceptor in the chain is
22
+ invoked, ultimately ending with the original handler. This
20
23
  means that a request will be made for data via the handler assigned to the
21
- `Data` component being intercepted.
24
+ `Data` component being intercepted if no interceptor handles the request first.
22
25
 
23
26
  ```jsx
24
27
  import {Body, BodyMonospace} from "@khanacademy/wonder-blocks-typography";
25
28
  import {View} from "@khanacademy/wonder-blocks-core";
26
- import {InterceptData, Data} from "@khanacademy/wonder-blocks-data";
29
+ import {InterceptRequests, Data} from "@khanacademy/wonder-blocks-data";
27
30
  import {Strut} from "@khanacademy/wonder-blocks-layout";
28
31
  import Color from "@khanacademy/wonder-blocks-color";
29
32
  import Spacing from "@khanacademy/wonder-blocks-spacing";
30
33
 
31
34
  const myHandler = () => Promise.reject(new Error("You should not see this!"));
32
35
 
33
- const interceptHandler = () => Promise.resolve("INTERCEPTED DATA!");
36
+ const interceptor = (requestId) => requestId === "INTERCEPT_EXAMPLE" ? Promise.resolve("INTERCEPTED DATA!") : null;
34
37
 
35
- <InterceptData handler={interceptHandler} requestId="INTERCEPT_EXAMPLE">
38
+ <InterceptRequests interceptor={interceptor}>
36
39
  <View>
37
40
  <Body>This received intercepted data!</Body>
38
41
  <Data handler={myHandler} requestId="INTERCEPT_EXAMPLE">
@@ -47,5 +50,5 @@ const interceptHandler = () => Promise.resolve("INTERCEPTED DATA!");
47
50
  }}
48
51
  </Data>
49
52
  </View>
50
- </InterceptData>
53
+ </InterceptRequests>
51
54
  ```
@@ -0,0 +1,255 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {renderHook} from "@testing-library/react-hooks";
4
+ import InterceptRequests from "../../components/intercept-requests.js";
5
+ import {useRequestInterception} from "../use-request-interception.js";
6
+
7
+ describe("#useRequestInterception", () => {
8
+ it("should return a function", () => {
9
+ // Arrange
10
+
11
+ // Act
12
+ const {
13
+ result: {current: result},
14
+ } = renderHook(() => useRequestInterception("ID", jest.fn()));
15
+
16
+ // Assert
17
+ expect(result).toBeInstanceOf(Function);
18
+ });
19
+
20
+ it("should return the same function if the arguments and context don't change", () => {
21
+ // Arrange
22
+ const handler = jest.fn();
23
+
24
+ // Act
25
+ const wrapper = renderHook(() => useRequestInterception("ID", handler));
26
+ const result1 = wrapper.result.current;
27
+ wrapper.rerender();
28
+ const result2 = wrapper.result.current;
29
+
30
+ // Assert
31
+ expect(result1).toBe(result2);
32
+ });
33
+
34
+ it("should return a new function if the requestId changes", () => {
35
+ // Arrange
36
+ const handler = jest.fn();
37
+
38
+ // Act
39
+ const wrapper = renderHook(
40
+ ({requestId}) => useRequestInterception(requestId, handler),
41
+ {initialProps: {requestId: "ID"}},
42
+ );
43
+ const result1 = wrapper.result.current;
44
+ wrapper.rerender({requestId: "ID2"});
45
+ const result2 = wrapper.result.current;
46
+
47
+ // Assert
48
+ expect(result1).not.toBe(result2);
49
+ });
50
+
51
+ it("should return a new function if the handler changes", () => {
52
+ // Arrange
53
+
54
+ // Act
55
+ const wrapper = renderHook(
56
+ ({handler}) => useRequestInterception("ID", handler),
57
+ {initialProps: {handler: jest.fn()}},
58
+ );
59
+ const result1 = wrapper.result.current;
60
+ wrapper.rerender({handler: jest.fn()});
61
+ const result2 = wrapper.result.current;
62
+
63
+ // Assert
64
+ expect(result1).not.toBe(result2);
65
+ });
66
+
67
+ it("should return a new function if the context changes", () => {
68
+ // Arrange
69
+ const handler = jest.fn();
70
+ const interceptor1 = jest.fn();
71
+ const interceptor2 = jest.fn();
72
+ const Wrapper = ({children, interceptor}: any) => (
73
+ <InterceptRequests interceptor={interceptor}>
74
+ {children}
75
+ </InterceptRequests>
76
+ );
77
+
78
+ // Act
79
+ const wrapper = renderHook(
80
+ () => useRequestInterception("ID", handler),
81
+ {wrapper: Wrapper, initialProps: {interceptor: interceptor1}},
82
+ );
83
+ const result1 = wrapper.result.current;
84
+ wrapper.rerender({wrapper: Wrapper, interceptor: interceptor2});
85
+ const result2 = wrapper.result.current;
86
+
87
+ // Assert
88
+ expect(result1).not.toBe(result2);
89
+ });
90
+
91
+ describe("returned function", () => {
92
+ it("should invoke the original handler when there are no interceptors", () => {
93
+ // Arrange
94
+ const handler = jest.fn();
95
+ const requestId = "ID";
96
+ const {
97
+ result: {current: interceptedHandler},
98
+ } = renderHook(() => useRequestInterception(requestId, handler));
99
+
100
+ // Act
101
+ interceptedHandler();
102
+
103
+ // Assert
104
+ expect(handler).toHaveBeenCalledTimes(1);
105
+ });
106
+
107
+ it("should invoke interceptors nearest to furthest", () => {
108
+ // Arrange
109
+ const handler = jest.fn();
110
+ const interceptorFurthest = jest.fn(() => null);
111
+ const interceptorNearest = jest.fn(() => null);
112
+ const Wrapper = ({children}: any) => (
113
+ <InterceptRequests interceptor={interceptorFurthest}>
114
+ <InterceptRequests interceptor={interceptorNearest}>
115
+ {children}
116
+ </InterceptRequests>
117
+ </InterceptRequests>
118
+ );
119
+ const {
120
+ result: {current: interceptedHandler},
121
+ } = renderHook(() => useRequestInterception("ID", handler), {
122
+ wrapper: Wrapper,
123
+ });
124
+
125
+ // Act
126
+ interceptedHandler();
127
+
128
+ // Assert
129
+ expect(interceptorNearest).toHaveBeenCalledBefore(
130
+ interceptorFurthest,
131
+ );
132
+ });
133
+
134
+ it("should invoke the handler last", () => {
135
+ // Arrange
136
+ const handler = jest.fn();
137
+ const interceptorFurthest = jest.fn(() => null);
138
+ const interceptorNearest = jest.fn(() => null);
139
+ const Wrapper = ({children}: any) => (
140
+ <InterceptRequests interceptor={interceptorFurthest}>
141
+ <InterceptRequests interceptor={interceptorNearest}>
142
+ {children}
143
+ </InterceptRequests>
144
+ </InterceptRequests>
145
+ );
146
+ const {
147
+ result: {current: interceptedHandler},
148
+ } = renderHook(() => useRequestInterception("ID", handler), {
149
+ wrapper: Wrapper,
150
+ });
151
+
152
+ // Act
153
+ interceptedHandler();
154
+
155
+ // Assert
156
+ expect(interceptorFurthest).toHaveBeenCalledBefore(handler);
157
+ });
158
+
159
+ it("should invoke the original handler when there all interceptors return null", () => {
160
+ // Arrange
161
+ const handler = jest.fn();
162
+ const interceptor1 = jest.fn(() => null);
163
+ const interceptor2 = jest.fn(() => null);
164
+ const Wrapper = ({children}: any) => (
165
+ <InterceptRequests interceptor={interceptor1}>
166
+ <InterceptRequests interceptor={interceptor2}>
167
+ {children}
168
+ </InterceptRequests>
169
+ </InterceptRequests>
170
+ );
171
+ const {
172
+ result: {current: interceptedHandler},
173
+ } = renderHook(() => useRequestInterception("ID", handler), {
174
+ wrapper: Wrapper,
175
+ });
176
+
177
+ // Act
178
+ interceptedHandler();
179
+
180
+ // Assert
181
+ expect(handler).toHaveBeenCalledTimes(1);
182
+ });
183
+
184
+ it("should return the result of the nearest interceptor that returns a non-null result", async () => {
185
+ // Arrange
186
+ const handler = jest
187
+ .fn()
188
+ .mockRejectedValue(
189
+ new Error("This handler should have been intercepted"),
190
+ );
191
+ const interceptorFurthest = jest
192
+ .fn()
193
+ .mockRejectedValue(
194
+ new Error("This interceptor should not get called"),
195
+ );
196
+ const interceptorNearest = jest
197
+ .fn()
198
+ .mockResolvedValue("INTERCEPTED_DATA");
199
+ const Wrapper = ({children}: any) => (
200
+ <InterceptRequests interceptor={interceptorFurthest}>
201
+ <InterceptRequests interceptor={interceptorNearest}>
202
+ {children}
203
+ </InterceptRequests>
204
+ </InterceptRequests>
205
+ );
206
+ const {
207
+ result: {current: interceptedHandler},
208
+ } = renderHook(() => useRequestInterception("ID", handler), {
209
+ wrapper: Wrapper,
210
+ });
211
+
212
+ // Act
213
+ const result = await interceptedHandler();
214
+
215
+ // Assert
216
+ expect(result).toBe("INTERCEPTED_DATA");
217
+ });
218
+
219
+ it("should not invoke interceptors or handlers beyond a non-null interception", () => {
220
+ // Arrange
221
+ const handler = jest
222
+ .fn()
223
+ .mockRejectedValue(
224
+ new Error("This handler should have been intercepted"),
225
+ );
226
+ const interceptorFurthest = jest
227
+ .fn()
228
+ .mockRejectedValue(
229
+ new Error("This interceptor should not get called"),
230
+ );
231
+ const interceptorNearest = jest
232
+ .fn()
233
+ .mockResolvedValue("INTERCEPTED_DATA");
234
+ const Wrapper = ({children}: any) => (
235
+ <InterceptRequests interceptor={interceptorFurthest}>
236
+ <InterceptRequests interceptor={interceptorNearest}>
237
+ {children}
238
+ </InterceptRequests>
239
+ </InterceptRequests>
240
+ );
241
+ const {
242
+ result: {current: interceptedHandler},
243
+ } = renderHook(() => useRequestInterception("ID", handler), {
244
+ wrapper: Wrapper,
245
+ });
246
+
247
+ // Act
248
+ interceptedHandler();
249
+
250
+ // Assert
251
+ expect(handler).not.toHaveBeenCalled();
252
+ expect(interceptorFurthest).not.toHaveBeenCalled();
253
+ });
254
+ });
255
+ });
@@ -0,0 +1,54 @@
1
+ // @flow
2
+ import * as React from "react";
3
+
4
+ import InterceptContext from "../components/intercept-context.js";
5
+ import type {ValidCacheData} from "../util/types.js";
6
+
7
+ /**
8
+ * Allow request handling to be intercepted.
9
+ *
10
+ * Hook to take a uniquely identified request handler and return a
11
+ * method that will support request interception from the InterceptRequest
12
+ * component.
13
+ *
14
+ * If you want request interception to be supported with `useServerEffect` or
15
+ * any client-side effect that uses the handler, call this first to generate
16
+ * an intercepted handler, and then invoke `useServerEffect` (or other things)
17
+ * with that intercepted handler.
18
+ */
19
+ export const useRequestInterception = <TData: ValidCacheData>(
20
+ requestId: string,
21
+ handler: () => Promise<?TData>,
22
+ ): (() => Promise<?TData>) => {
23
+ // Get the interceptors that have been registered.
24
+ const interceptors = React.useContext(InterceptContext);
25
+
26
+ // Now, we need to create a new handler that will check if the
27
+ // request is intercepted before ultimately calling the original handler
28
+ // if nothing intercepted it.
29
+ // We memoize this so that it only changes if something related to it
30
+ // changes.
31
+ const interceptedHandler = React.useMemo(
32
+ () => (): Promise<?TData> => {
33
+ // Call the interceptors from closest to furthest.
34
+ // If one returns a non-null result, then we keep that.
35
+ const interceptResponse = interceptors.reduceRight(
36
+ (prev, interceptor) => {
37
+ if (prev != null) {
38
+ return prev;
39
+ }
40
+ return interceptor(requestId);
41
+ },
42
+ null,
43
+ );
44
+ // If nothing intercepted this request, invoke the original handler.
45
+ // NOTE: We can't guarantee all interceptors return the same type
46
+ // as our handler, so how can flow know? Let's just suppress that.
47
+ // $FlowFixMe[incompatible-return]
48
+ return interceptResponse ?? handler();
49
+ },
50
+ [handler, interceptors, requestId],
51
+ );
52
+
53
+ return interceptedHandler;
54
+ };