1/* eslint-disable @typescript-eslint/no-use-before-define */
2import { filter, merge, mergeMap, pipe, takeUntil, onPush } from 'wonka';
3
4import type { Exchange } from '../types';
5import {
6 makeFetchBody,
7 makeFetchURL,
8 makeFetchOptions,
9 makeFetchSource,
10} from '../internal';
11
12/** Default GraphQL over HTTP fetch exchange.
13 *
14 * @remarks
15 * The default fetch exchange in `urql` supports sending GraphQL over HTTP
16 * requests, can optionally send GraphQL queries as GET requests, and
17 * handles incremental multipart responses.
18 *
19 * This exchange does not handle persisted queries or multipart uploads.
20 * Support for the former can be added using `@urql/exchange-persisted-fetch`
21 * and the latter using `@urql/exchange-multipart-fetch`.
22 *
23 * Hint: The `fetchExchange` and the two other exchanges all use the built-in fetch
24 * utilities in `@urql/core/internal`, which you can also use to implement
25 * a customized fetch exchange.
26 *
27 * @see {@link makeFetchSource} for the shared utility calling the Fetch API.
28 */
29export const fetchExchange: Exchange = ({ forward, dispatchDebug }) => {
30 return ops$ => {
31 const fetchResults$ = pipe(
32 ops$,
33 filter(operation => {
34 return (
35 operation.kind !== 'teardown' &&
36 (operation.kind !== 'subscription' ||
37 !!operation.context.fetchSubscriptions)
38 );
39 }),
40 mergeMap(operation => {
41 const body = makeFetchBody(operation);
42 const url = makeFetchURL(operation, body);
43 const fetchOptions = makeFetchOptions(operation, body);
44
45 dispatchDebug({
46 type: 'fetchRequest',
47 message: 'A fetch request is being executed.',
48 operation,
49 data: {
50 url,
51 fetchOptions,
52 },
53 });
54
55 const source = pipe(
56 makeFetchSource(operation, url, fetchOptions),
57 takeUntil(
58 pipe(
59 ops$,
60 filter(op => op.kind === 'teardown' && op.key === operation.key)
61 )
62 )
63 );
64
65 if (process.env.NODE_ENV !== 'production') {
66 return pipe(
67 source,
68 onPush(result => {
69 const error = !result.data ? result.error : undefined;
70
71 dispatchDebug({
72 type: error ? 'fetchError' : 'fetchSuccess',
73 message: `A ${
74 error ? 'failed' : 'successful'
75 } fetch response has been returned.`,
76 operation,
77 data: {
78 url,
79 fetchOptions,
80 value: error || result,
81 },
82 });
83 })
84 );
85 }
86
87 return source;
88 })
89 );
90
91 const forward$ = pipe(
92 ops$,
93 filter(operation => {
94 return (
95 operation.kind === 'teardown' ||
96 (operation.kind === 'subscription' &&
97 !operation.context.fetchSubscriptions)
98 );
99 }),
100 forward
101 );
102
103 return merge([fetchResults$, forward$]);
104 };
105};