import fetchRetry from "fetch-retry" import { GraphQLClient } from "graphql-request" import stringify from "json-stable-stringify-without-jsonify" import { cache as reactCache } from "react" import { type CacheTime, getCacheClient, } from "@scandic-hotels/common/dataCache" import { env } from "../../env/server" import { getPreviewHash, isPreviewByUid } from "../previewContext" import { request as _request } from "./_request" import { getOperationName } from "./getOperationName" import type { DocumentNode } from "graphql" import type { Data } from "../types/requestData" export async function request( query: string | DocumentNode, variables?: Record, cacheOptions?: { key: string | string[] ttl: CacheTime } ): Promise> { const shouldUsePreview = variables?.uid ? isPreviewByUid(variables.uid) : false const doCall = () => internalRequest(query, shouldUsePreview, variables, getPreviewHash()) if (!cacheOptions) { console.warn("[NO CACHE] for query", query) return doCall() } if (shouldUsePreview) { console.log("[NO CACHE] [PREVIEW] for query", query) return doCall() } const queryString = typeof query === "string" ? query : stringify(query) const variablesString = stringify(variables) const fullQuery = `${queryString}${variablesString}` const queryHash = await sha256(fullQuery) const operationName = getOperationName(query) const cacheKey: string = Array.isArray(cacheOptions.key) ? cacheOptions.key.join("_") : cacheOptions.key const extendedCacheKey = `${operationName}:${queryHash}:${cacheKey}` const _dataCache = await getCacheClient() return _dataCache.cacheOrGet(extendedCacheKey, doCall, cacheOptions.ttl, { includeGitHashInKey: false, }) } function internalRequest( query: string | DocumentNode, shouldUsePreview: boolean, variables?: Record, previewHash?: string ): Promise> { const cmsUrl = shouldUsePreview ? env.CMS_PREVIEW_URL : env.CMS_URL // Creating a new client for each request to avoid conflicting parameters const client = new GraphQLClient(cmsUrl, { fetch: reactCache(async function ( url: URL | RequestInfo, params?: RequestInit ) { const wrappedFetch = fetchRetry(fetch, { retries: 2, retryDelay: function (attempt) { return Math.pow(2, attempt) * 150 // 150, 300, 600 }, }) return wrappedFetch(url, { ...params, signal: AbortSignal.timeout(15_000), }) }), }) const mergedParams = shouldUsePreview && previewHash ? { headers: { live_preview: previewHash, preview_token: env.CMS_PREVIEW_TOKEN, }, } : {} return _request(client, query, variables, mergedParams) } async function sha256(input: string) { const encoder = new TextEncoder() const data = encoder.encode(input) const hashBuffer = await crypto.subtle.digest("SHA-256", data) const hashArray = Array.from(new Uint8Array(hashBuffer)) const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("") return hashHex }