From c6ad59419db3dadbf426e1080b9bc083c0236203 Mon Sep 17 00:00:00 2001 From: Jack Ellis Date: Wed, 17 Jul 2024 22:10:40 +0100 Subject: [PATCH] feat: query now accepts array of urls --- packages/query/src/query.ts | 138 +++++++++++++++++-------------- packages/query/src/queryGraph.ts | 46 +---------- packages/utils/src/web2/query.ts | 2 +- 3 files changed, 82 insertions(+), 104 deletions(-) diff --git a/packages/query/src/query.ts b/packages/query/src/query.ts index 02b613d..ba0b0bc 100644 --- a/packages/query/src/query.ts +++ b/packages/query/src/query.ts @@ -10,7 +10,7 @@ class QueryError extends Error { } type Args = Omit & { - url: string; + url: string | string[]; data?: Record | string; headers?: Record; method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | string; @@ -23,7 +23,7 @@ type Args = Omit & { const query = async (args: Args): Promise => { const { - url, + url: baseUrl, data: sourceData, headers = {}, method = 'GET', @@ -40,82 +40,98 @@ const query = async (args: Args): Promise => { 'Could not find fetch api. You may need to import a polyfill' ); } - if (!url) { + const urls = Array.isArray(baseUrl) ? baseUrl : [baseUrl]; + + if (!urls.some(Boolean)) { throw new Error('No URL provided'); } - const uri = new URL(url); - - if (method === 'GET') { - if (typeof sourceData === 'string' && sourceData) { - if (sourceData.startsWith('?')) { - uri.search = sourceData; - } else { - uri.search = '?' + sourceData; + while (urls.length) { + try { + const url = urls.shift(); + if (!url) { + continue; } - } else if (sourceData) { - Object.entries(sourceData).forEach(([key, value]) => { - if (value == null) { - return; - } - if (Array.isArray(value)) { - value.forEach((v) => { - if (v == null) { + const uri = new URL(url); + + if (method === 'GET') { + if (typeof sourceData === 'string' && sourceData) { + if (sourceData.startsWith('?')) { + uri.search = sourceData; + } else { + uri.search = '?' + sourceData; + } + } else if (sourceData) { + Object.entries(sourceData).forEach(([key, value]) => { + if (value == null) { return; } - uri.searchParams.append(key, v); + if (Array.isArray(value)) { + value.forEach((v) => { + if (v == null) { + return; + } + uri.searchParams.append(key, v); + }); + } else { + uri.searchParams.set(key, value); + } }); - } else { - uri.searchParams.set(key, value); } - }); - } - } + } - const body = - method === 'GET' - ? undefined - : typeof sourceData === 'string' - ? sourceData - : stringify(sourceData); + const body = + method === 'GET' + ? undefined + : typeof sourceData === 'string' + ? sourceData + : stringify(sourceData); - if (method !== 'GET' && typeof sourceData === 'object') { - headers['Content-Type'] = 'application/json'; - } + if (method !== 'GET' && typeof sourceData === 'object') { + headers['Content-Type'] = 'application/json'; + } - const response = await fetch(uri.toString(), { - method, - body, - headers, - ...requestInit, - }); + const response = await fetch(uri.toString(), { + method, + body, + headers, + ...requestInit, + }); - if ( - response.status >= 500 && - response.status <= 599 && - attempt < maxAttempts - ) { - await new Promise((res) => setTimeout(res, 1000)); - return query({ ...args, attempt: attempt + 1 }); - } - const contentType = response.headers.get('content-type'); + if ( + response.status >= 500 && + response.status <= 599 && + attempt < maxAttempts + ) { + await new Promise((res) => setTimeout(res, 1000)); + return query({ ...args, attempt: attempt + 1 }); + } + const contentType = response.headers.get('content-type'); + + if (!response.ok) { + if (contentType?.includes('application/json')) { + const json = await response.json(); + throw new QueryError(response, json, `Error fetching ${url}`); + } else { + const text = await response.text(); + throw new QueryError(response, {}, text); + } + } - if (!response.ok) { - if (contentType?.includes('application/json')) { - const json = await response.json(); - throw new QueryError(response, json, `Error fetching ${url}`); - } else { const text = await response.text(); - throw new QueryError(response, {}, text); + if (contentType?.includes('application/json')) { + return parse(text); + } else { + return text as T; + } + } catch (e) { + if (!urls.length) { + throw e; + } } } - const text = await response.text(); - if (contentType?.includes('application/json')) { - return parse(text); - } else { - return text as T; - } + throw new Error(); }; export default query; diff --git a/packages/query/src/queryGraph.ts b/packages/query/src/queryGraph.ts index 6f8b2a5..5ab5382 100644 --- a/packages/query/src/queryGraph.ts +++ b/packages/query/src/queryGraph.ts @@ -5,7 +5,7 @@ import { normalizeIfAddress } from './utils'; type Query = typeof sendQuery; const defaultSendQuery = sendQuery; -type SendQueryArgs = Omit[0], 'url' | 'data'>; +type SendQueryArgs = Omit[0], 'data'>; const interpolateQuery = (query: string, variables: Record) => { return Object.entries(variables).reduce((query, [key, value]) => { @@ -51,7 +51,7 @@ const sendGraphQuery = async ({ sendQuery, headers = {}, ...rest -}: SendQueryArgs & { url: string; query: string; sendQuery: Query }) => { +}: SendQueryArgs & { query: string; sendQuery: Query }) => { const { data, errors } = await sendQuery<{ errors: { message: string }[] & { message: string }; data: any; @@ -69,44 +69,6 @@ const sendGraphQuery = async ({ return data; }; -const queryUrls = async ({ - baseUrl, - query, - ...rest -}: Omit & { - baseUrl: string | string[]; - query: string; - sendQuery: Query; -}) => { - // We can be passed a single url or an array of urls - // If we have an array, we'll try them in order until we get a successful response - const urls = [baseUrl].flat(); - - while (urls.length) { - try { - const url = urls.shift(); - // Ignore empty urls (baseUrl could be undefined, or an array could've been built with missing content) - if (url == null) { - continue; - } - - const data = await sendGraphQuery({ - query, - url, - ...rest, - }); - - return data; - } catch (e) { - // If there's been an error, we'll try the next url - // if we've exhausted all urls, throw the most recent error - if (!urls.length) { - throw e; - } - } - } -}; - async function queryGraph>( args: SendQueryArgs & { url: string | string[]; @@ -140,8 +102,8 @@ async function queryGraph({ ); } - return queryUrls({ - baseUrl: url, + return sendGraphQuery({ + url, query: formatQuery({ query, variables }), fetch, sendQuery, diff --git a/packages/utils/src/web2/query.ts b/packages/utils/src/web2/query.ts index 50f5db2..5332006 100644 --- a/packages/utils/src/web2/query.ts +++ b/packages/utils/src/web2/query.ts @@ -5,7 +5,7 @@ import { query as sendQuery } from '@nftx/query'; type Fetch = typeof fetch; type Args = { - url: string; + url: string | string[]; query?: Record; headers?: Record; cache?: RequestCache;