1import type { Exchange, Operation, OperationContext } from '@urql/core';
2import { makeOperation } from '@urql/core';
3
4import { fromPromise, fromValue, mergeMap, pipe } from 'wonka';
5
6/** Input parameters for the {@link contextExchange}. */
7export interface ContextExchangeArgs {
8 /** Returns a new {@link OperationContext}, optionally wrapped in a `Promise`.
9 *
10 * @remarks
11 * `getContext` is called for every {@link Operation} the `contextExchange`
12 * receives and must return a new {@link OperationContext} or a `Promise`
13 * of it.
14 *
15 * The new `OperationContext` will be used to update the `Operation`'s
16 * context before it's forwarded to the next exchange.
17 */
18 getContext(
19 operation: Operation
20 ): OperationContext | Promise<OperationContext>;
21}
22
23/** Exchange factory modifying the {@link OperationContext} per incoming `Operation`.
24 *
25 * @param options - A {@link ContextExchangeArgs} configuration object.
26 * @returns the created context {@link Exchange}.
27 *
28 * @remarks
29 * The `contextExchange` allows the {@link OperationContext` to be easily
30 * modified per `Operation`. This may be useful to dynamically change the
31 * `Operation`’s parameters, even when we need to do so asynchronously.
32 *
33 * You must define a {@link ContextExchangeArgs.getContext} function,
34 * which may return a `Promise<OperationContext>` or `OperationContext`.
35 *
36 * Hint: If the `getContext` function passed to this exchange returns a
37 * `Promise` it must be placed _after_ all synchronous exchanges, such as
38 * a `cacheExchange`.
39 *
40 * @example
41 * ```ts
42 * import { Client, cacheExchange, fetchExchange } from '@urql/core';
43 * import { contextExchange } from '@urql/exchange-context';
44 *
45 * const client = new Client({
46 * url: '',
47 * exchanges: [
48 * cacheExchange,
49 * contextExchange({
50 * async getContext(operation) {
51 * const url = await loadDynamicUrl();
52 * return {
53 * ...operation.context,
54 * url,
55 * };
56 * },
57 * }),
58 * fetchExchange,
59 * ],
60 * });
61 * ```
62 */
63
64export const contextExchange =
65 ({ getContext }: ContextExchangeArgs): Exchange =>
66 ({ forward }) => {
67 return ops$ => {
68 return pipe(
69 ops$,
70 mergeMap(operation => {
71 const result = getContext(operation);
72 const isPromise = 'then' in result;
73 if (isPromise) {
74 return fromPromise(
75 result.then((ctx: OperationContext) =>
76 makeOperation(operation.kind, operation, ctx)
77 )
78 );
79 } else {
80 return fromValue(
81 makeOperation(
82 operation.kind,
83 operation,
84 result as OperationContext
85 )
86 );
87 }
88 }),
89 forward
90 );
91 };
92 };