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

(persisted-fetch) - Fix hash not being sent after Not Found error (#766)

* Fix persisted query miss not sending SHA256 hash

* Add changeset

Changed files
+52 -73
.changeset
exchanges
+5
.changeset/little-years-cough.md
···
···
+
---
+
'@urql/exchange-persisted-fetch': patch
+
---
+
+
Fix `persistedFetchExchange` not sending the SHA256 hash extension after a cache miss (`PersistedQueryNotFound` error)
+1 -1
exchanges/persisted-fetch/src/__snapshots__/persistedFetchExchange.test.ts.snap
···
exports[`supports cache-miss persisted query errors 1`] = `"{\\"operationName\\":\\"getUser\\",\\"variables\\":{\\"name\\":\\"Clara\\"},\\"extensions\\":{\\"persistedQuery\\":{\\"version\\":1,\\"sha256Hash\\":\\"bfa84414672fe625d36f2d2a52e1d3c1e71c5a01e79599c320db7656d6f014d4\\"}}}"`;
-
exports[`supports cache-miss persisted query errors 2`] = `"{\\"query\\":\\"query getUser($name: String) {\\\\n user(name: $name) {\\\\n id\\\\n firstName\\\\n lastName\\\\n }\\\\n}\\\\n\\",\\"operationName\\":\\"getUser\\",\\"variables\\":{\\"name\\":\\"Clara\\"}}"`;
exports[`supports unsupported persisted query errors 1`] = `"{\\"operationName\\":\\"getUser\\",\\"variables\\":{\\"name\\":\\"Clara\\"},\\"extensions\\":{\\"persistedQuery\\":{\\"version\\":1,\\"sha256Hash\\":\\"bfa84414672fe625d36f2d2a52e1d3c1e71c5a01e79599c320db7656d6f014d4\\"}}}"`;
···
exports[`supports cache-miss persisted query errors 1`] = `"{\\"operationName\\":\\"getUser\\",\\"variables\\":{\\"name\\":\\"Clara\\"},\\"extensions\\":{\\"persistedQuery\\":{\\"version\\":1,\\"sha256Hash\\":\\"bfa84414672fe625d36f2d2a52e1d3c1e71c5a01e79599c320db7656d6f014d4\\"}}}"`;
+
exports[`supports cache-miss persisted query errors 2`] = `"{\\"query\\":\\"query getUser($name: String) {\\\\n user(name: $name) {\\\\n id\\\\n firstName\\\\n lastName\\\\n }\\\\n}\\\\n\\",\\"operationName\\":\\"getUser\\",\\"variables\\":{\\"name\\":\\"Clara\\"},\\"extensions\\":{\\"persistedQuery\\":{\\"version\\":1,\\"sha256Hash\\":\\"bfa84414672fe625d36f2d2a52e1d3c1e71c5a01e79599c320db7656d6f014d4\\"}}}"`;
exports[`supports unsupported persisted query errors 1`] = `"{\\"operationName\\":\\"getUser\\",\\"variables\\":{\\"name\\":\\"Clara\\"},\\"extensions\\":{\\"persistedQuery\\":{\\"version\\":1,\\"sha256Hash\\":\\"bfa84414672fe625d36f2d2a52e1d3c1e71c5a01e79599c320db7656d6f014d4\\"}}}"`;
+46 -72
exchanges/persisted-fetch/src/persistedFetchExchange.ts
···
} from '@urql/core';
import {
makeFetchBody,
makeFetchURL,
makeFetchOptions,
···
filter(op => op.operationName === 'teardown' && op.key === key)
);
if (!supportsPersistedQueries) {
return pipe(
-
makeNormalFetchSource(operation, dispatchDebug),
takeUntil(teardown$)
);
}
return pipe(
-
makePersistedFetchSource(operation, dispatchDebug),
mergeMap(result => {
if (result.error && isPersistedUnsupported(result.error)) {
supportsPersistedQueries = false;
-
return makeNormalFetchSource(operation, dispatchDebug);
} else if (result.error && isPersistedMiss(result.error)) {
-
return makeNormalFetchSource(operation, dispatchDebug);
}
return fromValue(result);
···
const makePersistedFetchSource = (
operation: Operation,
dispatchDebug: ExchangeInput['dispatchDebug']
): Source<OperationResult> => {
-
const body = makeFetchBody(operation);
-
const query: string = body.query!;
-
-
return pipe(
-
fromPromise(hash(query)),
-
mergeMap(sha256Hash => {
-
body.query = undefined;
-
body.extensions = {
-
persistedQuery: {
-
version: 1,
-
sha256Hash,
-
},
-
};
-
-
const url = makeFetchURL(operation, { ...body, query: '' });
-
const fetchOptions = makeFetchOptions(operation, body);
-
-
dispatchDebug({
-
type: 'fetchRequest',
-
message: 'A fetch request for a persisted query is being executed.',
-
operation,
-
data: {
-
url,
-
fetchOptions,
-
},
-
});
-
-
return pipe(
-
makeFetchSource(operation, url, fetchOptions),
-
onPush(result => {
-
const persistFail =
-
result.error &&
-
(isPersistedMiss(result.error) ||
-
isPersistedUnsupported(result.error));
-
const error = !result.data ? result.error : undefined;
-
-
dispatchDebug({
-
// TODO: Assign a new name to this once @urql/devtools supports it
-
type: persistFail || error ? 'fetchError' : 'fetchSuccess',
-
message: persistFail
-
? 'A Persisted Query request has failed. A non-persisted GraphQL request will follow.'
-
: `A ${
-
error ? 'failed' : 'successful'
-
} fetch response has been returned.`,
-
operation,
-
data: {
-
url,
-
fetchOptions,
-
value: persistFail ? result.error! : error || result,
-
},
-
});
-
})
-
);
-
})
);
-
};
-
-
const makeNormalFetchSource = (
-
operation: Operation,
-
dispatchDebug: ExchangeInput['dispatchDebug']
-
): Source<OperationResult> => {
-
const body = makeFetchBody(operation);
-
const url = makeFetchURL(operation, body);
const fetchOptions = makeFetchOptions(operation, body);
dispatchDebug({
type: 'fetchRequest',
-
message: 'A fetch request is being executed.',
operation,
data: {
url,
···
return pipe(
makeFetchSource(operation, url, fetchOptions),
onPush(result => {
const error = !result.data ? result.error : undefined;
dispatchDebug({
-
type: error ? 'fetchError' : 'fetchSuccess',
-
message: `A ${
-
error ? 'failed' : 'successful'
-
} fetch response has been returned.`,
operation,
data: {
url,
fetchOptions,
-
value: error || result,
},
});
})
···
} from '@urql/core';
import {
+
FetchBody,
makeFetchBody,
makeFetchURL,
makeFetchOptions,
···
filter(op => op.operationName === 'teardown' && op.key === key)
);
+
const body = makeFetchBody(operation);
if (!supportsPersistedQueries) {
+
// Runs the usual non-persisted fetchExchange query logic
return pipe(
+
makePersistedFetchSource(operation, body, dispatchDebug),
takeUntil(teardown$)
);
}
+
const query: string = body.query!;
+
return pipe(
+
// Hash the given GraphQL query
+
fromPromise(hash(query)),
+
mergeMap(sha256Hash => {
+
// Attach SHA256 hash and remove query from body
+
body.query = undefined;
+
body.extensions = {
+
persistedQuery: {
+
version: 1,
+
sha256Hash,
+
},
+
};
+
+
return makePersistedFetchSource(operation, body, dispatchDebug);
+
}),
mergeMap(result => {
if (result.error && isPersistedUnsupported(result.error)) {
+
// Reset the body back to its non-persisted state
+
body.query = query;
+
body.extensions = undefined;
+
// Disable future persisted queries
supportsPersistedQueries = false;
+
return makePersistedFetchSource(operation, body, dispatchDebug);
} else if (result.error && isPersistedMiss(result.error)) {
+
// Add query to the body but leave SHA256 hash intact
+
body.query = query;
+
return makePersistedFetchSource(operation, body, dispatchDebug);
}
return fromValue(result);
···
const makePersistedFetchSource = (
operation: Operation,
+
body: FetchBody,
dispatchDebug: ExchangeInput['dispatchDebug']
): Source<OperationResult> => {
+
const url = makeFetchURL(
+
operation,
+
body.query ? body : { ...body, query: '' }
);
const fetchOptions = makeFetchOptions(operation, body);
dispatchDebug({
type: 'fetchRequest',
+
message: !body.query
+
? 'A fetch request for a persisted query is being executed.'
+
: 'A fetch request is being executed.',
operation,
data: {
url,
···
return pipe(
makeFetchSource(operation, url, fetchOptions),
onPush(result => {
+
const persistFail =
+
result.error &&
+
(isPersistedMiss(result.error) || isPersistedUnsupported(result.error));
const error = !result.data ? result.error : undefined;
dispatchDebug({
+
// TODO: Assign a new name to this once @urql/devtools supports it
+
type: persistFail || error ? 'fetchError' : 'fetchSuccess',
+
message: persistFail
+
? 'A Persisted Query request has failed. A non-persisted GraphQL request will follow.'
+
: `A ${
+
error ? 'failed' : 'successful'
+
} fetch response has been returned.`,
operation,
data: {
url,
fetchOptions,
+
value: persistFail ? result.error! : error || result,
},
});
})