/*** UTILITY ------------------------------------------ ***/ import { buildHeaders, postJson } from "./http-body.ts"; import type { Fetcher, FetcherOptions } from "./types.ts"; /*** EXPORT ------------------------------------------- ***/ export type ApqOptions = FetcherOptions & { disable?: boolean; }; export function createApqFetcher(options: ApqOptions): Fetcher { const cache = new Map(); const fetchImpl = options.fetch ?? globalThis.fetch; return async (req) => { const headers = buildHeaders(options, req); const sha256Hash = await getHash(cache, req.query); const extensions = { persistedQuery: { sha256Hash, version: 1 } }; if (options.disable === true) { return await postJson(fetchImpl, options.url, headers, { extensions, operationName: req.operationName, query: req.query, variables: req.variables }); } const firstResponse = await postJson(fetchImpl, options.url, headers, { extensions, operationName: req.operationName, variables: req.variables }); if (!isPersistedQueryNotFound(firstResponse)) return firstResponse; return await postJson(fetchImpl, options.url, headers, { extensions, operationName: req.operationName, query: req.query, variables: req.variables }); }; } /*** INTERNAL ----------------------------------------- ***/ async function getHash(cache: Map, query: string): Promise { const cached = cache.get(query); if (cached !== undefined) return cached; const hash = await sha256Hex(query); cache.set(query, hash); return hash; } function isPersistedQueryNotFound(response: Record): boolean { const errors = response.errors; if (!Array.isArray(errors)) return false; for (const entry of errors) { if (entry === null || typeof entry !== "object") continue; const err = entry as Record; if (err.message === "PersistedQueryNotFound") return true; const ext = err.extensions; if (ext !== null && typeof ext === "object") { const code = (ext as Record).code; if (code === "PERSISTED_QUERY_NOT_FOUND") return true; } } return false; } async function sha256Hex(input: string): Promise { const buf = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(input)); return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join(""); }