Skip to content

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 ​

typescript
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 Stream

Each 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:

Terminating exchanges handle operations and don't forward them further:

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:

typescript
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):

typescript
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:

typescript
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:

typescript
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:

typescript
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):

typescript
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 ​

typescript
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 ​

typescript
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:

typescript
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:

  1. Receives operations stream ops$
  2. Logs each operation with tap
  3. Forwards to next exchange with forward
  4. Logs each result with tap
  5. Returns the transformed results stream

Learn more about Custom Exchanges →

Exchange Interface ​

typescript
type Exchange = (input: ExchangeInput) => ExchangeIO;

type ExchangeInput = {
  forward: ExchangeIO;
  client: Client;
};

type ExchangeIO = (operations: Source<Operation>) => Source<OperationResult>;

An exchange:

  • Takes forward (next exchange) and client as 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 ​