Exchanges 
Customize how GraphQL operations are executed with composable middleware.
What are Exchanges? 
Exchanges are composable middleware that process your GraphQL operations. Each exchange can modify requests, handle responses, or add features like authentication, caching, and logging.
Acknowledgment
Mearie's exchange architecture is based on urql's exchange system, which provides a proven pattern for composable GraphQL middleware.
Basic Usage 
import { createClient, httpExchange, cacheExchange } from '@mearie/react'; // or @mearie/vue, @mearie/svelte, @mearie/solid
import { schema } from '$mearie';
export const client = createClient({
  schema,
  exchanges: [cacheExchange(), httpExchange({ url: 'https://api.example.com/graphql' })],
});How Exchanges Work 
Exchanges use a stream-based architecture. Operations flow as a stream through each exchange, and results flow back:
Operations Stream
  ↓
[dedupExchange]   ← Filters duplicate operations
  ↓
[cacheExchange]   ← Returns cached data or forwards
  ↓
[httpExchange]    ← Executes network requests
  ↓
Results StreamEach exchange receives a stream of operations, transforms it, and produces a stream of results. This enables:
- Reactive - Operations automatically flow through the pipeline
- Composable - Exchanges operate independently and compose cleanly
- Cancellable - Unsubscribing cancels in-flight operations
Learn More
For a conceptual understanding of streams, see Streams Concept. For the complete API, see Streams Reference.
Terminating vs Non-terminating Exchanges 
Exchanges fall into two categories based on their role in the stream pipeline:
Non-terminating exchanges transform the operation stream and forward it:
- cacheExchange- May emit cached results or forward operations
- retryExchange- Retries operations by replaying them in the stream
- dedupExchange- Filters duplicate operations from the stream
Terminating exchanges handle operations and don't forward them further:
- httpExchange- Executes queries/mutations over HTTP
- subscriptionExchange- Handles subscriptions via SSE or WebSocket
WARNING
You must include at least one terminating exchange (usually httpExchange) at the end of your chain. Without it, your operations won't execute.
Execution Order 
The order you define exchanges determines the execution flow:
export const client = createClient({
  schema,
  exchanges: [
    dedupExchange(), // 1. Deduplication (outermost)
    retryExchange(), // 2. Retry logic
    cacheExchange(), // 3. Cache layer
    httpExchange(), // 4. Network request (innermost)
  ],
});This means:
- Deduplication filters duplicate operations first
- Retries will trigger the cache + HTTP flow (downstream only)
- Cache can return early without hitting HTTP
- HTTP terminates the chain by making the actual request
Built-in Exchanges 
HTTP Exchange 
Sends GraphQL requests over HTTP (terminating exchange):
import { schema } from '$mearie';
export const client = createClient({
  schema,
  exchanges: [httpExchange({ url: 'https://api.example.com/graphql' })],
});Learn more about HTTP Exchange →
Cache Exchange 
Normalized caching with automatic updates:
import { schema } from '$mearie';
export const client = createClient({
  schema,
  exchanges: [cacheExchange(), httpExchange({ url: 'https://api.example.com/graphql' })],
});Learn more about Cache Exchange →
Retry Exchange 
Automatic retry with exponential backoff:
import { schema } from '$mearie';
export const client = createClient({
  schema,
  exchanges: [retryExchange({ maxAttempts: 3 }), httpExchange({ url: 'https://api.example.com/graphql' })],
});Learn more about Retry Exchange →
Deduplication Exchange 
Deduplicates identical concurrent requests:
import { schema } from '$mearie';
export const client = createClient({
  schema,
  exchanges: [dedupExchange(), cacheExchange(), httpExchange({ url: 'https://api.example.com/graphql' })],
});Learn more about Deduplication Exchange →
Subscription Exchange 
Handles real-time subscriptions via Server-Sent Events or WebSocket (terminating exchange):
import { createClient as createSSEClient } from 'graphql-sse';
import { schema } from '$mearie';
export const client = createClient({
  schema,
  exchanges: [
    httpExchange({ url: 'https://api.example.com/graphql' }),
    subscriptionExchange({
      client: createSSEClient({
        url: 'https://api.example.com/graphql',
      }),
    }),
  ],
});Learn more about Subscription Exchange →
Common Patterns 
Full-featured Client 
import { createClient as createSSEClient } from 'graphql-sse';
import { schema } from '$mearie';
export const client = createClient({
  schema,
  exchanges: [
    dedupExchange(),
    retryExchange({ maxAttempts: 3 }),
    cacheExchange(),
    httpExchange({ url: 'https://api.example.com/graphql' }),
    subscriptionExchange({
      client: createSSEClient({
        url: 'https://api.example.com/graphql',
      }),
    }),
  ],
});Retry on Network Errors 
import { schema } from '$mearie';
export const client = createClient({
  schema,
  exchanges: [
    retryExchange({
      maxAttempts: 3,
      retryIf: (error) => error.networkError,
    }),
    httpExchange({ url: 'https://api.example.com/graphql' }),
  ],
});Creating Custom Exchanges 
Exchanges transform operation streams. Here's a simple logging exchange:
import { pipe, tap } from '@mearie/core/stream';
import { type Exchange } from '@mearie/react';
import { schema } from '$mearie';
const loggingExchange = (): Exchange => {
  return ({ forward }) => {
    return (ops$) => {
      return pipe(
        ops$,
        tap((op) => console.log('Operation:', op)),
        forward,
        tap((result) => console.log('Result:', result)),
      );
    };
  };
};
export const client = createClient({
  schema,
  exchanges: [loggingExchange(), httpExchange({ url: 'https://api.example.com/graphql' })],
});The exchange:
- Receives operations stream ops$
- Logs each operation with tap
- Forwards to next exchange with forward
- Logs each result with tap
- Returns the transformed results stream
Learn more about Custom Exchanges →
Exchange Interface 
type Exchange = (input: ExchangeInput) => ExchangeIO;
type ExchangeInput = {
  forward: ExchangeIO;
  client: Client;
};
type ExchangeIO = (operations: Source<Operation>) => Source<OperationResult>;An exchange:
- Takes forward(next exchange) andclientas input
- Returns a function that transforms operation streams to result streams
- Uses stream operators to transform operations and results
Best Practices 
- Order exchanges from outermost (retry, dedup) to innermost (HTTP)
- Always end the chain with a terminating exchange (e.g., httpExchange)
- Keep custom exchanges simple and focused on a single concern
Next Steps 
- Streams Concept - Understand the stream architecture
- Custom Exchanges - Build your own exchanges
- HTTP Exchange - Execute GraphQL over HTTP
- Cache Exchange - Add normalized caching
- Streams Reference - Complete stream API documentation