@@ -9,21 +9,39 @@ import type { Logger } from './types.js';
99import { createHash , createHiveLogger } from './utils.js' ;
1010
1111type CreateCDNArtifactFetcherArgs = {
12- endpoint : string ;
12+ /**
13+ * The endpoint that should be fetched.
14+ *
15+ * It is possible to provide an endpoint list. The first endpoint will be treated as the primary source.
16+ * The secondary endpoint will be used in case the first endpoint fails to respond.
17+ *
18+ * Example:
19+ *
20+ * ```
21+ * [
22+ * "https://cdn.graphql-hive.com/artifacts/v1/9fb37bc4-e520-4019-843a-0c8698c25688/supergraph",
23+ * "https://cdn-mirror.graphql-hive.com/artifacts/v1/9fb37bc4-e520-4019-843a-0c8698c25688/supergraph"
24+ * ]
25+ * ```
26+ */
27+ endpoint : string | [ string , string ] ;
28+ /**
29+ * The access key that is used for authenticating on the endpoints (via the `X-Hive-CDN-Key` header).
30+ */
1331 accessKey : string ;
14- /** client meta data */
32+ logger ?: Logger ;
33+ circuitBreaker ?: CircuitBreakerConfiguration ;
34+ /**
35+ * Custom fetch implementation used for calling the endpoint.
36+ */
37+ fetch ?: typeof fetch ;
38+ /**
39+ * Optional client meta configuration.
40+ **/
1541 client ?: {
1642 name : string ;
1743 version : string ;
1844 } ;
19- circuitBreaker ?: CircuitBreakerConfiguration ;
20- logger ?: Logger ;
21- fetch ?: typeof fetch ;
22- } ;
23-
24- type CDNFetcherArgs = {
25- logger ?: Logger ;
26- fetch ?: typeof fetch ;
2745} ;
2846
2947type CDNFetchResult = {
@@ -48,47 +66,67 @@ export function createCDNArtifactFetcher(args: CreateCDNArtifactFetcherArgs) {
4866 let cached : CDNFetchResult | null = null ;
4967 const clientInfo = args . client ?? { name : 'hive-client' , version } ;
5068 const circuitBreakerConfig = args . circuitBreaker ?? defaultCircuitBreakerConfiguration ;
69+ const logger = createHiveLogger ( args . logger ?? console , '' ) ;
5170
52- const circuitBreaker = new CircuitBreaker (
53- function runFetch ( fetchArgs ?: CDNFetcherArgs ) {
54- const signal = circuitBreaker . getSignal ( ) ;
55- const logger = createHiveLogger ( fetchArgs ?. logger ?? args . logger ?? console , '' ) ;
56- const fetchImplementation = fetchArgs ?. fetch ?? args . fetch ;
57-
58- const headers : {
59- [ key : string ] : string ;
60- } = {
61- 'X-Hive-CDN-Key' : args . accessKey ,
62- 'User-Agent' : `${ clientInfo . name } /${ clientInfo . version } ` ,
63- } ;
71+ const endpoints = Array . isArray ( args . endpoint ) ? args . endpoint : [ args . endpoint ] ;
72+
73+ // TODO: we should probably do some endpoint validation
74+ // And print some errors if the enpoint paths do not match?
75+ // e.g. the only difference should be the domain name
76+
77+ const circuitBreakers = endpoints . map ( endpoint => {
78+ const circuitBreaker = new CircuitBreaker (
79+ function runFetch ( ) {
80+ const signal = circuitBreaker . getSignal ( ) ;
81+
82+ const headers : {
83+ [ key : string ] : string ;
84+ } = {
85+ 'X-Hive-CDN-Key' : args . accessKey ,
86+ 'User-Agent' : `${ clientInfo . name } /${ clientInfo . version } ` ,
87+ } ;
88+
89+ if ( cacheETag ) {
90+ headers [ 'If-None-Match' ] = cacheETag ;
91+ }
92+
93+ return http . get ( endpoint , {
94+ headers,
95+ isRequestOk,
96+ retry : {
97+ retries : 10 ,
98+ maxTimeout : 200 ,
99+ minTimeout : 1 ,
100+ } ,
101+ logger,
102+ fetchImplementation : args . fetch ,
103+ signal,
104+ } ) ;
105+ } ,
106+ {
107+ ...circuitBreakerConfig ,
108+ timeout : false ,
109+ autoRenewAbortController : true ,
110+ } ,
111+ ) ;
112+ return circuitBreaker ;
113+ } ) ;
114+
115+ return async function fetchArtifact ( ) : Promise < CDNFetchResult > {
116+ // TODO: we can probably do that better...
117+ // If an items is half open, we would probably want to try both the opened and half opened one
118+ const fetcher = circuitBreakers . find ( item => item . opened || item . halfOpen ) ;
64119
65- if ( cacheETag ) {
66- headers [ 'If-None-Match' ] = cacheETag ;
120+ if ( ! fetcher ) {
121+ if ( cached !== null ) {
122+ return cached ;
67123 }
68124
69- return http . get ( args . endpoint , {
70- headers,
71- isRequestOk,
72- retry : {
73- retries : 10 ,
74- maxTimeout : 200 ,
75- minTimeout : 1 ,
76- } ,
77- logger,
78- fetchImplementation,
79- signal,
80- } ) ;
81- } ,
82- {
83- ...circuitBreakerConfig ,
84- timeout : false ,
85- autoRenewAbortController : true ,
86- } ,
87- ) ;
88-
89- return async function fetchArtifact ( fetchArgs ?: CDNFetcherArgs ) : Promise < CDNFetchResult > {
125+ throw new Error ( 'Failed to retrieve artifact.' ) ;
126+ }
127+
90128 try {
91- const response = await circuitBreaker . fire ( fetchArgs ) ;
129+ const response = await fetcher . fire ( ) ;
92130
93131 if ( response . status === 304 ) {
94132 if ( cached !== null ) {
0 commit comments