0
# Exchange System
1
2
The exchange system is @urql/core's modular pipeline architecture for processing GraphQL operations. Exchanges handle caching, fetching, debugging, subscriptions, and can be composed to create custom operation processing workflows.
3
4
## Capabilities
5
6
### Core Exchanges
7
8
Essential exchanges for basic GraphQL client functionality.
9
10
```typescript { .api }
11
/**
12
* Document-based cache with automatic invalidation by typename
13
* @returns Exchange for document caching
14
*/
15
function cacheExchange(): Exchange;
16
17
/**
18
* HTTP fetch transport for GraphQL requests with multipart/streaming support
19
* @returns Exchange for HTTP request handling
20
*/
21
function fetchExchange(): Exchange;
22
23
/**
24
* Development logging exchange for debugging operations and results
25
* @returns Exchange for development debugging
26
*/
27
function debugExchange(): Exchange;
28
```
29
30
**Usage Examples:**
31
32
```typescript
33
import { createClient, cacheExchange, fetchExchange, debugExchange } from "@urql/core";
34
35
// Basic client with caching and fetching
36
const client = createClient({
37
url: "https://api.example.com/graphql",
38
exchanges: [cacheExchange, fetchExchange],
39
});
40
41
// Development client with debugging
42
const devClient = createClient({
43
url: "https://api.example.com/graphql",
44
exchanges: [debugExchange, cacheExchange, fetchExchange],
45
});
46
```
47
48
### Subscription Exchange
49
50
Handle GraphQL subscriptions with custom transport implementations.
51
52
```typescript { .api }
53
/**
54
* Generic subscription transport exchange
55
* @param options - Subscription transport configuration
56
* @returns Exchange for handling subscriptions
57
*/
58
function subscriptionExchange(options: SubscriptionExchangeOpts): Exchange;
59
60
interface SubscriptionExchangeOpts {
61
/** Function that creates subscription transport */
62
forwardSubscription: SubscriptionForwarder;
63
/** Enable subscriptions via fetch instead of custom transport */
64
enableAllOperations?: boolean;
65
}
66
67
type SubscriptionForwarder = (operation: SubscriptionOperation) =>
68
Subscription | Source<ExecutionResult>;
69
70
type SubscriptionOperation = Operation & {
71
kind: 'subscription';
72
};
73
```
74
75
**Usage Examples:**
76
77
```typescript
78
import { subscriptionExchange } from "@urql/core";
79
import { Client as WSClient, createClient as createWSClient } from 'graphql-ws';
80
81
// WebSocket subscription transport
82
const wsClient = createWSClient({
83
url: 'wss://api.example.com/graphql',
84
});
85
86
const client = createClient({
87
url: "https://api.example.com/graphql",
88
exchanges: [
89
cacheExchange,
90
subscriptionExchange({
91
forwardSubscription: (operation) => ({
92
subscribe: (sink) => ({
93
unsubscribe: wsClient.subscribe(operation, sink),
94
}),
95
}),
96
}),
97
fetchExchange,
98
],
99
});
100
```
101
102
### Server-Side Rendering Exchange
103
104
Cache and rehydrate results for server-side rendering scenarios.
105
106
```typescript { .api }
107
/**
108
* Server-side rendering exchange factory
109
* @param params - SSR configuration options
110
* @returns SSR exchange with serialization methods
111
*/
112
function ssrExchange(params: SSRExchangeParams): SSRExchange;
113
114
interface SSRExchangeParams {
115
/** Initial data for hydration */
116
initialState?: SSRData;
117
/** Enable server-side result serialization */
118
isClient?: boolean;
119
}
120
121
interface SSRExchange extends Exchange {
122
/** Extract serialized data for client hydration */
123
extractData(): SSRData;
124
/** Replace current data with serialized data */
125
restoreData(data: SSRData): void;
126
}
127
128
interface SSRData {
129
[key: string]: SerializedResult;
130
}
131
132
interface SerializedResult {
133
data?: any;
134
error?: {
135
networkError?: string;
136
graphQLErrors?: Array<{ message: string; [key: string]: any }>;
137
};
138
}
139
```
140
141
**Usage Examples:**
142
143
```typescript
144
import { ssrExchange } from "@urql/core";
145
146
// Server-side setup
147
const ssr = ssrExchange({ isClient: false });
148
149
const serverClient = createClient({
150
url: "https://api.example.com/graphql",
151
exchanges: [cacheExchange, ssr, fetchExchange],
152
});
153
154
// Execute queries on server
155
await serverClient.query(GetPageDataQuery, {}).toPromise();
156
157
// Extract data for client
158
const ssrData = ssr.extractData();
159
160
// Client-side setup with hydration
161
const clientSsr = ssrExchange({
162
initialState: ssrData,
163
isClient: true
164
});
165
166
const clientClient = createClient({
167
url: "https://api.example.com/graphql",
168
exchanges: [cacheExchange, clientSsr, fetchExchange],
169
});
170
```
171
172
### Map Exchange
173
174
Transform operations and results for custom processing, error handling, and middleware-like functionality.
175
176
```typescript { .api }
177
/**
178
* Exchange for transforming operations and results
179
* @param options - Transformation functions
180
* @returns Exchange that applies transformations
181
*/
182
function mapExchange(options: MapExchangeOpts): Exchange;
183
184
// Alias for backward compatibility
185
const errorExchange = mapExchange;
186
187
interface MapExchangeOpts {
188
/** Transform operations before processing */
189
onOperation?: (operation: Operation) => Operation;
190
/** Transform results after processing */
191
onResult?: (result: OperationResult, operation: Operation) => OperationResult;
192
/** Handle errors from operations */
193
onError?: (error: CombinedError, operation: Operation) => void;
194
}
195
```
196
197
**Usage Examples:**
198
199
```typescript
200
import { mapExchange } from "@urql/core";
201
202
// Add authentication headers to operations
203
const authExchange = mapExchange({
204
onOperation: (operation) => ({
205
...operation,
206
context: {
207
...operation.context,
208
fetchOptions: {
209
...operation.context.fetchOptions,
210
headers: {
211
...operation.context.fetchOptions?.headers,
212
authorization: `Bearer ${getAuthToken()}`,
213
},
214
},
215
},
216
}),
217
});
218
219
// Global error handling
220
const errorHandlingExchange = mapExchange({
221
onError: (error, operation) => {
222
if (error.networkError?.message?.includes('401')) {
223
// Handle authentication error
224
redirectToLogin();
225
}
226
console.error(`Operation ${operation.kind} failed:`, error);
227
},
228
});
229
230
const client = createClient({
231
url: "https://api.example.com/graphql",
232
exchanges: [
233
errorHandlingExchange,
234
authExchange,
235
cacheExchange,
236
fetchExchange,
237
],
238
});
239
```
240
241
### Exchange Composition
242
243
Combine multiple exchanges into a single exchange pipeline.
244
245
```typescript { .api }
246
/**
247
* Compose multiple exchanges into a single exchange
248
* @param exchanges - Array of exchanges to compose
249
* @returns Single composed exchange
250
*/
251
function composeExchanges(exchanges: Exchange[]): Exchange;
252
```
253
254
**Usage Examples:**
255
256
```typescript
257
import { composeExchanges, cacheExchange, fetchExchange } from "@urql/core";
258
259
// Create a reusable exchange combination
260
const standardExchanges = composeExchanges([
261
cacheExchange,
262
fetchExchange,
263
]);
264
265
const client = createClient({
266
url: "https://api.example.com/graphql",
267
exchanges: [standardExchanges],
268
});
269
270
// Compose with additional exchanges
271
const advancedExchanges = composeExchanges([
272
debugExchange,
273
cacheExchange,
274
subscriptionExchange({ forwardSubscription }),
275
fetchExchange,
276
]);
277
```
278
279
### Custom Exchange Development
280
281
Create custom exchanges for specialized functionality.
282
283
```typescript { .api }
284
/**
285
* Exchange function signature
286
* @param input - Exchange input with client and forward function
287
* @returns ExchangeIO function that processes operations
288
*/
289
type Exchange = (input: ExchangeInput) => ExchangeIO;
290
291
/**
292
* Exchange I/O function that processes operation streams
293
* @param ops$ - Stream of operations to process
294
* @returns Stream of operation results
295
*/
296
type ExchangeIO = (ops$: Source<Operation>) => Source<OperationResult>;
297
298
interface ExchangeInput {
299
/** Client instance */
300
client: Client;
301
/** Forward function to next exchange */
302
forward: ExchangeIO;
303
/** Debug event dispatcher */
304
dispatchDebug<T extends keyof DebugEventTypes | string>(
305
event: DebugEventArg<T>
306
): void;
307
}
308
```
309
310
**Usage Examples:**
311
312
```typescript
313
import { pipe, filter, onPush, merge, fromValue } from 'wonka';
314
import { Exchange } from "@urql/core";
315
316
// Simple logging exchange
317
const loggingExchange: Exchange = ({ forward }) => {
318
return ops$ => pipe(
319
ops$,
320
onPush(operation => {
321
console.log(`Executing ${operation.kind}:`, operation.query);
322
}),
323
forward,
324
onPush(result => {
325
console.log(`Result for ${result.operation.kind}:`, result);
326
})
327
);
328
};
329
330
// Caching exchange example
331
const memoryCacheExchange: Exchange = ({ forward }) => {
332
const cache = new Map();
333
334
return ops$ => {
335
const sharedOps$ = pipe(ops$, share);
336
337
return pipe(
338
merge([
339
// Check cache for queries
340
pipe(
341
sharedOps$,
342
filter(op => op.kind === 'query'),
343
mergeMap(operation => {
344
const cached = cache.get(operation.key);
345
if (cached) {
346
return fromValue(cached);
347
}
348
return pipe(
349
forward(fromValue(operation)),
350
onPush(result => {
351
if (result.data && !result.error) {
352
cache.set(operation.key, result);
353
}
354
})
355
);
356
})
357
),
358
359
// Forward non-queries normally
360
pipe(
361
sharedOps$,
362
filter(op => op.kind !== 'query'),
363
forward
364
)
365
])
366
);
367
};
368
};
369
```
370
371
## Types
372
373
### Exchange Core Types
374
375
```typescript { .api }
376
type Exchange = (input: ExchangeInput) => ExchangeIO;
377
type ExchangeIO = (ops$: Source<Operation>) => Source<OperationResult>;
378
379
interface ExchangeInput {
380
client: Client;
381
forward: ExchangeIO;
382
dispatchDebug<T extends keyof DebugEventTypes | string>(
383
t: DebugEventArg<T>
384
): void;
385
}
386
```
387
388
### Debug Event Types
389
390
```typescript { .api }
391
interface DebugEventTypes {
392
cacheHit: { value: any };
393
cacheInvalidation: { typenames: string[]; response: OperationResult };
394
fetchRequest: { url: string; fetchOptions: RequestInit };
395
fetchSuccess: { url: string; fetchOptions: RequestInit; value: object };
396
fetchError: { url: string; fetchOptions: RequestInit; value: Error };
397
retryRetrying: { retryCount: number };
398
}
399
400
type DebugEventArg<T extends keyof DebugEventTypes | string> = {
401
type: T;
402
message: string;
403
operation: Operation;
404
} & (T extends keyof DebugEventTypes
405
? { data: DebugEventTypes[T] }
406
: { data?: any });
407
408
type DebugEvent<T extends keyof DebugEventTypes | string = string> =
409
DebugEventArg<T> & {
410
timestamp: number;
411
source: string;
412
};
413
```
414
415
### Wonka Source Types
416
417
```typescript { .api }
418
// Re-exported from wonka for exchange development
419
type Source<T> = import('wonka').Source<T>;
420
type Subscription = import('wonka').Subscription;
421
```