// Copyright (c) Mysten Labs, Inc. // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import type { TadaDocumentNode } from 'gql.tada'; import type { DocumentNode } from 'graphql'; import { print } from 'graphql'; export type GraphQLDocument< Result = Record, Variables = Record, > = | string | DocumentNode | TypedDocumentNode | TadaDocumentNode; export type GraphQLQueryOptions< Result = Record, Variables = Record, > = { query: GraphQLDocument; operationName?: string; extensions?: Record; } & (Variables extends { [key: string]: never } ? { variables?: Variables } : { variables: Variables; }); export type GraphQLQueryResult> = { data?: Result; errors?: GraphQLResponseErrors; extensions?: Record; }; export type GraphQLResponseErrors = Array<{ message: string; locations?: { line: number; column: number }[]; path?: (string | number)[]; }>; export interface IotaGraphQLClientOptions> { url: string; fetch?: typeof fetch; headers?: Record; queries?: Queries; } export class IotaGraphQLRequestError extends Error {} // eslint-disable-next-line @typescript-eslint/ban-types export class IotaGraphQLClient = {}> { #url: string; #queries: Queries; #headers: Record; #fetch: typeof fetch; constructor({ url, fetch: fetchFn = fetch, headers = {}, queries = {} as Queries, }: IotaGraphQLClientOptions) { this.#url = url; this.#queries = queries; this.#headers = headers; this.#fetch = (...args) => fetchFn(...args); } async query, Variables = Record>( options: GraphQLQueryOptions, ): Promise> { const res = await this.#fetch(this.#url, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.#headers, }, body: JSON.stringify({ query: typeof options.query === 'string' ? String(options.query) : print(options.query), variables: options.variables, extensions: options.extensions, operationName: options.operationName, }), }); if (!res.ok) { throw new IotaGraphQLRequestError( `GraphQL request failed: ${res.statusText} (${res.status})`, ); } return await res.json(); } async execute< const Query extends Extract, Result = Queries[Query] extends GraphQLDocument ? R : Record, Variables = Queries[Query] extends GraphQLDocument ? V : Record, >( query: Query, options: Omit, 'query'>, ): Promise> { return this.query({ ...(options as { variables: Record }), query: this.#queries[query]!, }) as Promise>; } }