@constructive-io/graphql-codegen 2.24.1 → 2.26.0

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.
Files changed (38) hide show
  1. package/README.md +403 -279
  2. package/cli/codegen/orm/client-generator.js +475 -136
  3. package/cli/codegen/orm/custom-ops-generator.js +8 -3
  4. package/cli/codegen/orm/model-generator.js +18 -5
  5. package/cli/codegen/orm/select-types.d.ts +33 -0
  6. package/cli/commands/generate-orm.d.ts +14 -0
  7. package/cli/commands/generate-orm.js +160 -44
  8. package/cli/commands/generate.d.ts +22 -0
  9. package/cli/commands/generate.js +195 -55
  10. package/cli/commands/init.js +29 -9
  11. package/cli/index.js +133 -28
  12. package/cli/watch/orchestrator.d.ts +4 -0
  13. package/cli/watch/orchestrator.js +4 -0
  14. package/esm/cli/codegen/orm/client-generator.js +475 -136
  15. package/esm/cli/codegen/orm/custom-ops-generator.js +8 -3
  16. package/esm/cli/codegen/orm/model-generator.js +18 -5
  17. package/esm/cli/codegen/orm/select-types.d.ts +33 -0
  18. package/esm/cli/commands/generate-orm.d.ts +14 -0
  19. package/esm/cli/commands/generate-orm.js +161 -45
  20. package/esm/cli/commands/generate.d.ts +22 -0
  21. package/esm/cli/commands/generate.js +195 -56
  22. package/esm/cli/commands/init.js +29 -9
  23. package/esm/cli/index.js +134 -29
  24. package/esm/cli/watch/orchestrator.d.ts +4 -0
  25. package/esm/cli/watch/orchestrator.js +5 -1
  26. package/esm/types/config.d.ts +39 -2
  27. package/esm/types/config.js +88 -4
  28. package/esm/types/index.d.ts +2 -2
  29. package/esm/types/index.js +1 -1
  30. package/package.json +10 -7
  31. package/types/config.d.ts +39 -2
  32. package/types/config.js +91 -4
  33. package/types/index.d.ts +2 -2
  34. package/types/index.js +2 -1
  35. package/cli/codegen/orm/query-builder.d.ts +0 -161
  36. package/cli/codegen/orm/query-builder.js +0 -366
  37. package/esm/cli/codegen/orm/query-builder.d.ts +0 -161
  38. package/esm/cli/codegen/orm/query-builder.js +0 -353
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.generateCommand = generateCommand;
37
37
  exports.writeGeneratedFiles = writeGeneratedFiles;
38
+ exports.formatOutput = formatOutput;
38
39
  /**
39
40
  * Generate command - generates SDK from GraphQL schema
40
41
  *
@@ -45,7 +46,7 @@ exports.writeGeneratedFiles = writeGeneratedFiles;
45
46
  */
46
47
  const fs = __importStar(require("node:fs"));
47
48
  const path = __importStar(require("node:path"));
48
- const prettier = __importStar(require("prettier"));
49
+ const node_child_process_1 = require("node:child_process");
49
50
  const config_1 = require("../../types/config");
50
51
  const source_1 = require("../introspect/source");
51
52
  const shared_1 = require("./shared");
@@ -55,9 +56,9 @@ const codegen_1 = require("../codegen");
55
56
  * Execute the generate command
56
57
  */
