UNPKG

@graphql-hive/core

Version:
98 lines (97 loc) 3.67 kB
import LRU from 'tiny-lru'; import CircuitBreaker from '../circuit-breaker/circuit.js'; import { defaultCircuitBreakerConfiguration } from './circuit-breaker.js'; import { http } from './http-client.js'; function isRequestOk(response) { return response.status === 200 || response.status === 404; } export function createPersistedDocuments(config) { var _a; const persistedDocumentsCache = LRU((_a = config.cache) !== null && _a !== void 0 ? _a : 10000); let allowArbitraryDocuments; if (typeof config.allowArbitraryDocuments === 'boolean') { let value = config.allowArbitraryDocuments; allowArbitraryDocuments = () => value; } else if (typeof config.allowArbitraryDocuments === 'function') { allowArbitraryDocuments = config.allowArbitraryDocuments; } else { allowArbitraryDocuments = () => false; } /** if there is already a in-flight request for a document, we re-use it. */ const fetchCache = new Map(); const endpoints = Array.isArray(config.cdn.endpoint) ? config.cdn.endpoint : [config.cdn.endpoint]; const circuitBreakers = endpoints.map(endpoint => { var _a; const circuitBreaker = new CircuitBreaker(async function doFetch(cdnDocumentId) { const signal = circuitBreaker.getSignal(); return await http .get(endpoint + '/apps/' + cdnDocumentId, { headers: { 'X-Hive-CDN-Key': config.cdn.accessToken, }, logger: config.logger, isRequestOk, fetchImplementation: config.fetch, signal, retry: config.retry, }) .then(async (response) => { if (response.status !== 200) { return null; } const text = await response.text(); return text; }); }, Object.assign(Object.assign({}, ((_a = config.circuitBreaker) !== null && _a !== void 0 ? _a : defaultCircuitBreakerConfiguration)), { timeout: false, autoRenewAbortController: true })); return circuitBreaker; }); /** Batch load a persisted documents */ function loadPersistedDocument(documentId) { const document = persistedDocumentsCache.get(documentId); if (document) { return document; } let promise = fetchCache.get(documentId); if (promise) { return promise; } promise = Promise.resolve() .then(async () => { const cdnDocumentId = documentId.replaceAll('~', '/'); let lastError = null; for (const breaker of circuitBreakers) { try { return await breaker.fire(cdnDocumentId); } catch (error) { config.logger.debug({ error }); lastError = error; } } if (lastError) { config.logger.error({ error: lastError }); } throw new Error('Failed to look up persisted operation.'); }) .then(result => { persistedDocumentsCache.set(documentId, result); return result; }) .finally(() => { fetchCache.delete(documentId); }); fetchCache.set(documentId, promise); return promise; } return { allowArbitraryDocuments, resolve: loadPersistedDocument, dispose() { circuitBreakers.map(breaker => breaker.shutdown()); }, }; }