@constructive-io/graphql-codegen 3.1.1 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,86 +1,58 @@
1
1
  /**
2
- * GraphQL client configuration and execution (Node.js with undici)
2
+ * GraphQL client configuration and execution (Node.js with native http/https)
3
3
  *
4
4
  * This is the RUNTIME code that gets copied to generated output.
5
- * Uses undici fetch with dispatcher support for localhost DNS resolution.
5
+ * Uses native Node.js http/https modules.
6
6
  *
7
7
  * NOTE: This file is read at codegen time and written to output.
8
8
  * Any changes here will affect all generated clients.
9
9
  */
10
10
 
11
- import dns from 'node:dns';
12
- import { Agent, fetch, type RequestInit } from 'undici';
11
+ import http from 'node:http';
12
+ import https from 'node:https';
13
13
 
14
14
  // ============================================================================
15
- // Localhost DNS Resolution
15
+ // HTTP Request Helper
16
16
  // ============================================================================
17
17
 
18
- /**
19
- * Check if a hostname is localhost or a localhost subdomain
20
- */
21
- function isLocalhostHostname(hostname: string): boolean {
22
- return hostname === 'localhost' || hostname.endsWith('.localhost');
18
+ interface HttpResponse {
19
+ statusCode: number;
20
+ statusMessage: string;
21
+ data: string;
23
22
  }
24
23
 
25
24
  /**
26
- * Create an undici Agent that resolves *.localhost to 127.0.0.1
27
- * This fixes DNS resolution issues on macOS where subdomains like api.localhost
28
- * don't resolve automatically (unlike browsers which handle *.localhost).
25
+ * Make an HTTP/HTTPS request using native Node modules
29
26
  */
30
- function createLocalhostAgent(): Agent {
31
- return new Agent({
32
- connect: {
33
- lookup(hostname, opts, cb) {
34
- if (isLocalhostHostname(hostname)) {
35
- // When opts.all is true, callback expects an array of {address, family} objects
36
- // When opts.all is false/undefined, callback expects (err, address, family)
37
- if (opts.all) {
38
- cb(null, [{ address: '127.0.0.1', family: 4 }]);
39
- } else {
40
- cb(null, '127.0.0.1', 4);
41
- }
42
- return;
43
- }
44
- dns.lookup(hostname, opts, cb);
45
- },
46
- },
27
+ function makeRequest(
28
+ url: URL,
29
+ options: http.RequestOptions,
30
+ body: string
31
+ ): Promise<HttpResponse> {
32
+ return new Promise((resolve, reject) => {
33
+ const protocol = url.protocol === 'https:' ? https : http;
34
+
35
+ const req = protocol.request(url, options, (res) => {
36
+ let data = '';
37
+ res.setEncoding('utf8');
38
+ res.on('data', (chunk: string) => {
39
+ data += chunk;
40
+ });
41
+ res.on('end', () => {
42
+ resolve({
43
+ statusCode: res.statusCode || 0,
44
+ statusMessage: res.statusMessage || '',
45
+ data,
46
+ });
47
+ });
48
+ });
49
+
50
+ req.on('error', reject);
51
+ req.write(body);
52
+ req.end();
47
53
  });
48
54
  }
49
55
 
50
- let localhostAgent: Agent | null = null;
51
-
52
- function getLocalhostAgent(): Agent {
53
- if (!localhostAgent) {
54
- localhostAgent = createLocalhostAgent();
55
- }
56
- return localhostAgent;
57
- }
58
-
59
- /**
60
- * Get fetch options with localhost agent if needed
61
- */
62
- function getFetchOptions(
63
- endpoint: string,
64
- baseOptions: RequestInit
65
- ): RequestInit {
66
- const url = new URL(endpoint);
67
- if (isLocalhostHostname(url.hostname)) {
68
- const options: RequestInit = {
69
- ...baseOptions,
70
- dispatcher: getLocalhostAgent(),
71
- };
72
- // Set Host header for localhost subdomains to preserve routing
73
- if (url.hostname !== 'localhost') {
74
- options.headers = {
75
- ...(baseOptions.headers as Record<string, string>),
76
- Host: url.hostname,
77
- };
78
- }
79
- return options;
80
- }
81
- return baseOptions;
82
- }
83
-
84
56
  // ============================================================================
85
57
  // Configuration
86
58
  // ============================================================================
@@ -174,7 +146,7 @@ export class GraphQLClientError extends Error {
174
146
  constructor(
175
147
  message: string,
176
148
  public errors: GraphQLError[],
177
- public response?: Response
149
+ public statusCode?: number
178
150
  ) {
179
151
  super(message);
180
152
  this.name = 'GraphQLClientError';
@@ -188,8 +160,6 @@ export class GraphQLClientError extends Error {
188
160
  export interface ExecuteOptions {
189
161
  /** Override headers for this request */
190
162
  headers?: Record<string, string>;
191
- /** AbortSignal for request cancellation */
192
- signal?: AbortSignal;
193
163
  }
194
164
 
195
165
  /**
@@ -212,25 +182,29 @@ export async function execute<
212
182
  options?: ExecuteOptions
213
183
  ): Promise<TData> {
214
184
  const config = getConfig();
185
+ const url = new URL(config.endpoint);
186
+
187
+ const body = JSON.stringify({
188
+ query: document,
189
+ variables,
190
+ });
215
191
 
216
- const baseOptions: RequestInit = {
192
+ const requestOptions: http.RequestOptions = {
217
193
  method: 'POST',
218
194
  headers: {
219
195
  'Content-Type': 'application/json',
220
196
  ...config.headers,
221
197
  ...options?.headers,
222
198
  },
223
- body: JSON.stringify({
224
- query: document,
225
- variables,
226
- }),
227
- signal: options?.signal,
228
199
  };
229
200
 
230
- const fetchOptions = getFetchOptions(config.endpoint, baseOptions);
231
- const response = await fetch(config.endpoint, fetchOptions);
201
+ const response = await makeRequest(url, requestOptions, body);
202
+
203
+ if (response.statusCode < 200 || response.statusCode >= 300) {
204
+ throw new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`);
205
+ }
232
206
 
233
- const json = (await response.json()) as {
207
+ const json = JSON.parse(response.data) as {
234
208
  data?: TData;
235
209
  errors?: GraphQLError[];
236
210
  };
@@ -239,7 +213,7 @@ export async function execute<
239
213
  throw new GraphQLClientError(
240
214
  json.errors[0].message || 'GraphQL request failed',
241
215
  json.errors,
242
- response as unknown as Response
216
+ response.statusCode
243
217
  );
244
218
  }
245
219
 
@@ -259,25 +233,32 @@ export async function executeWithErrors<
259
233
  options?: ExecuteOptions
260
234
  ): Promise<{ data: TData | null; errors: GraphQLError[] | null }> {
261
235
  const config = getConfig();
236
+ const url = new URL(config.endpoint);
262
237
 
263
- const baseOptions: RequestInit = {
238
+ const body = JSON.stringify({
239
+ query: document,
240
+ variables,
241
+ });
242
+
243
+ const requestOptions: http.RequestOptions = {
264
244
  method: 'POST',
265
245
  headers: {
266
246
  'Content-Type': 'application/json',
267
247
  ...config.headers,
268
248
  ...options?.headers,
269
249
  },
270
- body: JSON.stringify({
271
- query: document,
272
- variables,
273
- }),
274
- signal: options?.signal,
275
250
  };
276
251
 
277
- const fetchOptions = getFetchOptions(config.endpoint, baseOptions);
278
- const response = await fetch(config.endpoint, fetchOptions);
252
+ const response = await makeRequest(url, requestOptions, body);
253
+
254
+ if (response.statusCode < 200 || response.statusCode >= 300) {
255
+ return {
256
+ data: null,
257
+ errors: [{ message: `HTTP ${response.statusCode}: ${response.statusMessage}` }],
258
+ };
259
+ }
279
260
 
280
- const json = (await response.json()) as {
261
+ const json = JSON.parse(response.data) as {
281
262
  data?: TData;
282
263
  errors?: GraphQLError[];
283
264
  };
@@ -6,129 +6,120 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.fetchSchema = fetchSchema;
7
7
  /**
8
8
  * Fetch GraphQL schema introspection from an endpoint
9
+ * Uses native Node.js http/https modules
9
10
  */
10
- const node_dns_1 = __importDefault(require("node:dns"));
11
- const undici_1 = require("undici");
11
+ const node_http_1 = __importDefault(require("node:http"));
12
+ const node_https_1 = __importDefault(require("node:https"));
12
13
  const schema_query_1 = require("./schema-query");
13
14
  /**
14
- * Check if a hostname is localhost or a localhost subdomain
15
+ * Make an HTTP/HTTPS request using native Node modules
15
16
  */
16
- function isLocalhostHostname(hostname) {
17
- return hostname === 'localhost' || hostname.endsWith('.localhost');
18
- }
19
- /**
20
- * Create an undici Agent that resolves *.localhost to 127.0.0.1
21
- * This fixes DNS resolution issues on macOS where subdomains like api.localhost
22
- * don't resolve automatically (unlike browsers which handle *.localhost).
23
- */
24
- function createLocalhostAgent() {
25
- return new undici_1.Agent({
26
- connect: {
27
- lookup(hostname, opts, cb) {
28
- if (isLocalhostHostname(hostname)) {
29
- // When opts.all is true, callback expects an array of {address, family} objects
30
- // When opts.all is false/undefined, callback expects (err, address, family)
31
- if (opts.all) {
32
- cb(null, [{ address: '127.0.0.1', family: 4 }]);
33
- }
34
- else {
35
- cb(null, '127.0.0.1', 4);
36
- }
37
- return;
38
- }
39
- node_dns_1.default.lookup(hostname, opts, cb);
40
- },
41
- },
17
+ function makeRequest(url, options, body, timeout) {
18
+ return new Promise((resolve, reject) => {
19
+ const protocol = url.protocol === 'https:' ? node_https_1.default : node_http_1.default;
20
+ const req = protocol.request(url, options, (res) => {
21
+ let data = '';
22
+ res.setEncoding('utf8');
23
+ res.on('data', (chunk) => {
24
+ data += chunk;
25
+ });
26
+ res.on('end', () => {
27
+ resolve({
28
+ statusCode: res.statusCode || 0,
29
+ statusMessage: res.statusMessage || '',
30
+ data,
31
+ });
32
+ });
33
+ });
34
+ req.on('error', reject);
35
+ req.setTimeout(timeout, () => {
36
+ req.destroy();
37
+ reject(new Error(`Request timeout after ${timeout}ms`));
38
+ });
39
+ req.write(body);
40
+ req.end();
42
41
  });
43
42
  }
44
- let localhostAgent = null;
45
- function getLocalhostAgent() {
46
- if (!localhostAgent) {
47
- localhostAgent = createLocalhostAgent();
48
- }
49
- return localhostAgent;
50
- }
51
43
  /**
52
44
  * Fetch the full schema introspection from a GraphQL endpoint
53
45
  */
54
46
  async function fetchSchema(options) {
55
47
  const { endpoint, authorization, headers = {}, timeout = 30000 } = options;
56
- // Parse the endpoint URL to check for localhost
57
48
  const url = new URL(endpoint);
58
- const useLocalhostAgent = isLocalhostHostname(url.hostname);
59
- // Build headers
60
49
  const requestHeaders = {
61
50
  'Content-Type': 'application/json',
62
51
  Accept: 'application/json',
63
52
  ...headers,
64
53
  };
65
- // Set Host header for localhost subdomains to preserve routing
66
- if (useLocalhostAgent && url.hostname !== 'localhost') {
67
- requestHeaders['Host'] = url.hostname;
68
- }
69
54
  if (authorization) {
70
55
  requestHeaders['Authorization'] = authorization;
71
56
  }
72
- // Create abort controller for timeout
73
- const controller = new AbortController();
74
- const timeoutId = setTimeout(() => controller.abort(), timeout);
75
- // Build fetch options using undici's RequestInit type
76
- const fetchOptions = {
57
+ const body = JSON.stringify({
58
+ query: schema_query_1.SCHEMA_INTROSPECTION_QUERY,
59
+ variables: {},
60
+ });
61
+ const requestOptions = {
77
62
  method: 'POST',
78
63
  headers: requestHeaders,
79
- body: JSON.stringify({
80
- query: schema_query_1.SCHEMA_INTROSPECTION_QUERY,
81
- variables: {},
82
- }),
83
- signal: controller.signal,
84
64
  };
85
- // Use custom agent for localhost to fix DNS resolution on macOS
86
- if (useLocalhostAgent) {
87
- fetchOptions.dispatcher = getLocalhostAgent();
88
- }
89
65
  try {
90
- const response = await (0, undici_1.fetch)(endpoint, fetchOptions);
91
- clearTimeout(timeoutId);
92
- if (!response.ok) {
66
+ const response = await makeRequest(url, requestOptions, body, timeout);
67
+ if (response.statusCode < 200 || response.statusCode >= 300) {
93
68
  return {
94
69
  success: false,
95
- error: `HTTP ${response.status}: ${response.statusText}`,
96
- statusCode: response.status,
70
+ error: `HTTP ${response.statusCode}: ${response.statusMessage}`,
71
+ statusCode: response.statusCode,
97
72
  };
98
73
  }
99
- const json = (await response.json());
100
- // Check for GraphQL errors
74
+ const json = JSON.parse(response.data);
101
75
  if (json.errors && json.errors.length > 0) {
102
76
  const errorMessages = json.errors.map((e) => e.message).join('; ');
103
77
  return {
104
78
  success: false,
105
79
  error: `GraphQL errors: ${errorMessages}`,
106
- statusCode: response.status,
80
+ statusCode: response.statusCode,
107
81
  };
108
82
  }
109
- // Check if __schema is present
110
83
  if (!json.data?.__schema) {
111
84
  return {
112
85
  success: false,
113
86
  error: 'No __schema field in response. Introspection may be disabled on this endpoint.',
114
- statusCode: response.status,
87
+ statusCode: response.statusCode,
115
88
  };
116
89
  }
117
90
  return {
118
91
  success: true,
119
92
  data: json.data,
120
- statusCode: response.status,
93
+ statusCode: response.statusCode,
121
94
  };
122
95
  }
123
96
  catch (err) {
124
- clearTimeout(timeoutId);
125
97
  if (err instanceof Error) {
126
- if (err.name === 'AbortError') {
98
+ if (err.message.includes('timeout')) {
127
99
  return {
128
100
  success: false,
129
101
  error: `Request timeout after ${timeout}ms`,
130
102
  };
131
103
  }
104
+ const errorCode = err.code;
105
+ if (errorCode === 'ECONNREFUSED') {
106
+ return {
107
+ success: false,
108
+ error: `Connection refused - is the server running at ${endpoint}?`,
109
+ };
110
+ }
111
+ if (errorCode === 'ENOTFOUND') {
112
+ return {
113
+ success: false,
114
+ error: `DNS lookup failed for ${url.hostname} - check the endpoint URL`,
115
+ };
116
+ }
117
+ if (errorCode === 'ECONNRESET') {
118
+ return {
119
+ success: false,
120
+ error: `Connection reset by server at ${endpoint}`,
121
+ };
122
+ }
132
123
  return {
133
124
  success: false,
134
125
  error: err.message,
@@ -27,12 +27,12 @@ export interface WriteOptions {
27
27
  */
28
28
  export declare function writeGeneratedFiles(files: GeneratedFile[], outputDir: string, subdirs: string[], options?: WriteOptions): Promise<WriteResult>;
29
29
  /**
30
- * Format generated files using oxfmt
30
+ * Format generated files using oxfmt programmatically
31
31
  *
32
- * Runs oxfmt on the output directory after all files are written.
33
- * Uses the same formatting options as prettier: single quotes, trailing commas, 2-space tabs, semicolons.
32
+ * @deprecated Use writeGeneratedFiles with formatFiles option instead.
33
+ * This function is kept for backwards compatibility.
34
34
  */
35
- export declare function formatOutput(outputDir: string): {
35
+ export declare function formatOutput(outputDir: string): Promise<{
36
36
  success: boolean;
37
37
  error?: string;
38
- };
38
+ }>;
@@ -43,7 +43,37 @@ exports.formatOutput = formatOutput;
43
43
  */
44
44
  const fs = __importStar(require("node:fs"));
45
45
  const path = __importStar(require("node:path"));
46
- const node_child_process_1 = require("node:child_process");
46
+ /**
47
+ * Dynamically import oxfmt's format function
48
+ * Returns null if oxfmt is not available
49
+ */
50
+ async function getOxfmtFormat() {
51
+ try {
52
+ const oxfmt = await Promise.resolve().then(() => __importStar(require('oxfmt')));
53
+ return oxfmt.format;
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ /**
60
+ * Format a single file's content using oxfmt programmatically
61
+ */
62
+ async function formatFileContent(fileName, content, formatFn) {
63
+ try {
64
+ const result = await formatFn(fileName, content, {
65
+ singleQuote: true,
66
+ trailingComma: 'es5',
67
+ tabWidth: 2,
68
+ semi: true,
69
+ });
70
+ return result.code;
71
+ }
72
+ catch {
73
+ // If formatting fails, return original content
74
+ return content;
75
+ }
76
+ }
47
77
  /**
48
78
  * Write generated files to disk
49
79
  *
@@ -83,6 +113,11 @@ async function writeGeneratedFiles(files, outputDir, subdirs, options = {}) {
83
113
  if (errors.length > 0) {
84
114
  return { success: false, errors };
85
115
  }
116
+ // Get oxfmt format function if formatting is enabled
117
+ const formatFn = formatFiles ? await getOxfmtFormat() : null;
118
+ if (formatFiles && !formatFn && showProgress) {
119
+ console.warn('Warning: oxfmt not available, files will not be formatted');
120
+ }
86
121
  for (let i = 0; i < files.length; i++) {
87
122
  const file = files[i];
88
123
  const filePath = path.join(outputDir, file.path);
@@ -105,8 +140,13 @@ async function writeGeneratedFiles(files, outputDir, subdirs, options = {}) {
105
140
  catch {
106
141
  // Ignore if already exists
107
142
  }
143
+ // Format content if oxfmt is available and file is TypeScript
144
+ let content = file.content;
145
+ if (formatFn && file.path.endsWith('.ts')) {
146
+ content = await formatFileContent(file.path, content, formatFn);
147
+ }
108
148
  try {
109
- fs.writeFileSync(filePath, file.content, 'utf-8');
149
+ fs.writeFileSync(filePath, content, 'utf-8');
110
150
  written.push(filePath);
111
151
  }
112
152
  catch (err) {
@@ -118,16 +158,6 @@ async function writeGeneratedFiles(files, outputDir, subdirs, options = {}) {
118
158
  if (showProgress && isTTY) {
119
159
  process.stdout.write('\r' + ' '.repeat(40) + '\r');
120
160
  }
121
- // Format all generated files with oxfmt
122
- if (formatFiles && errors.length === 0) {
123
- if (showProgress) {
124
- console.log('Formatting generated files...');
125
- }
126
- const formatResult = formatOutput(outputDir);
127
- if (!formatResult.success && showProgress) {
128
- console.warn('Warning: Failed to format generated files:', formatResult.error);
129
- }
130
- }
131
161
  return {
132
162
  success: errors.length === 0,
133
163
  filesWritten: written,
@@ -135,18 +165,50 @@ async function writeGeneratedFiles(files, outputDir, subdirs, options = {}) {
135
165
  };
136
166
  }
137
167
  /**
138
- * Format generated files using oxfmt
168
+ * Recursively find all .ts files in a directory
169
+ */
170
+ function findTsFiles(dir) {
171
+ const files = [];
172
+ try {
173
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
174
+ for (const entry of entries) {
175
+ const fullPath = path.join(dir, entry.name);
176
+ if (entry.isDirectory()) {
177
+ files.push(...findTsFiles(fullPath));
178
+ }
179
+ else if (entry.isFile() && entry.name.endsWith('.ts')) {
180
+ files.push(fullPath);
181
+ }
182
+ }
183
+ }
184
+ catch {
185
+ // Ignore errors reading directories
186
+ }
187
+ return files;
188
+ }
189
+ /**
190
+ * Format generated files using oxfmt programmatically
139
191
  *
140
- * Runs oxfmt on the output directory after all files are written.
141
- * Uses the same formatting options as prettier: single quotes, trailing commas, 2-space tabs, semicolons.
192
+ * @deprecated Use writeGeneratedFiles with formatFiles option instead.
193
+ * This function is kept for backwards compatibility.
142
194
  */
143
- function formatOutput(outputDir) {
195
+ async function formatOutput(outputDir) {
196
+ const formatFn = await getOxfmtFormat();
197
+ if (!formatFn) {
198
+ return {
199
+ success: false,
200
+ error: 'oxfmt not available. Install it with: npm install oxfmt',
201
+ };
202
+ }
144
203
  const absoluteOutputDir = path.resolve(outputDir);
145
204
  try {
146
- (0, node_child_process_1.execSync)(`npx oxfmt --write "${absoluteOutputDir}"`, {
147
- stdio: 'pipe',
148
- encoding: 'utf-8',
149
- });
205
+ // Find all .ts files in the output directory
206
+ const tsFiles = findTsFiles(absoluteOutputDir);
207
+ for (const filePath of tsFiles) {
208
+ const content = fs.readFileSync(filePath, 'utf-8');
209
+ const formatted = await formatFileContent(path.basename(filePath), content, formatFn);
210
+ fs.writeFileSync(filePath, formatted, 'utf-8');
211
+ }
150
212
  return { success: true };
151
213
  }
152
214
  catch (err) {
@@ -1,128 +1,119 @@
1
1
  /**
2
2
  * Fetch GraphQL schema introspection from an endpoint
3
+ * Uses native Node.js http/https modules
3
4
  */
4
- import dns from 'node:dns';
5
- import { Agent, fetch } from 'undici';
5
+ import http from 'node:http';
6
+ import https from 'node:https';
6
7
  import { SCHEMA_INTROSPECTION_QUERY } from './schema-query';
7
8
  /**
8
- * Check if a hostname is localhost or a localhost subdomain
9
+ * Make an HTTP/HTTPS request using native Node modules
9
10
  */
10
- function isLocalhostHostname(hostname) {
11
- return hostname === 'localhost' || hostname.endsWith('.localhost');
12
- }
13
- /**
14
- * Create an undici Agent that resolves *.localhost to 127.0.0.1
15
- * This fixes DNS resolution issues on macOS where subdomains like api.localhost
16
- * don't resolve automatically (unlike browsers which handle *.localhost).
17
- */
18
- function createLocalhostAgent() {
19
- return new Agent({
20
- connect: {
21
- lookup(hostname, opts, cb) {
22
- if (isLocalhostHostname(hostname)) {
23
- // When opts.all is true, callback expects an array of {address, family} objects
24
- // When opts.all is false/undefined, callback expects (err, address, family)
25
- if (opts.all) {
26
- cb(null, [{ address: '127.0.0.1', family: 4 }]);
27
- }
28
- else {
29
- cb(null, '127.0.0.1', 4);
30
- }
31
- return;
32
- }
33
- dns.lookup(hostname, opts, cb);
34
- },
35
- },
11
+ function makeRequest(url, options, body, timeout) {
12
+ return new Promise((resolve, reject) => {
13
+ const protocol = url.protocol === 'https:' ? https : http;
14
+ const req = protocol.request(url, options, (res) => {
15
+ let data = '';
16
+ res.setEncoding('utf8');
17
+ res.on('data', (chunk) => {
18
+ data += chunk;
19
+ });
20
+ res.on('end', () => {
21
+ resolve({
22
+ statusCode: res.statusCode || 0,
23
+ statusMessage: res.statusMessage || '',
24
+ data,
25
+ });
26
+ });
27
+ });
28
+ req.on('error', reject);
29
+ req.setTimeout(timeout, () => {
30
+ req.destroy();
31
+ reject(new Error(`Request timeout after ${timeout}ms`));
32
+ });
33
+ req.write(body);
34
+ req.end();
36
35
  });
37
36
  }
38
- let localhostAgent = null;
39
- function getLocalhostAgent() {
40
- if (!localhostAgent) {
41
- localhostAgent = createLocalhostAgent();
42
- }
43
- return localhostAgent;
44
- }
45
37
  /**
46
38
  * Fetch the full schema introspection from a GraphQL endpoint
47
39
  */
48
40
  export async function fetchSchema(options) {
49
41
  const { endpoint, authorization, headers = {}, timeout = 30000 } = options;
50
- // Parse the endpoint URL to check for localhost
51
42
  const url = new URL(endpoint);
52
- const useLocalhostAgent = isLocalhostHostname(url.hostname);
53
- // Build headers
54
43
  const requestHeaders = {
55
44
  'Content-Type': 'application/json',
56
45
  Accept: 'application/json',
57
46
  ...headers,
58
47
  };
59
- // Set Host header for localhost subdomains to preserve routing
60
- if (useLocalhostAgent && url.hostname !== 'localhost') {
61
- requestHeaders['Host'] = url.hostname;
62
- }
63
48
  if (authorization) {
64
49
  requestHeaders['Authorization'] = authorization;
65
50
  }
66
- // Create abort controller for timeout
67
- const controller = new AbortController();
68
- const timeoutId = setTimeout(() => controller.abort(), timeout);
69
- // Build fetch options using undici's RequestInit type
70
- const fetchOptions = {
51
+ const body = JSON.stringify({
52
+ query: SCHEMA_INTROSPECTION_QUERY,
53
+ variables: {},
54
+ });
55
+ const requestOptions = {
71
56
  method: 'POST',
72
57
  headers: requestHeaders,
73
- body: JSON.stringify({
74
- query: SCHEMA_INTROSPECTION_QUERY,
75
- variables: {},
76
- }),
77
- signal: controller.signal,
78
58
  };
79
- // Use custom agent for localhost to fix DNS resolution on macOS
80
- if (useLocalhostAgent) {
81
- fetchOptions.dispatcher = getLocalhostAgent();
82
- }
83
59
  try {
84
- const response = await fetch(endpoint, fetchOptions);
85
- clearTimeout(timeoutId);
86
- if (!response.ok) {
60
+ const response = await makeRequest(url, requestOptions, body, timeout);
61
+ if (response.statusCode < 200 || response.statusCode >= 300) {
87
62
  return {
88
63
  success: false,
89
- error: `HTTP ${response.status}: ${response.statusText}`,
90
- statusCode: response.status,
64
+ error: `HTTP ${response.statusCode}: ${response.statusMessage}`,
65
+ statusCode: response.statusCode,
91
66
  };
92
67
  }
93
- const json = (await response.json());
94
- // Check for GraphQL errors
68
+ const json = JSON.parse(response.data);
95
69
  if (json.errors && json.errors.length > 0) {
96
70
  const errorMessages = json.errors.map((e) => e.message).join('; ');
97
71
  return {
98
72
  success: false,
99
73
  error: `GraphQL errors: ${errorMessages}`,
100
- statusCode: response.status,
74
+ statusCode: response.statusCode,
101
75
  };
102
76
  }
103
- // Check if __schema is present
104
77
  if (!json.data?.__schema) {
105
78
  return {
106
79
  success: false,
107
80
  error: 'No __schema field in response. Introspection may be disabled on this endpoint.',
108
- statusCode: response.status,
81
+ statusCode: response.statusCode,
109
82
  };
110
83
  }
111
84
  return {
112
85
  success: true,
113
86
  data: json.data,
114
- statusCode: response.status,
87
+ statusCode: response.statusCode,
115
88
  };
116
89
  }
117
90
  catch (err) {
118
- clearTimeout(timeoutId);
119
91
  if (err instanceof Error) {
120
- if (err.name === 'AbortError') {
92
+ if (err.message.includes('timeout')) {
121
93
  return {
122
94
  success: false,
123
95
  error: `Request timeout after ${timeout}ms`,
124
96
  };
125
97
  }
98
+ const errorCode = err.code;
99
+ if (errorCode === 'ECONNREFUSED') {
100
+ return {
101
+ success: false,
102
+ error: `Connection refused - is the server running at ${endpoint}?`,
103
+ };
104
+ }
105
+ if (errorCode === 'ENOTFOUND') {
106
+ return {
107
+ success: false,
108
+ error: `DNS lookup failed for ${url.hostname} - check the endpoint URL`,
109
+ };
110
+ }
111
+ if (errorCode === 'ECONNRESET') {
112
+ return {
113
+ success: false,
114
+ error: `Connection reset by server at ${endpoint}`,
115
+ };
116
+ }
126
117
  return {
127
118
  success: false,
128
119
  error: err.message,
@@ -27,12 +27,12 @@ export interface WriteOptions {
27
27
  */
28
28
  export declare function writeGeneratedFiles(files: GeneratedFile[], outputDir: string, subdirs: string[], options?: WriteOptions): Promise<WriteResult>;
29
29
  /**
30
- * Format generated files using oxfmt
30
+ * Format generated files using oxfmt programmatically
31
31
  *
32
- * Runs oxfmt on the output directory after all files are written.
33
- * Uses the same formatting options as prettier: single quotes, trailing commas, 2-space tabs, semicolons.
32
+ * @deprecated Use writeGeneratedFiles with formatFiles option instead.
33
+ * This function is kept for backwards compatibility.
34
34
  */
35
- export declare function formatOutput(outputDir: string): {
35
+ export declare function formatOutput(outputDir: string): Promise<{
36
36
  success: boolean;
37
37
  error?: string;
38
- };
38
+ }>;
@@ -6,7 +6,37 @@
6
6
  */
7
7
  import * as fs from 'node:fs';
8
8
  import * as path from 'node:path';
9
- import { execSync } from 'node:child_process';
9
+ /**
10
+ * Dynamically import oxfmt's format function
11
+ * Returns null if oxfmt is not available
12
+ */
13
+ async function getOxfmtFormat() {
14
+ try {
15
+ const oxfmt = await import('oxfmt');
16
+ return oxfmt.format;
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ /**
23
+ * Format a single file's content using oxfmt programmatically
24
+ */
25
+ async function formatFileContent(fileName, content, formatFn) {
26
+ try {
27
+ const result = await formatFn(fileName, content, {
28
+ singleQuote: true,
29
+ trailingComma: 'es5',
30
+ tabWidth: 2,
31
+ semi: true,
32
+ });
33
+ return result.code;
34
+ }
35
+ catch {
36
+ // If formatting fails, return original content
37
+ return content;
38
+ }
39
+ }
10
40
  /**
11
41
  * Write generated files to disk
12
42
  *
@@ -46,6 +76,11 @@ export async function writeGeneratedFiles(files, outputDir, subdirs, options = {
46
76
  if (errors.length > 0) {
47
77
  return { success: false, errors };
48
78
  }
79
+ // Get oxfmt format function if formatting is enabled
80
+ const formatFn = formatFiles ? await getOxfmtFormat() : null;
81
+ if (formatFiles && !formatFn && showProgress) {
82
+ console.warn('Warning: oxfmt not available, files will not be formatted');
83
+ }
49
84
  for (let i = 0; i < files.length; i++) {
50
85
  const file = files[i];
51
86
  const filePath = path.join(outputDir, file.path);
@@ -68,8 +103,13 @@ export async function writeGeneratedFiles(files, outputDir, subdirs, options = {
68
103
  catch {
69
104
  // Ignore if already exists
70
105
  }
106
+ // Format content if oxfmt is available and file is TypeScript
107
+ let content = file.content;
108
+ if (formatFn && file.path.endsWith('.ts')) {
109
+ content = await formatFileContent(file.path, content, formatFn);
110
+ }
71
111
  try {
72
- fs.writeFileSync(filePath, file.content, 'utf-8');
112
+ fs.writeFileSync(filePath, content, 'utf-8');
73
113
  written.push(filePath);
74
114
  }
75
115
  catch (err) {
@@ -81,16 +121,6 @@ export async function writeGeneratedFiles(files, outputDir, subdirs, options = {
81
121
  if (showProgress && isTTY) {
82
122
  process.stdout.write('\r' + ' '.repeat(40) + '\r');
83
123
  }
84
- // Format all generated files with oxfmt
85
- if (formatFiles && errors.length === 0) {
86
- if (showProgress) {
87
- console.log('Formatting generated files...');
88
- }
89
- const formatResult = formatOutput(outputDir);
90
- if (!formatResult.success && showProgress) {
91
- console.warn('Warning: Failed to format generated files:', formatResult.error);
92
- }
93
- }
94
124
  return {
95
125
  success: errors.length === 0,
96
126
  filesWritten: written,
@@ -98,18 +128,50 @@ export async function writeGeneratedFiles(files, outputDir, subdirs, options = {
98
128
  };
99
129
  }
100
130
  /**
101
- * Format generated files using oxfmt
131
+ * Recursively find all .ts files in a directory
132
+ */
133
+ function findTsFiles(dir) {
134
+ const files = [];
135
+ try {
136
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
137
+ for (const entry of entries) {
138
+ const fullPath = path.join(dir, entry.name);
139
+ if (entry.isDirectory()) {
140
+ files.push(...findTsFiles(fullPath));
141
+ }
142
+ else if (entry.isFile() && entry.name.endsWith('.ts')) {
143
+ files.push(fullPath);
144
+ }
145
+ }
146
+ }
147
+ catch {
148
+ // Ignore errors reading directories
149
+ }
150
+ return files;
151
+ }
152
+ /**
153
+ * Format generated files using oxfmt programmatically
102
154
  *
103
- * Runs oxfmt on the output directory after all files are written.
104
- * Uses the same formatting options as prettier: single quotes, trailing commas, 2-space tabs, semicolons.
155
+ * @deprecated Use writeGeneratedFiles with formatFiles option instead.
156
+ * This function is kept for backwards compatibility.
105
157
  */
106
- export function formatOutput(outputDir) {
158
+ export async function formatOutput(outputDir) {
159
+ const formatFn = await getOxfmtFormat();
160
+ if (!formatFn) {
161
+ return {
162
+ success: false,
163
+ error: 'oxfmt not available. Install it with: npm install oxfmt',
164
+ };
165
+ }
107
166
  const absoluteOutputDir = path.resolve(outputDir);
108
167
  try {
109
- execSync(`npx oxfmt --write "${absoluteOutputDir}"`, {
110
- stdio: 'pipe',
111
- encoding: 'utf-8',
112
- });
168
+ // Find all .ts files in the output directory
169
+ const tsFiles = findTsFiles(absoluteOutputDir);
170
+ for (const filePath of tsFiles) {
171
+ const content = fs.readFileSync(filePath, 'utf-8');
172
+ const formatted = await formatFileContent(path.basename(filePath), content, formatFn);
173
+ fs.writeFileSync(filePath, formatted, 'utf-8');
174
+ }
113
175
  return { success: true };
114
176
  }
115
177
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-codegen",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "description": "GraphQL SDK generator for Constructive databases with React Query hooks",
5
5
  "keywords": [
6
6
  "graphql",
@@ -99,5 +99,5 @@
99
99
  "tsx": "^4.21.0",
100
100
  "typescript": "^5.9.3"
101
101
  },
102
- "gitHead": "03cfe7dad85283c7364bc38261136933a20ba73d"
102
+ "gitHead": "96a9219694ed3a992f6d909867f777818979ee6b"
103
103
  }