Skip to content

Commit c2e5d0b

Browse files
committed
feat(codegen): add AbortSignal and per-request headers to NodeHttpAdapter
- Add optional AbortSignal support to makeRequest() for request cancellation (early reject if already aborted, destroy request on abort event, clean up listener on close) - Add NodeHttpExecuteOptions interface with headers and signal fields - Add optional options parameter to NodeHttpAdapter.execute() for per-request header overrides and AbortSignal - These additions are backward-compatible (all new parameters are optional) and extend beyond the base GraphQLAdapter interface
1 parent c586aa0 commit c2e5d0b

1 file changed

Lines changed: 37 additions & 1 deletion

File tree

graphql/codegen/src/core/codegen/templates/node-fetch.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,20 @@ function isLocalhostSubdomain(hostname: string): boolean {
4343

4444
/**
4545
* Make an HTTP/HTTPS request using native Node modules.
46+
* Supports optional AbortSignal for request cancellation.
4647
*/
4748
function makeRequest(
4849
url: URL,
4950
options: http.RequestOptions,
5051
body: string,
52+
signal?: AbortSignal,
5153
): Promise<HttpResponse> {
5254
return new Promise((resolve, reject) => {
55+
if (signal?.aborted) {
56+
reject(new Error('The operation was aborted'));
57+
return;
58+
}
59+
5360
const protocol = url.protocol === 'https:' ? https : http;
5461

5562
const req = protocol.request(url, options, (res) => {
@@ -68,11 +75,33 @@ function makeRequest(
6875
});
6976

7077
req.on('error', reject);
78+
79+
if (signal) {
80+
const onAbort = () => {
81+
req.destroy(new Error('The operation was aborted'));
82+
};
83+
signal.addEventListener('abort', onAbort, { once: true });
84+
req.on('close', () => {
85+
signal.removeEventListener('abort', onAbort);
86+
});
87+
}
88+
7189
req.write(body);
7290
req.end();
7391
});
7492
}
7593

94+
/**
95+
* Options for individual execute calls.
96+
* Allows per-request header overrides and request cancellation.
97+
*/
98+
export interface NodeHttpExecuteOptions {
99+
/** Additional headers to include in this request only */
100+
headers?: Record<string, string>;
101+
/** AbortSignal for request cancellation */
102+
signal?: AbortSignal;
103+
}
104+
76105
/**
77106
* GraphQL adapter that uses node:http/node:https for requests.
78107
*
@@ -94,12 +123,14 @@ export class NodeHttpAdapter implements GraphQLAdapter {
94123
async execute<T>(
95124
document: string,
96125
variables?: Record<string, unknown>,
126+
options?: NodeHttpExecuteOptions,
97127
): Promise<QueryResult<T>> {
98128
const requestUrl = new URL(this.url.href);
99129
const requestHeaders: Record<string, string> = {
100130
'Content-Type': 'application/json',
101131
Accept: 'application/json',
102132
...this.headers,
133+
...options?.headers,
103134
};
104135

105136
// For *.localhost subdomains, rewrite hostname and inject Host header
@@ -118,7 +149,12 @@ export class NodeHttpAdapter implements GraphQLAdapter {
118149
headers: requestHeaders,
119150
};
120151

121-
const response = await makeRequest(requestUrl, requestOptions, body);
152+
const response = await makeRequest(
153+
requestUrl,
154+
requestOptions,
155+
body,
156+
options?.signal,
157+
);
122158

123159
if (response.statusCode < 200 || response.statusCode >= 300) {
124160
return {

0 commit comments

Comments
 (0)