Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

fix(core): Add typename formatting to SSR Exchange (#3288)

Changed files
+24 -9
.changeset
packages
core
src
+5
.changeset/honest-hairs-wave.md
···
···
+
---
+
'@urql/core': patch
+
---
+
+
Fix `ssrExchange` not formatting query documents using `formatDocument`. Without this call we'd run the risk of not having `__typename` available on the client-side when rehydrating.
+12 -7
packages/core/src/exchanges/cache.ts
···
const shouldSkip = ({ kind }: Operation) =>
kind !== 'mutation' && kind !== 'query';
/** Default document cache exchange.
*
* @remarks
···
export const cacheExchange: Exchange = ({ forward, client, dispatchDebug }) => {
const resultCache: ResultCache = new Map();
const operationCache: OperationCache = new Map();
-
-
// Adds unique typenames to query (for invalidating cache entries)
-
const mapTypeNames = (operation: Operation): Operation => {
-
const formattedOperation = makeOperation(operation.kind, operation);
-
formattedOperation.query = formatDocument(operation.query);
-
return formattedOperation;
-
};
const isOperationCached = (operation: Operation) =>
operation.kind === 'query' &&
···
const shouldSkip = ({ kind }: Operation) =>
kind !== 'mutation' && kind !== 'query';
+
/** Adds unique typenames to query (for invalidating cache entries) */
+
export const mapTypeNames = (operation: Operation): Operation => {
+
const query = formatDocument(operation.query);
+
if (query !== operation.query) {
+
const formattedOperation = makeOperation(operation.kind, operation);
+
formattedOperation.query = query;
+
return formattedOperation;
+
} else {
+
return operation;
+
}
+
};
+
/** Default document cache exchange.
*
* @remarks
···
export const cacheExchange: Exchange = ({ forward, client, dispatchDebug }) => {
const resultCache: ResultCache = new Map();
const operationCache: OperationCache = new Map();
const isOperationCached = (operation: Operation) =>
operation.kind === 'query' &&
+5 -1
packages/core/src/exchanges/ssr.test.ts
···
import { Client } from '../client';
import { queryOperation, queryResponse } from '../test-utils';
import { ExchangeIO, Operation, OperationResult } from '../types';
-
import { CombinedError } from '../utils';
import { ssrExchange } from './ssr';
let forward: ExchangeIO;
···
},
},
});
});
it('deletes cached results in non-suspense environments', async () => {
···
import { Client } from '../client';
import { queryOperation, queryResponse } from '../test-utils';
import { ExchangeIO, Operation, OperationResult } from '../types';
+
import { CombinedError, formatDocument } from '../utils';
import { ssrExchange } from './ssr';
let forward: ExchangeIO;
···
},
},
});
+
+
expect(output.mock.calls[0][0].query).toBe(
+
formatDocument(queryOperation.query)
+
);
});
it('deletes cached results in non-suspense environments', async () => {
+2 -1
packages/core/src/exchanges/ssr.ts
···
import { pipe, filter, merge, map, tap } from 'wonka';
import { Exchange, OperationResult, Operation } from '../types';
import { addMetadata, CombinedError } from '../utils';
-
import { reexecuteOperation } from './cache';
/** A serialized version of an {@link OperationResult}.
*
···
!!data[operation.key]!.hasNext ||
operation.context.requestPolicy === 'network-only'
),
forward
);
···
import { pipe, filter, merge, map, tap } from 'wonka';
import { Exchange, OperationResult, Operation } from '../types';
import { addMetadata, CombinedError } from '../utils';
+
import { reexecuteOperation, mapTypeNames } from './cache';
/** A serialized version of an {@link OperationResult}.
*
···
!!data[operation.key]!.hasNext ||
operation.context.requestPolicy === 'network-only'
),
+
map(mapTypeNames),
forward
);