@graphql-yoga/plugin-apq
Version:
APQ plugin for GraphQL Yoga.
71 lines (70 loc) • 2.5 kB
JavaScript
import { createGraphQLError } from 'graphql-yoga';
import { lru } from 'tiny-lru';
export async function hashSHA256(str, api = globalThis) {
const { crypto, TextEncoder } = api;
const textEncoder = new TextEncoder();
const utf8 = textEncoder.encode(str);
const hashBuffer = await crypto.subtle.digest('SHA-256', utf8);
let hashHex = '';
for (const bytes of new Uint8Array(hashBuffer)) {
hashHex += bytes.toString(16).padStart(2, '0');
}
return hashHex;
}
export function createInMemoryAPQStore(options = {}) {
return lru(options.max ?? 1000, options.ttl ?? 36000);
}
function isAPQExtension(input) {
return (input != null &&
typeof input === 'object' &&
'version' in input &&
input?.version === 1 &&
'sha256Hash' in input &&
typeof input?.sha256Hash === 'string');
}
function decodeAPQExtension(input) {
if (isAPQExtension(input)) {
return input;
}
return null;
}
export function useAPQ(options = {}) {
const { store = createInMemoryAPQStore(), hash = hashSHA256 } = options;
return {
async onParams({ params, setParams, fetchAPI }) {
const persistedQueryData = decodeAPQExtension(params.extensions?.persistedQuery);
if (persistedQueryData === null) {
return;
}
if (params.query == null) {
const persistedQuery = await store.get(persistedQueryData.sha256Hash);
if (persistedQuery == null) {
throw createGraphQLError('PersistedQueryNotFound', {
extensions: {
http: {
status: 404,
},
},
});
}
setParams({
...params,
query: persistedQuery,
});
}
else {
const expectedHash = await hash(params.query, fetchAPI);
if (persistedQueryData.sha256Hash !== expectedHash) {
throw createGraphQLError('PersistedQueryMismatch', {
extensions: {
http: {
status: 400,
},
},
});
}
await store.set(persistedQueryData.sha256Hash, params.query);
}
},
};
}