@graphql-tools/executor-http
Advanced tools
Comparing version
# @graphql-tools/executor-http | ||
## 1.2.9-rc-fa9af4651df2ba248ee59d3d4b64dfd70b45e988 | ||
## 1.3.0-alpha-97aa61c442facee49ab18af23145184fd1cb236c | ||
### Minor Changes | ||
- [#792](https://github.com/graphql-hive/gateway/pull/792) [`9af6aae`](https://github.com/graphql-hive/gateway/commit/9af6aae2beec0dc73952b2972b4ed66f27d9dd78) Thanks [@ardatan](https://github.com/ardatan)! - Ensure subgraph name is present in the upstream error extensions when HTTP Executor throws | ||
### Patch Changes | ||
@@ -19,2 +23,9 @@ | ||
- [#791](https://github.com/graphql-hive/gateway/pull/791) [`661b103`](https://github.com/graphql-hive/gateway/commit/661b103a7b9586641e69b78cbaad516e550e7192) Thanks [@ardatan](https://github.com/ardatan)! - dependencies updates: | ||
- Removed dependency [`value-or-promise@^1.0.12` ↗︎](https://www.npmjs.com/package/value-or-promise/v/1.0.12) (from `dependencies`) | ||
- Updated dependencies [[`e393337`](https://github.com/graphql-hive/gateway/commit/e393337ecb40beffb79748b19b5aa8f2fd9197b7)]: | ||
- @graphql-tools/executor-common@0.0.4-alpha-97aa61c442facee49ab18af23145184fd1cb236c | ||
## 1.2.8 | ||
@@ -21,0 +32,0 @@ |
@@ -18,2 +18,6 @@ import { ExecutionRequest, DisposableSyncExecutor, DisposableAsyncExecutor } from '@graphql-tools/utils'; | ||
/** | ||
* The name of the service | ||
*/ | ||
serviceName?: string; | ||
/** | ||
* The endpoint to use when querying the upstream API | ||
@@ -20,0 +24,0 @@ * @default '/graphql' |
@@ -6,3 +6,2 @@ import { defaultPrintFn, serializeExecutionRequest } from '@graphql-tools/executor-common'; | ||
import { isPromise, handleMaybePromise } from '@whatwg-node/promise-helpers'; | ||
import { ValueOrPromise } from 'value-or-promise'; | ||
import { Repeater } from '@repeaterjs/repeater'; | ||
@@ -522,2 +521,3 @@ import { GraphQLError } from 'graphql'; | ||
const disposeCtrl = new AbortController(); | ||
const serviceName = options?.serviceName; | ||
const baseExecutor = (request, excludeQuery) => { | ||
@@ -572,2 +572,3 @@ if (disposeCtrl.signal.aborted) { | ||
const upstreamErrorExtensions = { | ||
serviceName, | ||
request: { | ||
@@ -607,160 +608,176 @@ method | ||
} | ||
function handleError(e) { | ||
if (e.name === "AggregateError") { | ||
return { | ||
errors: e.errors.map( | ||
(e2) => coerceFetchError(e2, { | ||
signal, | ||
endpoint, | ||
upstreamErrorExtensions | ||
}) | ||
) | ||
}; | ||
} | ||
return { | ||
errors: [ | ||
coerceFetchError(e, { | ||
signal, | ||
endpoint, | ||
upstreamErrorExtensions | ||
}) | ||
] | ||
}; | ||
} | ||
return handleMaybePromise( | ||
() => serializeFn(), | ||
(body) => new ValueOrPromise(() => { | ||
switch (method) { | ||
case "GET": { | ||
const finalUrl = prepareGETUrl({ | ||
baseUrl: endpoint, | ||
body | ||
}); | ||
const fetchOptions = { | ||
method: "GET", | ||
headers, | ||
signal | ||
}; | ||
if (options?.credentials != null) { | ||
fetchOptions.credentials = options.credentials; | ||
(body) => handleMaybePromise( | ||
() => { | ||
switch (method) { | ||
case "GET": { | ||
const finalUrl = prepareGETUrl({ | ||
baseUrl: endpoint, | ||
body | ||
}); | ||
const fetchOptions = { | ||
method: "GET", | ||
headers, | ||
signal | ||
}; | ||
if (options?.credentials != null) { | ||
fetchOptions.credentials = options.credentials; | ||
} | ||
upstreamErrorExtensions.request.url = finalUrl; | ||
return fetchFn( | ||
finalUrl, | ||
fetchOptions, | ||
request.context, | ||
request.info | ||
); | ||
} | ||
upstreamErrorExtensions.request.url = finalUrl; | ||
return fetchFn( | ||
finalUrl, | ||
fetchOptions, | ||
request.context, | ||
request.info | ||
); | ||
case "POST": { | ||
upstreamErrorExtensions.request.body = body; | ||
return handleMaybePromise( | ||
() => createFormDataFromVariables(body, { | ||
File: options?.File, | ||
FormData: options?.FormData | ||
}), | ||
(body2) => { | ||
if (typeof body2 === "string" && !headers["content-type"]) { | ||
upstreamErrorExtensions.request.body = body2; | ||
headers["content-type"] = "application/json"; | ||
} | ||
const fetchOptions = { | ||
method: "POST", | ||
body: body2, | ||
headers, | ||
signal | ||
}; | ||
if (options?.credentials != null) { | ||
fetchOptions.credentials = options.credentials; | ||
} | ||
return fetchFn( | ||
endpoint, | ||
fetchOptions, | ||
request.context, | ||
request.info | ||
); | ||
}, | ||
handleError | ||
); | ||
} | ||
} | ||
case "POST": { | ||
upstreamErrorExtensions.request.body = body; | ||
return handleMaybePromise( | ||
() => createFormDataFromVariables(body, { | ||
File: options?.File, | ||
FormData: options?.FormData | ||
}), | ||
(body2) => { | ||
if (typeof body2 === "string" && !headers["content-type"]) { | ||
upstreamErrorExtensions.request.body = body2; | ||
headers["content-type"] = "application/json"; | ||
}, | ||
(fetchResult) => handleMaybePromise( | ||
() => { | ||
upstreamErrorExtensions.response ||= {}; | ||
upstreamErrorExtensions.response.status = fetchResult.status; | ||
upstreamErrorExtensions.response.statusText = fetchResult.statusText; | ||
Object.defineProperty( | ||
upstreamErrorExtensions.response, | ||
"headers", | ||
{ | ||
get() { | ||
return Object.fromEntries(fetchResult.headers.entries()); | ||
} | ||
const fetchOptions = { | ||
method: "POST", | ||
body: body2, | ||
headers, | ||
signal | ||
}; | ||
if (options?.credentials != null) { | ||
fetchOptions.credentials = options.credentials; | ||
} | ||
return fetchFn( | ||
endpoint, | ||
fetchOptions, | ||
request.context, | ||
request.info | ||
); | ||
} | ||
); | ||
} | ||
} | ||
}).then((fetchResult) => { | ||
upstreamErrorExtensions.response ||= {}; | ||
upstreamErrorExtensions.response.status = fetchResult.status; | ||
upstreamErrorExtensions.response.statusText = fetchResult.statusText; | ||
Object.defineProperty(upstreamErrorExtensions.response, "headers", { | ||
get() { | ||
return Object.fromEntries(fetchResult.headers.entries()); | ||
} | ||
}); | ||
if (options?.retry != null && !fetchResult.status.toString().startsWith("2")) { | ||
throw new Error( | ||
fetchResult.statusText || `Upstream HTTP Error: ${fetchResult.status}` | ||
); | ||
} | ||
const contentType = fetchResult.headers.get("content-type"); | ||
if (contentType?.includes("text/event-stream")) { | ||
return handleEventStreamResponse( | ||
fetchResult, | ||
subscriptionCtrl, | ||
signal | ||
); | ||
} else if (contentType?.includes("multipart/mixed")) { | ||
return handleMultipartMixedResponse(fetchResult); | ||
} | ||
return fetchResult.text(); | ||
}).then((result) => { | ||
if (typeof result === "string") { | ||
upstreamErrorExtensions.response ||= {}; | ||
upstreamErrorExtensions.response.body = result; | ||
if (result) { | ||
try { | ||
const parsedResult = JSON.parse(result); | ||
upstreamErrorExtensions.response.body = parsedResult; | ||
if (parsedResult.data == null && (parsedResult.errors == null || parsedResult.errors.length === 0)) { | ||
return { | ||
errors: [ | ||
createGraphQLError( | ||
'Unexpected empty "data" and "errors" fields in result: ' + result, | ||
{ | ||
extensions: upstreamErrorExtensions | ||
} | ||
) | ||
] | ||
}; | ||
if (options?.retry != null && !fetchResult.status.toString().startsWith("2")) { | ||
throw new Error( | ||
fetchResult.statusText || `Upstream HTTP Error: ${fetchResult.status}` | ||
); | ||
} | ||
const contentType = fetchResult.headers.get("content-type"); | ||
if (contentType?.includes("text/event-stream")) { | ||
return handleEventStreamResponse( | ||
fetchResult, | ||
subscriptionCtrl, | ||
signal | ||
); | ||
} else if (contentType?.includes("multipart/mixed")) { | ||
return handleMultipartMixedResponse(fetchResult); | ||
} | ||
return fetchResult.text(); | ||
}, | ||
(result) => { | ||
if (typeof result === "string") { | ||
upstreamErrorExtensions.response ||= {}; | ||
upstreamErrorExtensions.response.body = result; | ||
if (result) { | ||
try { | ||
const parsedResult = JSON.parse(result); | ||
upstreamErrorExtensions.response.body = parsedResult; | ||
if (parsedResult.data == null && (parsedResult.errors == null || parsedResult.errors.length === 0)) { | ||
return { | ||
errors: [ | ||
createGraphQLError( | ||
'Unexpected empty "data" and "errors" fields in result: ' + result, | ||
{ | ||
extensions: upstreamErrorExtensions | ||
} | ||
) | ||
] | ||
}; | ||
} | ||
if (Array.isArray(parsedResult.errors)) { | ||
return { | ||
...parsedResult, | ||
errors: parsedResult.errors.map( | ||
({ | ||
message, | ||
...options2 | ||
}) => createGraphQLError(message, { | ||
...options2, | ||
extensions: { | ||
code: "DOWNSTREAM_SERVICE_ERROR", | ||
serviceName, | ||
...options2.extensions || {} | ||
} | ||
}) | ||
) | ||
}; | ||
} | ||
return parsedResult; | ||
} catch (e) { | ||
return { | ||
errors: [ | ||
createGraphQLError( | ||
`Unexpected response: ${JSON.stringify(result)}`, | ||
{ | ||
extensions: upstreamErrorExtensions, | ||
originalError: e | ||
} | ||
) | ||
] | ||
}; | ||
} | ||
} | ||
if (Array.isArray(parsedResult.errors)) { | ||
return { | ||
...parsedResult, | ||
errors: parsedResult.errors.map( | ||
({ | ||
message, | ||
...options2 | ||
}) => createGraphQLError(message, { | ||
...options2, | ||
extensions: { | ||
code: "DOWNSTREAM_SERVICE_ERROR", | ||
...options2.extensions || {} | ||
} | ||
}) | ||
) | ||
}; | ||
} | ||
return parsedResult; | ||
} catch (e) { | ||
return { | ||
errors: [ | ||
createGraphQLError( | ||
`Unexpected response: ${JSON.stringify(result)}`, | ||
{ | ||
extensions: upstreamErrorExtensions, | ||
originalError: e | ||
} | ||
) | ||
] | ||
}; | ||
} else { | ||
return result; | ||
} | ||
} | ||
} else { | ||
return result; | ||
} | ||
}).catch((e) => { | ||
if (e.name === "AggregateError") { | ||
return { | ||
errors: e.errors.map( | ||
(e2) => coerceFetchError(e2, { | ||
signal, | ||
endpoint, | ||
upstreamErrorExtensions | ||
}) | ||
) | ||
}; | ||
} | ||
return { | ||
errors: [ | ||
coerceFetchError(e, { | ||
signal, | ||
endpoint, | ||
upstreamErrorExtensions | ||
}) | ||
] | ||
}; | ||
}).resolve() | ||
}, | ||
handleError | ||
), | ||
handleError | ||
), | ||
handleError | ||
); | ||
@@ -767,0 +784,0 @@ }; |
{ | ||
"name": "@graphql-tools/executor-http", | ||
"version": "1.2.9-rc-fa9af4651df2ba248ee59d3d4b64dfd70b45e988", | ||
"version": "1.3.0-alpha-97aa61c442facee49ab18af23145184fd1cb236c", | ||
"type": "module", | ||
@@ -42,3 +42,3 @@ "description": "A set of utils for faster development of GraphQL tools", | ||
"dependencies": { | ||
"@graphql-tools/executor-common": "^0.0.3", | ||
"@graphql-tools/executor-common": "0.0.4-alpha-97aa61c442facee49ab18af23145184fd1cb236c", | ||
"@graphql-tools/utils": "^10.8.1", | ||
@@ -50,4 +50,3 @@ "@repeaterjs/repeater": "^3.0.4", | ||
"meros": "^1.2.1", | ||
"tslib": "^2.8.1", | ||
"value-or-promise": "^1.0.12" | ||
"tslib": "^2.8.1" | ||
}, | ||
@@ -60,3 +59,3 @@ "devDependencies": { | ||
"graphql": "^16.9.0", | ||
"graphql-yoga": "^5.12.0", | ||
"graphql-yoga": "^5.13.1", | ||
"pkgroll": "2.11.2" | ||
@@ -63,0 +62,0 @@ }, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
94720
2.63%9
-10%1780
2.18%+ Added
- Removed
- Removed
- Removed
Updated