57
58
  async function generateCommand(options = {}) {
58
- const log = options.verbose ? console.log : () => { };
59
- // 1. Load config
60
- log('Loading configuration...');
59
+ if (options.verbose) {
60
+ console.log('Loading configuration...');
61
+ }
61
62
  const configResult = await loadConfig(options);
62
63
  if (!configResult.success) {
63
64
  return {
@@ -65,24 +66,72 @@ async function generateCommand(options = {}) {
65
66
  message: configResult.error,
66
67
  };
67
68
  }
68
- const config = configResult.config;
69
- // Log source
70
- if (config.schema) {
71
- log(` Schema: ${config.schema}`);
69
+ const targets = configResult.targets ?? [];
70
+ if (targets.length === 0) {
71
+ return {
72
+ success: false,
73
+ message: 'No targets resolved from configuration.',
74
+ };
75
+ }
76
+ const isMultiTarget = configResult.isMulti ?? targets.length > 1;
77
+ const results = [];
78
+ for (const target of targets) {
79
+ const result = await generateForTarget(target, options, isMultiTarget);
80
+ results.push(result);
81
+ }
82
+ if (!isMultiTarget) {
83
+ const [result] = results;
84
+ return {
85
+ success: result.success,
86
+ message: result.message,
87
+ targets: results,
88
+ tables: result.tables,
89
+ customQueries: result.customQueries,
90
+ customMutations: result.customMutations,
91
+ filesWritten: result.filesWritten,
92
+ errors: result.errors,
93
+ };
72
94
  }
73
- else {
74
- log(` Endpoint: ${config.endpoint}`);
95
+ const successCount = results.filter((result) => result.success).length;
96
+ const failedCount = results.length - successCount;
97
+ const summaryMessage = failedCount === 0
98
+ ? `Generated SDK for ${results.length} targets.`
99
+ : `Generated SDK for ${successCount} of ${results.length} targets.`;
100
+ return {
101
+ success: failedCount === 0,
102
+ message: summaryMessage,
103
+ targets: results,
104
+ errors: failedCount > 0
105
+ ? results.flatMap((result) => result.errors ?? [])
106
+ : undefined,
107
+ };
108
+ }
109
+ async function generateForTarget(target, options, isMultiTarget) {
110
+ const config = target.config;
111
+ const prefix = isMultiTarget ? `[${target.name}] ` : '';
112
+ const log = options.verbose
113
+ ? (message) => console.log(`${prefix}${message}`)
114
+ : () => { };
115
+ const formatMessage = (message) => isMultiTarget ? `Target "${target.name}": ${message}` : message;
116
+ if (isMultiTarget) {
117
+ console.log(`\nTarget "${target.name}"`);
118
+ const sourceLabel = config.schema
119
+ ? `schema: ${config.schema}`
120
+ : `endpoint: ${config.endpoint}`;
121
+ console.log(` Source: ${sourceLabel}`);
122
+ console.log(` Output: ${config.output}`);
75
123
  }
76
- log(` Output: ${config.output}`);
77
- // 2. Create schema source
124
+ // 1. Validate source
78
125
  const sourceValidation = (0, source_1.validateSourceOptions)({
79
126
  endpoint: config.endpoint || undefined,
80
127
  schema: config.schema || undefined,
81
128
  });
82
129
  if (!sourceValidation.valid) {
83
130
  return {
131
+ name: target.name,
132
+ output: config.output,
84
133
  success: false,
85
- message: sourceValidation.error,
134
+ message: formatMessage(sourceValidation.error),
86
135
  };
87
136
  }
88
137
  const source = (0, source_1.createSchemaSource)({
@@ -91,7 +140,7 @@ async function generateCommand(options = {}) {
91
140
  authorization: options.authorization || config.headers['Authorization'],
92
141
  headers: config.headers,
93
142
  });
94
- // 3. Run the codegen pipeline
143
+ // 2. Run the codegen pipeline
95
144
  let pipelineResult;
96
145
  try {
97
146
  pipelineResult = await (0, shared_1.runCodegenPipeline)({
@@ -103,21 +152,25 @@ async function generateCommand(options = {}) {
103
152
  }
104
153
  catch (err) {
105
154
  return {
155
+ name: target.name,
156
+ output: config.output,
106
157
  success: false,
107
- message: `Failed to fetch schema: ${err instanceof Error ? err.message : 'Unknown error'}`,
158
+ message: formatMessage(`Failed to fetch schema: ${err instanceof Error ? err.message : 'Unknown error'}`),
108
159
  };
109
160
  }
110
161
  const { tables, customOperations, stats } = pipelineResult;
111
- // 4. Validate tables found
162
+ // 3. Validate tables found
112
163
  const tablesValidation = (0, shared_1.validateTablesFound)(tables);
113
164
  if (!tablesValidation.valid) {
114
165
  return {
166
+ name: target.name,
167
+ output: config.output,
115
168
  success: false,
116
- message: tablesValidation.error,
169
+ message: formatMessage(tablesValidation.error),
117
170
  };
118
171
  }
119
- // 5. Generate code
120
- console.log('Generating code...');
172
+ // 4. Generate code
173
+ console.log(`${prefix}Generating code...`);
121
174
  const { files: generatedFiles, stats: genStats } = (0, codegen_1.generate)({
122
175
  tables,
123
176
  customOperations: {
@@ -127,7 +180,7 @@ async function generateCommand(options = {}) {
127
180
  },
128
181
  config,
129
182
  });
130
- console.log(`Generated ${genStats.totalFiles} files`);
183
+ console.log(`${prefix}Generated ${genStats.totalFiles} files`);
131
184
  log(` ${genStats.queryHooks} table query hooks`);
132
185
  log(` ${genStats.mutationHooks} table mutation hooks`);
133
186
  log(` ${genStats.customQueryHooks} custom query hooks`);
@@ -136,15 +189,17 @@ async function generateCommand(options = {}) {
136
189
  const customMutations = customOperations.mutations.map((m) => m.name);
137
190
  if (options.dryRun) {
138
191
  return {
192
+ name: target.name,
193
+ output: config.output,
139
194
  success: true,
140
- message: `Dry run complete. Would generate ${generatedFiles.length} files for ${tables.length} tables and ${stats.customQueries + stats.customMutations} custom operations.`,
195
+ message: formatMessage(`Dry run complete. Would generate ${generatedFiles.length} files for ${tables.length} tables and ${stats.customQueries + stats.customMutations} custom operations.`),
141
196
  tables: tables.map((t) => t.name),
142
197
  customQueries,
143
198
  customMutations,
144
199
  filesWritten: generatedFiles.map((f) => f.path),
145
200
  };
146
201
  }
147
- // 6. Write files
202
+ // 5. Write files
148
203
  log('Writing files...');
149
204
  const writeResult = await writeGeneratedFiles(generatedFiles, config.output, [
150
205
  'queries',
@@ -152,23 +207,48 @@ async function generateCommand(options = {}) {
152
207
  ]);
153
208
  if (!writeResult.success) {
154
209
  return {
210
+ name: target.name,
211
+ output: config.output,
155
212
  success: false,
156
- message: `Failed to write files: ${writeResult.errors?.join(', ')}`,
213
+ message: formatMessage(`Failed to write files: ${writeResult.errors?.join(', ')}`),
157
214
  errors: writeResult.errors,
158
215
  };
159
216
  }
160
217
  const totalOps = customQueries.length + customMutations.length;
161
218
  const customOpsMsg = totalOps > 0 ? ` and ${totalOps} custom operations` : '';
162
219
  return {
220
+ name: target.name,
221
+ output: config.output,
163
222
  success: true,
164
- message: `Generated SDK for ${tables.length} tables${customOpsMsg}. Files written to ${config.output}`,
223
+ message: formatMessage(`Generated SDK for ${tables.length} tables${customOpsMsg}. Files written to ${config.output}`),
165
224
  tables: tables.map((t) => t.name),
166
225
  customQueries,
167
226
  customMutations,
168
227
  filesWritten: writeResult.filesWritten,
169
228
  };
170
229
  }
230
+ function buildTargetOverrides(options) {
231
+ const overrides = {};
232
+ if (options.endpoint) {
233
+ overrides.endpoint = options.endpoint;
234
+ overrides.schema = undefined;
235
+ }
236
+ if (options.schema) {
237
+ overrides.schema = options.schema;
238
+ overrides.endpoint = undefined;
239
+ }
240
+ if (options.output) {
241
+ overrides.output = options.output;
242
+ }
243
+ return overrides;
244
+ }
171
245
  async function loadConfig(options) {
246
+ if (options.endpoint && options.schema) {
247
+ return {
248
+ success: false,
249
+ error: 'Cannot use both --endpoint and --schema. Choose one source.',
250
+ };
251
+ }
172
252
  // Find config file
173
253
  let configPath = options.config;
174
254
  if (!configPath) {
@@ -182,31 +262,72 @@ async function loadConfig(options) {
182
262
  }
183
263
  baseConfig = loadResult.config;
184
264
  }
185
- // Override with CLI options
186
- const mergedConfig = {
187
- endpoint: options.endpoint || baseConfig.endpoint,
188
- schema: options.schema || baseConfig.schema,
189
- output: options.output || baseConfig.output,
190
- headers: baseConfig.headers,
191
- tables: baseConfig.tables,
192
- queries: baseConfig.queries,
193
- mutations: baseConfig.mutations,
194
- excludeFields: baseConfig.excludeFields,
195
- hooks: baseConfig.hooks,
196
- postgraphile: baseConfig.postgraphile,
197
- codegen: baseConfig.codegen,
198
- reactQuery: baseConfig.reactQuery,
199
- };
200
- // Validate at least one source is provided
265
+ const overrides = buildTargetOverrides(options);
266
+ if ((0, config_1.isMultiConfig)(baseConfig)) {
267
+ if (Object.keys(baseConfig.targets).length === 0) {
268
+ return {
269
+ success: false,
270
+ error: 'Config file defines no targets.',
271
+ };
272
+ }
273
+ if (!options.target &&
274
+ (options.endpoint || options.schema || options.output)) {
275
+ return {
276
+ success: false,
277
+ error: 'Multiple targets configured. Use --target with --endpoint, --schema, or --output.',
278
+ };
279
+ }
280
+ if (options.target && !baseConfig.targets[options.target]) {
281
+ return {
282
+ success: false,
283
+ error: `Target "${options.target}" not found in config file.`,
284
+ };
285
+ }
286
+ const selectedTargets = options.target
287
+ ? { [options.target]: baseConfig.targets[options.target] }
288
+ : baseConfig.targets;
289
+ const defaults = baseConfig.defaults ?? {};
290
+ const resolvedTargets = [];
291
+ for (const [name, target] of Object.entries(selectedTargets)) {
292
+ let mergedTarget = (0, config_1.mergeConfig)(defaults, target);
293
+ if (options.target && name === options.target) {
294
+ mergedTarget = (0, config_1.mergeConfig)(mergedTarget, overrides);
295
+ }
296
+ if (!mergedTarget.endpoint && !mergedTarget.schema) {
297
+ return {
298
+ success: false,
299
+ error: `Target "${name}" is missing an endpoint or schema.`,
300
+ };
301
+ }
302
+ resolvedTargets.push({
303
+ name,
304
+ config: (0, config_1.resolveConfig)(mergedTarget),
305
+ });
306
+ }
307
+ return {
308
+ success: true,
309
+ targets: resolvedTargets,
310
+ isMulti: true,
311
+ };
312
+ }
313
+ if (options.target) {
314
+ return {
315
+ success: false,
316
+ error: 'Config file does not define targets. Remove --target to continue.',
317
+ };
318
+ }
319
+ const mergedConfig = (0, config_1.mergeConfig)(baseConfig, overrides);
201
320
  if (!mergedConfig.endpoint && !mergedConfig.schema) {
202
321
  return {
203
322
  success: false,
204
323
  error: 'No source specified. Use --endpoint or --schema, or create a config file with "graphql-codegen init".',
205
324
  };
206
325
  }
207
- // Resolve with defaults
208
- const config = (0, config_1.resolveConfig)(mergedConfig);
209
- return { success: true, config };
326
+ return {
327
+ success: true,
328
+ targets: [{ name: 'default', config: (0, config_1.resolveConfig)(mergedConfig) }],
329
+ isMulti: false,
330
+ };
210
331
  }
211
332
  async function writeGeneratedFiles(files, outputDir, subdirs, options = {}) {
212
333
  const { showProgress = true } = options;
@@ -262,9 +383,7 @@ async function writeGeneratedFiles(files, outputDir, subdirs, options = {}) {
262
383
  // Ignore if already exists
263
384
  }
264
385
  try {
265
- // Format with prettier
266
- const formattedContent = await formatCode(file.content);
267
- fs.writeFileSync(filePath, formattedContent, 'utf-8');
386
+ fs.writeFileSync(filePath, file.content, 'utf-8');
268
387
  written.push(filePath);
269
388
  }
270
389
  catch (err) {
@@ -276,23 +395,44 @@ async function writeGeneratedFiles(files, outputDir, subdirs, options = {}) {
276
395
  if (showProgress && isTTY) {
277
396
  process.stdout.write('\r' + ' '.repeat(40) + '\r');
278
397
  }
398
+ // Format all generated files with oxfmt
399
+ if (errors.length === 0) {
400
+ if (showProgress) {
401
+ console.log('Formatting generated files...');
402
+ }
403
+ const formatResult = formatOutput(outputDir);
404
+ if (!formatResult.success && showProgress) {
405
+ console.warn('Warning: Failed to format generated files:', formatResult.error);
406
+ }
407
+ }
279
408
  return {
280
409
  success: errors.length === 0,
281
410
  filesWritten: written,
282
411
  errors: errors.length > 0 ? errors : undefined,
283
412
  };
284
413
  }
285
- async function formatCode(code) {
414
+ /**
415
+ * Format generated files using oxfmt
416
+ * Runs oxfmt on the output directory after all files are written
417
+ */
418
+ function formatOutput(outputDir) {
419
+ // Resolve to absolute path for reliable execution
420
+ const absoluteOutputDir = path.resolve(outputDir);
286
421
  try {
287
- return await prettier.format(code, {
288
- parser: 'typescript',
289
- singleQuote: true,
290
- trailingComma: 'es5',
291
- tabWidth: 2,
422
+ // Find oxfmt binary from this package's node_modules/.bin
423
+ // oxfmt is a dependency of @constructive-io/graphql-codegen
424
+ const oxfmtPkgPath = require.resolve('oxfmt/package.json');
425
+ const oxfmtDir = path.dirname(oxfmtPkgPath);
426
+ const oxfmtBin = path.join(oxfmtDir, 'bin', 'oxfmt');
427
+ (0, node_child_process_1.execSync)(`"${oxfmtBin}" "${absoluteOutputDir}"`, {
428
+ stdio: 'pipe',
429
+ encoding: 'utf-8',
292
430
  });
431
+ return { success: true };
293
432
  }
294
- catch {
295
- // If prettier fails, return unformatted code
296
- return code;
433
+ catch (err) {
434
+ // oxfmt may fail if files have syntax errors or if not installed
435
+ const message = err instanceof Error ? err.message : 'Unknown error';
436
+ return { success: false, error: message };
297
437
  }
298
438
  }
@@ -52,6 +52,15 @@ export default defineConfig({
52
52
  // Output directory for generated files
53
53
  output: '{{OUTPUT}}',
54
54
 
55
+ // Optional: Multi-target config (use instead of endpoint/output)
56
+ // defaults: {
57
+ // headers: { Authorization: 'Bearer YOUR_TOKEN' },
58
+ // },
59
+ // targets: {
60
+ // public: { endpoint: 'https://api.example.com/graphql', output: './generated/public' },
61
+ // admin: { schema: './admin.schema.graphql', output: './generated/admin' },
62
+ // },
63
+
55
64
  // Optional: Tables to include/exclude (supports glob patterns)
56
65
  // tables: {
57
66
  // include: ['*'],
@@ -76,7 +85,7 @@ export default defineConfig({
76
85
  * Execute the init command
77
86
  */
78
87
  async function initCommand(options = {}) {
79
- const { directory = process.cwd(), force = false, endpoint = '', output = './generated' } = options;
88
+ const { directory = process.cwd(), force = false, endpoint = '', output = './generated', } = options;
80
89
  const configPath = path.join(directory, CONFIG_FILENAME);
81
90
  // Check if config already exists
82
91
  if (fs.existsSync(configPath) && !force) {
@@ -86,9 +95,7 @@ async function initCommand(options = {}) {
86
95
  };
87
96
  }
88
97
  // Generate config content
89
- const content = CONFIG_TEMPLATE
90
- .replace('{{ENDPOINT}}', endpoint || 'http://localhost:5000/graphql')
91
- .replace('{{OUTPUT}}', output);
98
+ const content = CONFIG_TEMPLATE.replace('{{ENDPOINT}}', endpoint || 'http://localhost:5000/graphql').replace('{{OUTPUT}}', output);
92
99
  try {
93
100
  // Ensure directory exists
94
101
  fs.mkdirSync(directory, { recursive: true });
@@ -134,10 +141,11 @@ function findConfigFile(startDir = process.cwd()) {
134
141
  * tsx or ts-node installed.
135
142
  */
136
143
  async function loadConfigFile(configPath) {
137
- if (!fs.existsSync(configPath)) {
144
+ const resolvedPath = path.resolve(configPath);
145
+ if (!fs.existsSync(resolvedPath)) {
138
146
  return {
139
147
  success: false,
140
- error: `Config file not found: ${configPath}`,
148
+ error: `Config file not found: ${resolvedPath}`,
141
149
  };
142
150
  }
143
151
  try {
@@ -148,19 +156,31 @@ async function loadConfigFile(configPath) {
148
156
  debug: process.env.JITI_DEBUG === '1',
149
157
  });
150
158
  // jiti.import() with { default: true } returns mod?.default ?? mod
151
- const config = await jiti.import(configPath, { default: true });
159
+ const config = await jiti.import(resolvedPath, { default: true });
152
160
  if (!config || typeof config !== 'object') {
153
161
  return {
154
162
  success: false,
155
163
  error: 'Config file must export a configuration object',
156
164
  };
157
165
  }
158
- if (!('endpoint' in config)) {
166
+ const hasEndpoint = 'endpoint' in config;
167
+ const hasSchema = 'schema' in config;
168
+ const hasTargets = 'targets' in config;
169
+ if (!hasEndpoint && !hasSchema && !hasTargets) {
159
170
  return {
160
171
  success: false,
161
- error: 'Config file missing required "endpoint" property',
172
+ error: 'Config file must define "endpoint", "schema", or "targets".',
162
173
  };
163
174
  }
175
+ if (hasTargets) {
176
+ const targets = config.targets;
177
+ if (!targets || typeof targets !== 'object' || Array.isArray(targets)) {
178
+ return {
179
+ success: false,
180
+ error: 'Config file "targets" must be an object of named configs.',
181
+ };
182
+ }
183
+ }
164
184
  return {
165
185
  success: true,
166
186
  config,