@constructive-io/graphql-codegen 3.0.4 → 3.1.1

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.
package/README.md CHANGED
@@ -183,6 +183,8 @@ interface GraphQLSDKConfigTarget {
183
183
  // Generator flags
184
184
  reactQuery?: boolean; // Generate React Query hooks (output: {output}/hooks)
185
185
  orm?: boolean; // Generate ORM client (output: {output}/orm)
186
+ browserCompatible?: boolean; // Generate browser-compatible code (default: true)
187
+ // Set to false for Node.js with localhost DNS fix
186
188
 
187
189
  // Table filtering (for CRUD operations from _meta)
188
190
  tables?: {
@@ -683,6 +685,8 @@ Generator Options:
683
685
  -t, --target <name> Target name in config file
684
686
  -o, --output <dir> Output directory
685
687
  -a, --authorization <token> Authorization header value
688
+ --browser-compatible Generate browser-compatible code (default: true)
689
+ Set to false for Node.js with localhost DNS fix
686
690
  --skip-custom-operations Only generate table CRUD operations
687
691
  --dry-run Preview without writing files
688
692
  --keep-db Keep ephemeral database after generation (pgpm modes)
package/cli/index.js CHANGED
@@ -33,6 +33,8 @@ Generator Options:
33
33
  -o, --output <dir> Output directory
34
34
  -t, --target <name> Target name (for multi-target configs)
35
35
  -a, --authorization <token> Authorization header value
36
+ --browser-compatible Generate browser-compatible code (default: true)
37
+ Set to false for Node.js with localhost DNS fix
36
38
  --dry-run Preview without writing files
37
39
  -v, --verbose Show detailed output
38
40
 
@@ -105,6 +107,7 @@ const commands = async (argv, prompter, _options) => {
105
107
  authorization: answers.authorization,
106
108
  reactQuery: answers.reactQuery,
107
109
  orm: answers.orm,
110
+ browserCompatible: answers.browserCompatible,
108
111
  dryRun: answers.dryRun,
109
112
  verbose: answers.verbose,
110
113
  });
@@ -126,7 +129,7 @@ exports.options = {
126
129
  v: 'verbose',
127
130
  },
128
131
  boolean: [
129
- 'help', 'version', 'verbose', 'dry-run', 'react-query', 'orm', 'keep-db',
132
+ 'help', 'version', 'verbose', 'dry-run', 'react-query', 'orm', 'keep-db', 'browser-compatible',
130
133
  ],
131
134
  string: [
132
135
  'config', 'endpoint', 'schema-file', 'output', 'target', 'authorization',
package/cli/shared.d.ts CHANGED
@@ -21,6 +21,7 @@ export interface CodegenAnswers {
21
21
  apiNames?: string[];
22
22
  reactQuery?: boolean;
23
23
  orm?: boolean;
24
+ browserCompatible?: boolean;
24
25
  authorization?: string;
25
26
  dryRun?: boolean;
26
27
  verbose?: boolean;
package/cli/shared.js CHANGED
@@ -65,6 +65,14 @@ exports.codegenQuestions = [
65
65
  default: false,
66
66
  useDefault: true,
67
67
  },
68
+ {
69
+ name: 'browserCompatible',
70
+ message: 'Generate browser-compatible code?',
71
+ type: 'confirm',
72
+ required: false,
73
+ default: true,
74
+ useDefault: true,
75
+ },
68
76
  {
69
77
  name: 'authorization',
70
78
  message: 'Authorization header value',
@@ -1,4 +1,14 @@
1
+ export interface GenerateClientFileOptions {
2
+ /**
3
+ * Generate browser-compatible code using native fetch
4
+ * When true (default), uses native W3C fetch API
5
+ * When false, uses undici fetch with dispatcher support for localhost DNS resolution
6
+ * @default true
7
+ */
8
+ browserCompatible?: boolean;
9
+ }
1
10
  /**
2
11
  * Generate client.ts content
12
+ * @param options - Generation options
3
13
  */
4
- export declare function generateClientFile(): string;
14
+ export declare function generateClientFile(options?: GenerateClientFileOptions): string;
@@ -1,264 +1,79 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.generateClientFile = generateClientFile;
4
37
  /**
5
38
  * Client generator - generates client.ts with configure() and execute()
6
- */
7
- const utils_1 = require("./utils");
8
- /**
9
- * Generate client.ts content
10
- */
11
- function generateClientFile() {
12
- return `${(0, utils_1.getGeneratedFileHeader)('GraphQL client configuration and execution')}
13
-
14
- // ============================================================================
15
- // Configuration
16
- // ============================================================================
17
-
18
- export interface GraphQLClientConfig {
19
- /** GraphQL endpoint URL */
20
- endpoint: string;
21
- /** Default headers to include in all requests */
22
- headers?: Record<string, string>;
23
- }
24
-
25
- let globalConfig: GraphQLClientConfig | null = null;
26
-
27
- /**
28
- * Configure the GraphQL client
29
- *
30
- * @example
31
- * \`\`\`ts
32
- * import { configure } from './generated';
33
- *
34
- * configure({
35
- * endpoint: 'https://api.example.com/graphql',
36
- * headers: {
37
- * Authorization: 'Bearer <token>',
38
- * },
39
- * });
40
- * \`\`\`
41
- */
42
- export function configure(config: GraphQLClientConfig): void {
43
- globalConfig = config;
44
- }
45
-
46
- /**
47
- * Get the current configuration
48
- * @throws Error if not configured
49
- */
50
- export function getConfig(): GraphQLClientConfig {
51
- if (!globalConfig) {
52
- throw new Error(
53
- 'GraphQL client not configured. Call configure() before making requests.'
54
- );
55
- }
56
- return globalConfig;
57
- }
58
-
59
- /**
60
- * Set a single header value
61
- * Useful for updating Authorization after login
62
39
  *
63
- * @example
64
- * \`\`\`ts
65
- * setHeader('Authorization', 'Bearer <new-token>');
66
- * \`\`\`
40
+ * Reads from template files in the templates/ directory for proper type checking.
67
41
  */
68
- export function setHeader(key: string, value: string): void {
69
- const config = getConfig();
70
- globalConfig = {
71
- ...config,
72
- headers: { ...config.headers, [key]: value },
73
- };
74
- }
75
-
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const utils_1 = require("./utils");
76
45
  /**
77
- * Merge multiple headers into the current configuration
78
- *
79
- * @example
80
- * \`\`\`ts
81
- * setHeaders({ Authorization: 'Bearer <token>', 'X-Custom': 'value' });
82
- * \`\`\`
46
+ * Find a template file path.
47
+ * Templates are at ./templates/ relative to this file in both src/ and dist/.
83
48
  */
84
- export function setHeaders(headers: Record<string, string>): void {
85
- const config = getConfig();
86
- globalConfig = {
87
- ...config,
88
- headers: { ...config.headers, ...headers },
89
- };
90
- }
91
-
92
- // ============================================================================
93
- // Error handling
94
- // ============================================================================
95
-
96
- export interface GraphQLError {
97
- message: string;
98
- locations?: Array<{ line: number; column: number }>;
99
- path?: Array<string | number>;
100
- extensions?: Record<string, unknown>;
101
- }
102
-
103
- export class GraphQLClientError extends Error {
104
- constructor(
105
- message: string,
106
- public errors: GraphQLError[],
107
- public response?: Response
108
- ) {
109
- super(message);
110
- this.name = 'GraphQLClientError';
111
- }
49
+ function findTemplateFile(templateName) {
50
+ const templatePath = path.join(__dirname, 'templates', templateName);
51
+ if (fs.existsSync(templatePath)) {
52
+ return templatePath;
53
+ }
54
+ throw new Error(`Could not find template file: ${templateName}. ` +
55
+ `Searched in: ${templatePath}`);
112
56
  }
113
-
114
- // ============================================================================
115
- // Execution
116
- // ============================================================================
117
-
118
- export interface ExecuteOptions {
119
- /** Override headers for this request */
120
- headers?: Record<string, string>;
121
- /** AbortSignal for request cancellation */
122
- signal?: AbortSignal;
123
- }
124
-
125
57
  /**
126
- * Execute a GraphQL operation
127
- *
128
- * @example
129
- * \`\`\`ts
130
- * const result = await execute<CarsQueryResult, CarsQueryVariables>(
131
- * carsQueryDocument,
132
- * { first: 10 }
133
- * );
134
- * \`\`\`
58
+ * Read a template file and replace the header with generated file header
135
59
  */
136
- export async function execute<TData = unknown, TVariables = Record<string, unknown>>(
137
- document: string,
138
- variables?: TVariables,
139
- options?: ExecuteOptions
140
- ): Promise<TData> {
141
- const config = getConfig();
142
-
143
- const response = await fetch(config.endpoint, {
144
- method: 'POST',
145
- headers: {
146
- 'Content-Type': 'application/json',
147
- ...config.headers,
148
- ...options?.headers,
149
- },
150
- body: JSON.stringify({
151
- query: document,
152
- variables,
153
- }),
154
- signal: options?.signal,
155
- });
156
-
157
- const json = await response.json();
158
-
159
- if (json.errors && json.errors.length > 0) {
160
- throw new GraphQLClientError(
161
- json.errors[0].message || 'GraphQL request failed',
162
- json.errors,
163
- response
164
- );
165
- }
166
-
167
- return json.data as TData;
60
+ function readTemplateFile(templateName, description) {
61
+ const templatePath = findTemplateFile(templateName);
62
+ let content = fs.readFileSync(templatePath, 'utf-8');
63
+ // Replace the source file header comment with the generated file header
64
+ // Match the header pattern used in template files
65
+ const headerPattern = /\/\*\*[\s\S]*?\* NOTE: This file is read at codegen time and written to output\.[\s\S]*?\*\/\n*/;
66
+ content = content.replace(headerPattern, (0, utils_1.getGeneratedFileHeader)(description) + '\n');
67
+ return content;
168
68
  }
169
-
170
69
  /**
171
- * Execute a GraphQL operation with full response (data + errors)
172
- * Useful when you want to handle partial data with errors
173
- */
174
- export async function executeWithErrors<TData = unknown, TVariables = Record<string, unknown>>(
175
- document: string,
176
- variables?: TVariables,
177
- options?: ExecuteOptions
178
- ): Promise<{ data: TData | null; errors: GraphQLError[] | null }> {
179
- const config = getConfig();
180
-
181
- const response = await fetch(config.endpoint, {
182
- method: 'POST',
183
- headers: {
184
- 'Content-Type': 'application/json',
185
- ...config.headers,
186
- ...options?.headers,
187
- },
188
- body: JSON.stringify({
189
- query: document,
190
- variables,
191
- }),
192
- signal: options?.signal,
193
- });
194
-
195
- const json = await response.json();
196
-
197
- return {
198
- data: json.data ?? null,
199
- errors: json.errors ?? null,
200
- };
201
- }
202
-
203
- // ============================================================================
204
- // QueryClient Factory
205
- // ============================================================================
206
-
207
- /**
208
- * Default QueryClient configuration optimized for GraphQL
209
- *
210
- * These defaults provide a good balance between freshness and performance:
211
- * - staleTime: 1 minute - data considered fresh, won't refetch
212
- * - gcTime: 5 minutes - unused data kept in cache
213
- * - refetchOnWindowFocus: false - don't refetch when tab becomes active
214
- * - retry: 1 - retry failed requests once
215
- */
216
- export const defaultQueryClientOptions = {
217
- defaultOptions: {
218
- queries: {
219
- staleTime: 1000 * 60, // 1 minute
220
- gcTime: 1000 * 60 * 5, // 5 minutes
221
- refetchOnWindowFocus: false,
222
- retry: 1,
223
- },
224
- },
225
- };
226
-
227
- /**
228
- * QueryClient options type for createQueryClient
70
+ * Generate client.ts content
71
+ * @param options - Generation options
229
72
  */
230
- export interface CreateQueryClientOptions {
231
- defaultOptions?: {
232
- queries?: {
233
- staleTime?: number;
234
- gcTime?: number;
235
- refetchOnWindowFocus?: boolean;
236
- retry?: number | boolean;
237
- retryDelay?: number | ((attemptIndex: number) => number);
238
- };
239
- mutations?: {
240
- retry?: number | boolean;
241
- retryDelay?: number | ((attemptIndex: number) => number);
242
- };
243
- };
244
- }
245
-
246
- // Note: createQueryClient is available when using with @tanstack/react-query
247
- // Import QueryClient from '@tanstack/react-query' and use these options:
248
- //
249
- // import { QueryClient } from '@tanstack/react-query';
250
- // const queryClient = new QueryClient(defaultQueryClientOptions);
251
- //
252
- // Or merge with your own options:
253
- // const queryClient = new QueryClient({
254
- // ...defaultQueryClientOptions,
255
- // defaultOptions: {
256
- // ...defaultQueryClientOptions.defaultOptions,
257
- // queries: {
258
- // ...defaultQueryClientOptions.defaultOptions.queries,
259
- // staleTime: 30000, // Override specific options
260
- // },
261
- // },
262
- // });
263
- `;
73
+ function generateClientFile(options = {}) {
74
+ const { browserCompatible = true } = options;
75
+ const templateName = browserCompatible
76
+ ? 'client.browser.ts'
77
+ : 'client.node.ts';
78
+ return readTemplateFile(templateName, 'GraphQL client configuration and execution');
264
79
  }
@@ -45,7 +45,9 @@ function generate(options) {
45
45
  // 1. Generate client.ts
46
46
  files.push({
47
47
  path: 'client.ts',
48
- content: (0, client_1.generateClientFile)(),
48
+ content: (0, client_1.generateClientFile)({
49
+ browserCompatible: config.browserCompatible ?? true,
50
+ }),
49
51
  });
50
52
  // Collect table type names for import path resolution
51
53
  const tableTypeNames = new Set(tables.map((t) => (0, utils_1.getTableNames)(t).typeName));
@@ -11,17 +11,20 @@ export interface GeneratedClientFile {
11
11
  /**
12
12
  * Generate the main client.ts file (OrmClient class)
13
13
  * This is the runtime client that handles GraphQL execution
14
+ *
15
+ * Reads from the templates directory for proper type checking.
14
16
  */
15
17
  export declare function generateOrmClientFile(): GeneratedClientFile;
16
18
  /**
17
19
  * Generate the query-builder.ts file (runtime query builder)
18
20
  *
19
- * Reads from the actual TypeScript file in the source directory,
20
- * which enables proper type checking and testability.
21
+ * Reads from the templates directory for proper type checking and testability.
21
22
  */
22
23
  export declare function generateQueryBuilderFile(): GeneratedClientFile;
23
24
  /**
24
25
  * Generate the select-types.ts file
26
+ *
27
+ * Reads from the templates directory for proper type checking.
25
28
  */
26
29
  export declare function generateSelectTypesFile(): GeneratedClientFile;
27
30
  /**