@constructive-io/graphql-codegen 2.21.0 → 2.22.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 (93) hide show
  1. package/cli/codegen/barrel.d.ts +4 -1
  2. package/cli/codegen/barrel.js +18 -12
  3. package/cli/codegen/client.js +33 -0
  4. package/cli/codegen/custom-mutations.d.ts +4 -0
  5. package/cli/codegen/custom-mutations.js +39 -13
  6. package/cli/codegen/custom-queries.d.ts +4 -0
  7. package/cli/codegen/custom-queries.js +36 -11
  8. package/cli/codegen/gql-ast.js +9 -5
  9. package/cli/codegen/index.js +35 -7
  10. package/cli/codegen/mutations.d.ts +2 -0
  11. package/cli/codegen/mutations.js +87 -23
  12. package/cli/codegen/orm/barrel.js +4 -2
  13. package/cli/codegen/orm/index.js +17 -0
  14. package/cli/codegen/orm/input-types-generator.js +83 -29
  15. package/cli/codegen/orm/model-generator.js +6 -4
  16. package/cli/codegen/queries.js +36 -27
  17. package/cli/codegen/scalars.d.ts +6 -4
  18. package/cli/codegen/scalars.js +17 -9
  19. package/cli/codegen/schema-types-generator.d.ts +26 -0
  20. package/cli/codegen/schema-types-generator.js +365 -0
  21. package/cli/codegen/ts-ast.d.ts +3 -1
  22. package/cli/codegen/ts-ast.js +2 -2
  23. package/cli/codegen/type-resolver.d.ts +52 -6
  24. package/cli/codegen/type-resolver.js +97 -19
  25. package/cli/codegen/types.d.ts +7 -4
  26. package/cli/codegen/types.js +94 -41
  27. package/cli/codegen/utils.d.ts +20 -2
  28. package/cli/codegen/utils.js +32 -7
  29. package/cli/commands/generate-orm.js +5 -5
  30. package/cli/commands/generate.d.ts +4 -1
  31. package/cli/commands/generate.js +27 -8
  32. package/cli/introspect/transform-schema.d.ts +33 -21
  33. package/cli/introspect/transform-schema.js +31 -21
  34. package/esm/cli/codegen/barrel.d.ts +4 -1
  35. package/esm/cli/codegen/barrel.js +18 -12
  36. package/esm/cli/codegen/client.js +33 -0
  37. package/esm/cli/codegen/custom-mutations.d.ts +4 -0
  38. package/esm/cli/codegen/custom-mutations.js +40 -14
  39. package/esm/cli/codegen/custom-queries.d.ts +4 -0
  40. package/esm/cli/codegen/custom-queries.js +37 -12
  41. package/esm/cli/codegen/gql-ast.js +10 -6
  42. package/esm/cli/codegen/index.js +35 -7
  43. package/esm/cli/codegen/mutations.d.ts +2 -0
  44. package/esm/cli/codegen/mutations.js +88 -24
  45. package/esm/cli/codegen/orm/barrel.js +4 -2
  46. package/esm/cli/codegen/orm/index.js +17 -0
  47. package/esm/cli/codegen/orm/input-types-generator.js +83 -29
  48. package/esm/cli/codegen/orm/model-generator.js +7 -5
  49. package/esm/cli/codegen/queries.js +37 -28
  50. package/esm/cli/codegen/scalars.d.ts +6 -4
  51. package/esm/cli/codegen/scalars.js +16 -8
  52. package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
  53. package/esm/cli/codegen/schema-types-generator.js +362 -0
  54. package/esm/cli/codegen/ts-ast.d.ts +3 -1
  55. package/esm/cli/codegen/ts-ast.js +2 -2
  56. package/esm/cli/codegen/type-resolver.d.ts +52 -6
  57. package/esm/cli/codegen/type-resolver.js +97 -20
  58. package/esm/cli/codegen/types.d.ts +7 -4
  59. package/esm/cli/codegen/types.js +95 -41
  60. package/esm/cli/codegen/utils.d.ts +20 -2
  61. package/esm/cli/codegen/utils.js +31 -7
  62. package/esm/cli/commands/generate-orm.js +5 -5
  63. package/esm/cli/commands/generate.d.ts +4 -1
  64. package/esm/cli/commands/generate.js +27 -8
  65. package/esm/cli/introspect/transform-schema.d.ts +33 -21
  66. package/esm/cli/introspect/transform-schema.js +31 -21
  67. package/esm/types/schema.d.ts +2 -0
  68. package/package.json +8 -6
  69. package/types/schema.d.ts +2 -0
  70. package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  71. package/__tests__/codegen/input-types-generator.test.js +0 -635
  72. package/__tests__/codegen/react-query-optional.test.d.ts +0 -1
  73. package/__tests__/codegen/react-query-optional.test.js +0 -292
  74. package/cli/codegen/filters.d.ts +0 -27
  75. package/cli/codegen/filters.js +0 -357
  76. package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  77. package/cli/codegen/orm/input-types-generator.test.js +0 -75
  78. package/cli/codegen/orm/select-types.test.d.ts +0 -11
  79. package/cli/codegen/orm/select-types.test.js +0 -22
  80. package/cli/introspect/transform-schema.test.d.ts +0 -1
  81. package/cli/introspect/transform-schema.test.js +0 -67
  82. package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  83. package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
  84. package/esm/__tests__/codegen/react-query-optional.test.d.ts +0 -1
  85. package/esm/__tests__/codegen/react-query-optional.test.js +0 -290
  86. package/esm/cli/codegen/filters.d.ts +0 -27
  87. package/esm/cli/codegen/filters.js +0 -351
  88. package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  89. package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
  90. package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
  91. package/esm/cli/codegen/orm/select-types.test.js +0 -21
  92. package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
  93. package/esm/cli/introspect/transform-schema.test.js +0 -65
@@ -33,7 +33,7 @@ function generateListQueryHook(table, options = {}) {
33
33
  // Collect all filter types used by this table's fields
34
34
  const filterTypesUsed = new Set();
35
35
  for (const field of scalarFields) {
36
- const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType);
36
+ const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType, field.type.isArray);
37
37
  if (filterType) {
38
38
  filterTypesUsed.add(filterType);
39
39
  }
@@ -71,12 +71,14 @@ function generateListQueryHook(table, options = {}) {
71
71
  // Generate filter interface
72
72
  const fieldFilters = scalarFields
73
73
  .map((field) => {
74
- const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType);
74
+ const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType, field.type.isArray);
75
75
  return filterType ? { fieldName: field.name, filterType } : null;
76
76
  })
77
77
  .filter((f) => f !== null);
78
- sourceFile.addInterface((0, ts_ast_1.createFilterInterface)(filterTypeName, fieldFilters));
78
+ // Note: Not exported to avoid conflicts with schema-types
79
+ sourceFile.addInterface((0, ts_ast_1.createFilterInterface)(filterTypeName, fieldFilters, { isExported: false }));
79
80
  // Generate OrderBy type
81
+ // Note: Not exported to avoid conflicts with schema-types
80
82
  const orderByValues = [
81
83
  ...scalarFields.flatMap((f) => [
82
84
  `${(0, utils_1.toScreamingSnake)(f.name)}_ASC`,
@@ -86,7 +88,7 @@ function generateListQueryHook(table, options = {}) {
86
88
  'PRIMARY_KEY_ASC',
87
89
  'PRIMARY_KEY_DESC',
88
90
  ];
89
- sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(orderByTypeName, (0, ts_ast_1.createUnionType)(orderByValues)));
91
+ sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(orderByTypeName, (0, ts_ast_1.createUnionType)(orderByValues), { isExported: false }));
90
92
  // Variables interface
91
93
  const variablesProps = [
92
94
  { name: 'first', type: 'number', optional: true },
@@ -269,6 +271,13 @@ function generateSingleQueryHook(table, options = {}) {
269
271
  const { typeName, singularName } = (0, utils_1.getTableNames)(table);
270
272
  const hookName = (0, utils_1.getSingleQueryHookName)(table);
271
273
  const queryName = (0, utils_1.getSingleRowQueryName)(table);
274
+ // Get primary key info dynamically from table constraints
275
+ const pkFields = (0, utils_1.getPrimaryKeyInfo)(table);
276
+ // For simplicity, use first PK field (most common case)
277
+ // Composite PKs would need more complex handling
278
+ const pkField = pkFields[0];
279
+ const pkName = pkField.name;
280
+ const pkTsType = pkField.tsType;
272
281
  // Generate GraphQL document via AST
273
282
  const queryAST = (0, gql_ast_1.buildSingleQueryAST)({ table });
274
283
  const queryDocument = (0, gql_ast_1.printGraphQL)(queryAST);
@@ -308,9 +317,9 @@ function generateSingleQueryHook(table, options = {}) {
308
317
  sourceFile.addStatements('\n// ============================================================================');
309
318
  sourceFile.addStatements('// Types');
310
319
  sourceFile.addStatements('// ============================================================================\n');
311
- // Variables interface
320
+ // Variables interface - use dynamic PK field name and type
312
321
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`, [
313
- { name: 'id', type: 'string' },
322
+ { name: pkName, type: pkTsType },
314
323
  ]));
315
324
  // Result interface
316
325
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(singularName)}QueryResult`, [
@@ -320,20 +329,20 @@ function generateSingleQueryHook(table, options = {}) {
320
329
  sourceFile.addStatements('\n// ============================================================================');
321
330
  sourceFile.addStatements('// Query Key');
322
331
  sourceFile.addStatements('// ============================================================================\n');
323
- // Query key factory
324
- sourceFile.addVariableStatement((0, ts_ast_1.createConst)(`${queryName}QueryKey`, `(id: string) =>
325
- ['${typeName.toLowerCase()}', 'detail', id] as const`));
332
+ // Query key factory - use dynamic PK field name and type
333
+ sourceFile.addVariableStatement((0, ts_ast_1.createConst)(`${queryName}QueryKey`, `(${pkName}: ${pkTsType}) =>
334
+ ['${typeName.toLowerCase()}', 'detail', ${pkName}] as const`));
326
335
  // Add React Query hook section (only if enabled)
327
336
  if (reactQueryEnabled) {
328
337
  sourceFile.addStatements('\n// ============================================================================');
329
338
  sourceFile.addStatements('// Hook');
330
339
  sourceFile.addStatements('// ============================================================================\n');
331
- // Hook function
340
+ // Hook function - use dynamic PK field name and type
332
341
  sourceFile.addFunction({
333
342
  name: hookName,
334
343
  isExported: true,
335
344
  parameters: [
336
- { name: 'id', type: 'string' },
345
+ { name: pkName, type: pkTsType },
337
346
  {
338
347
  name: 'options',
339
348
  type: `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
@@ -341,24 +350,24 @@ function generateSingleQueryHook(table, options = {}) {
341
350
  },
342
351
  ],
343
352
  statements: `return useQuery({
344
- queryKey: ${queryName}QueryKey(id),
353
+ queryKey: ${queryName}QueryKey(${pkName}),
345
354
  queryFn: () => execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
346
355
  ${queryName}QueryDocument,
347
- { id }
356
+ { ${pkName} }
348
357
  ),
349
- enabled: !!id && (options?.enabled !== false),
358
+ enabled: !!${pkName} && (options?.enabled !== false),
350
359
  ...options,
351
360
  });`,
352
361
  docs: [
353
362
  {
354
- description: `Query hook for fetching a single ${typeName} by ID
363
+ description: `Query hook for fetching a single ${typeName} by primary key
355
364
 
356
365
  @example
357
366
  \`\`\`tsx
358
- const { data, isLoading } = ${hookName}('uuid-here');
367
+ const { data, isLoading } = ${hookName}(${pkTsType === 'string' ? "'value-here'" : '123'});
359
368
 
360
369
  if (data?.${queryName}) {
361
- console.log(data.${queryName}.id);
370
+ console.log(data.${queryName}.${pkName});
362
371
  }
363
372
  \`\`\``,
364
373
  },
@@ -369,13 +378,13 @@ if (data?.${queryName}) {
369
378
  sourceFile.addStatements('\n// ============================================================================');
370
379
  sourceFile.addStatements('// Standalone Functions (non-React)');
371
380
  sourceFile.addStatements('// ============================================================================\n');
372
- // Fetch function (standalone, no React)
381
+ // Fetch function (standalone, no React) - use dynamic PK
373
382
  sourceFile.addFunction({
374
383
  name: `fetch${(0, utils_1.ucFirst)(singularName)}Query`,
375
384
  isExported: true,
376
385
  isAsync: true,
377
386
  parameters: [
378
- { name: 'id', type: 'string' },
387
+ { name: pkName, type: pkTsType },
379
388
  {
380
389
  name: 'options',
381
390
  type: 'ExecuteOptions',
@@ -385,21 +394,21 @@ if (data?.${queryName}) {
385
394
  returnType: `Promise<${(0, utils_1.ucFirst)(singularName)}QueryResult>`,
386
395
  statements: `return execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
387
396
  ${queryName}QueryDocument,
388
- { id },
397
+ { ${pkName} },
389
398
  options
390
399
  );`,
391
400
  docs: [
392
401
  {
393
- description: `Fetch a single ${typeName} by ID without React hooks
402
+ description: `Fetch a single ${typeName} by primary key without React hooks
394
403
 
395
404
  @example
396
405
  \`\`\`ts
397
- const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query('uuid-here');
406
+ const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query(${pkTsType === 'string' ? "'value-here'" : '123'});
398
407
  \`\`\``,
399
408
  },
400
409
  ],
401
410
  });
402
- // Prefetch function (for SSR/QueryClient) - only if React Query is enabled
411
+ // Prefetch function (for SSR/QueryClient) - only if React Query is enabled, use dynamic PK
403
412
  if (reactQueryEnabled) {
404
413
  sourceFile.addFunction({
405
414
  name: `prefetch${(0, utils_1.ucFirst)(singularName)}Query`,
@@ -407,7 +416,7 @@ const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query('uuid-here');
407
416
  isAsync: true,
408
417
  parameters: [
409
418
  { name: 'queryClient', type: 'QueryClient' },
410
- { name: 'id', type: 'string' },
419
+ { name: pkName, type: pkTsType },
411
420
  {
412
421
  name: 'options',
413
422
  type: 'ExecuteOptions',
@@ -416,10 +425,10 @@ const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query('uuid-here');
416
425
  ],
417
426
  returnType: 'Promise<void>',
418
427
  statements: `await queryClient.prefetchQuery({
419
- queryKey: ${queryName}QueryKey(id),
428
+ queryKey: ${queryName}QueryKey(${pkName}),
420
429
  queryFn: () => execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
421
430
  ${queryName}QueryDocument,
422
- { id },
431
+ { ${pkName} },
423
432
  options
424
433
  ),
425
434
  });`,
@@ -429,7 +438,7 @@ const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query('uuid-here');
429
438
 
430
439
  @example
431
440
  \`\`\`ts
432
- await prefetch${(0, utils_1.ucFirst)(singularName)}Query(queryClient, 'uuid-here');
441
+ await prefetch${(0, utils_1.ucFirst)(singularName)}Query(queryClient, ${pkTsType === 'string' ? "'value-here'" : '123'});
433
442
  \`\`\``,
434
443
  },
435
444
  ],
@@ -4,9 +4,11 @@
4
4
  export declare const SCALAR_TS_MAP: Record<string, string>;
5
5
  export declare const SCALAR_FILTER_MAP: Record<string, string>;
6
6
  export declare const SCALAR_NAMES: Set<string>;
7
- export interface ScalarToTsOptions {
7
+ /** All base filter type names - skip these in schema-types.ts to avoid duplicates */
8
+ export declare const BASE_FILTER_TYPE_NAMES: Set<string>;
9
+ export declare function scalarToTsType(scalarName: string, options?: {
8
10
  unknownScalar?: 'unknown' | 'name';
9
11
  overrides?: Record<string, string>;
10
- }
11
- export declare function scalarToTsType(scalarName: string, options?: ScalarToTsOptions): string;
12
- export declare function scalarToFilterType(scalarName: string): string | null;
12
+ }): string;
13
+ /** Get the filter type for a scalar (handles both scalar and array types) */
14
+ export declare function scalarToFilterType(scalarName: string, isArray?: boolean): string | null;
@@ -3,7 +3,7 @@
3
3
  * Shared scalar mappings for code generation
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SCALAR_NAMES = exports.SCALAR_FILTER_MAP = exports.SCALAR_TS_MAP = void 0;
6
+ exports.BASE_FILTER_TYPE_NAMES = exports.SCALAR_NAMES = exports.SCALAR_FILTER_MAP = exports.SCALAR_TS_MAP = void 0;
7
7
  exports.scalarToTsType = scalarToTsType;
8
8
  exports.scalarToFilterType = scalarToFilterType;
9
9
  exports.SCALAR_TS_MAP = {
@@ -37,6 +37,8 @@ exports.SCALAR_TS_MAP = {
37
37
  MacAddr: 'string',
38
38
  TsVector: 'string',
39
39
  TsQuery: 'string',
40
+ // File upload
41
+ Upload: 'File',
40
42
  };
41
43
  exports.SCALAR_FILTER_MAP = {
42
44
  String: 'StringFilter',
@@ -57,15 +59,21 @@ exports.SCALAR_FILTER_MAP = {
57
59
  Interval: 'StringFilter',
58
60
  };
59
61
  exports.SCALAR_NAMES = new Set(Object.keys(exports.SCALAR_TS_MAP));
62
+ /** Scalars that have list filter variants (e.g., StringListFilter) */
63
+ const LIST_FILTER_SCALARS = new Set(['String', 'Int', 'UUID']);
64
+ /** All base filter type names - skip these in schema-types.ts to avoid duplicates */
65
+ exports.BASE_FILTER_TYPE_NAMES = new Set([
66
+ ...new Set(Object.values(exports.SCALAR_FILTER_MAP)),
67
+ ...Array.from(LIST_FILTER_SCALARS).map((s) => `${s}ListFilter`),
68
+ ]);
60
69
  function scalarToTsType(scalarName, options = {}) {
61
- const override = options.overrides?.[scalarName];
62
- if (override)
63
- return override;
64
- const mapped = exports.SCALAR_TS_MAP[scalarName];
65
- if (mapped)
66
- return mapped;
67
- return options.unknownScalar === 'unknown' ? 'unknown' : scalarName;
70
+ return options.overrides?.[scalarName] ?? exports.SCALAR_TS_MAP[scalarName] ?? (options.unknownScalar === 'unknown' ? 'unknown' : scalarName);
68
71
  }
69
- function scalarToFilterType(scalarName) {
72
+ /** Get the filter type for a scalar (handles both scalar and array types) */
73
+ function scalarToFilterType(scalarName, isArray = false) {
74
+ const baseName = scalarName === 'ID' ? 'UUID' : scalarName;
75
+ if (isArray) {
76
+ return LIST_FILTER_SCALARS.has(baseName) ? `${baseName}ListFilter` : null;
77
+ }
70
78
  return exports.SCALAR_FILTER_MAP[scalarName] ?? null;
71
79
  }
@@ -0,0 +1,26 @@
1
+ import type { TypeRegistry } from '../../types/schema';
2
+ export interface GeneratedSchemaTypesFile {
3
+ fileName: string;
4
+ content: string;
5
+ /** List of enum type names that were generated */
6
+ generatedEnums: string[];
7
+ /** List of table entity types that are referenced */
8
+ referencedTableTypes: string[];
9
+ }
10
+ export interface GenerateSchemaTypesOptions {
11
+ /** The TypeRegistry containing all GraphQL types */
12
+ typeRegistry: TypeRegistry;
13
+ /** Type names that already exist in types.ts (table entity types) */
14
+ tableTypeNames: Set<string>;
15
+ }
16
+ export interface PayloadTypesResult {
17
+ generatedTypes: Set<string>;
18
+ referencedTableTypes: Set<string>;
19
+ }
20
+ /**
21
+ * Generate comprehensive schema-types.ts file using ts-morph AST
22
+ *
23
+ * This generates all Input/Payload/Enum types from the TypeRegistry
24
+ * that are needed by custom mutation/query hooks.
25
+ */
26
+ export declare function generateSchemaTypesFile(options: GenerateSchemaTypesOptions): GeneratedSchemaTypesFile;
@@ -0,0 +1,365 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateSchemaTypesFile = generateSchemaTypesFile;
4
+ const ts_ast_1 = require("./ts-ast");
5
+ const type_resolver_1 = require("./type-resolver");
6
+ const scalars_1 = require("./scalars");
7
+ // ============================================================================
8
+ // Constants
9
+ // ============================================================================
10
+ /**
11
+ * Types that should not be generated (scalars, built-ins, types generated elsewhere)
12
+ */
13
+ const SKIP_TYPES = new Set([
14
+ ...scalars_1.SCALAR_NAMES,
15
+ // GraphQL built-ins
16
+ 'Query',
17
+ 'Mutation',
18
+ 'Subscription',
19
+ '__Schema',
20
+ '__Type',
21
+ '__Field',
22
+ '__InputValue',
23
+ '__EnumValue',
24
+ '__Directive',
25
+ // Note: PageInfo and Cursor are NOT skipped - they're needed by Connection types
26
+ // Base filter types (generated in types.ts via filters.ts)
27
+ ...scalars_1.BASE_FILTER_TYPE_NAMES,
28
+ ]);
29
+ /**
30
+ * Type name patterns to skip (regex patterns)
31
+ *
32
+ * Note: We intentionally DO NOT skip Connection, Edge, Filter, or Patch types
33
+ * because they may be referenced by custom operations or payload types.
34
+ * Only skip Condition and OrderBy which are typically not needed.
35
+ */
36
+ const SKIP_TYPE_PATTERNS = [
37
+ /Condition$/, // e.g., UserCondition (filter conditions are separate)
38
+ /OrderBy$/, // e.g., UsersOrderBy (ordering is handled separately)
39
+ ];
40
+ // ============================================================================
41
+ // Type Conversion Utilities
42
+ // ============================================================================
43
+ /**
44
+ * Convert a CleanTypeRef to TypeScript type string
45
+ */
46
+ function typeRefToTs(typeRef) {
47
+ if (typeRef.kind === 'NON_NULL') {
48
+ if (typeRef.ofType) {
49
+ return typeRefToTs(typeRef.ofType);
50
+ }
51
+ return typeRef.name ?? 'unknown';
52
+ }
53
+ if (typeRef.kind === 'LIST') {
54
+ if (typeRef.ofType) {
55
+ return `${typeRefToTs(typeRef.ofType)}[]`;
56
+ }
57
+ return 'unknown[]';
58
+ }
59
+ // Scalar or named type
60
+ const name = typeRef.name ?? 'unknown';
61
+ return (0, scalars_1.scalarToTsType)(name, { unknownScalar: 'name' });
62
+ }
63
+ /**
64
+ * Check if a type is required (NON_NULL)
65
+ */
66
+ function isRequired(typeRef) {
67
+ return typeRef.kind === 'NON_NULL';
68
+ }
69
+ /**
70
+ * Check if a type should be skipped
71
+ */
72
+ function shouldSkipType(typeName, tableTypeNames) {
73
+ // Skip scalars and built-ins
74
+ if (SKIP_TYPES.has(typeName))
75
+ return true;
76
+ // Skip table entity types (already in types.ts)
77
+ if (tableTypeNames.has(typeName))
78
+ return true;
79
+ // Skip types matching patterns
80
+ for (const pattern of SKIP_TYPE_PATTERNS) {
81
+ if (pattern.test(typeName))
82
+ return true;
83
+ }
84
+ // Skip table-specific types that would conflict with inline types in table-based hooks.
85
+ // Note: Patch and CreateInput are now NOT exported from hooks (isExported: false),
86
+ // so we only skip Filter types here.
87
+ // The Filter and OrderBy types are generated inline (non-exported) by table query hooks,
88
+ // but schema-types should still generate them for custom operations that need them.
89
+ // Actually, we don't skip any table-based types now since hooks don't export them.
90
+ return false;
91
+ }
92
+ // ============================================================================
93
+ // ENUM Types Generator
94
+ // ============================================================================
95
+ /**
96
+ * Add ENUM types to source file
97
+ */
98
+ function addEnumTypes(sourceFile, typeRegistry, tableTypeNames) {
99
+ const generatedTypes = new Set();
100
+ (0, ts_ast_1.addSectionComment)(sourceFile, 'Enum Types');
101
+ for (const [typeName, typeInfo] of typeRegistry) {
102
+ if (typeInfo.kind !== 'ENUM')
103
+ continue;
104
+ if (shouldSkipType(typeName, tableTypeNames))
105
+ continue;
106
+ if (!typeInfo.enumValues || typeInfo.enumValues.length === 0)
107
+ continue;
108
+ const values = typeInfo.enumValues.map((v) => `'${v}'`).join(' | ');
109
+ sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(typeName, values));
110
+ generatedTypes.add(typeName);
111
+ }
112
+ return generatedTypes;
113
+ }
114
+ // ============================================================================
115
+ // INPUT_OBJECT Types Generator
116
+ // ============================================================================
117
+ /**
118
+ * Add INPUT_OBJECT types to source file
119
+ * Uses iteration to handle nested input types
120
+ */
121
+ function addInputObjectTypes(sourceFile, typeRegistry, tableTypeNames, alreadyGenerated) {
122
+ const generatedTypes = new Set(alreadyGenerated);
123
+ const typesToGenerate = new Set();
124
+ // Collect all INPUT_OBJECT types
125
+ for (const [typeName, typeInfo] of typeRegistry) {
126
+ if (typeInfo.kind !== 'INPUT_OBJECT')
127
+ continue;
128
+ if (shouldSkipType(typeName, tableTypeNames))
129
+ continue;
130
+ if (generatedTypes.has(typeName))
131
+ continue;
132
+ typesToGenerate.add(typeName);
133
+ }
134
+ if (typesToGenerate.size === 0)
135
+ return generatedTypes;
136
+ (0, ts_ast_1.addSectionComment)(sourceFile, 'Input Object Types');
137
+ // Process all types - no artificial limit
138
+ while (typesToGenerate.size > 0) {
139
+ const typeNameResult = typesToGenerate.values().next();
140
+ if (typeNameResult.done)
141
+ break;
142
+ const typeName = typeNameResult.value;
143
+ typesToGenerate.delete(typeName);
144
+ if (generatedTypes.has(typeName))
145
+ continue;
146
+ const typeInfo = typeRegistry.get(typeName);
147
+ if (!typeInfo || typeInfo.kind !== 'INPUT_OBJECT')
148
+ continue;
149
+ generatedTypes.add(typeName);
150
+ if (typeInfo.inputFields && typeInfo.inputFields.length > 0) {
151
+ const properties = [];
152
+ for (const field of typeInfo.inputFields) {
153
+ const optional = !isRequired(field.type);
154
+ const tsType = typeRefToTs(field.type);
155
+ properties.push({
156
+ name: field.name,
157
+ type: tsType,
158
+ optional,
159
+ docs: field.description ? [field.description] : undefined,
160
+ });
161
+ // Follow nested Input types
162
+ const baseType = (0, type_resolver_1.getTypeBaseName)(field.type);
163
+ if (baseType &&
164
+ !generatedTypes.has(baseType) &&
165
+ !shouldSkipType(baseType, tableTypeNames)) {
166
+ const nestedType = typeRegistry.get(baseType);
167
+ if (nestedType?.kind === 'INPUT_OBJECT') {
168
+ typesToGenerate.add(baseType);
169
+ }
170
+ }
171
+ }
172
+ sourceFile.addInterface((0, ts_ast_1.createInterface)(typeName, properties));
173
+ }
174
+ else {
175
+ // Empty input object
176
+ sourceFile.addInterface((0, ts_ast_1.createInterface)(typeName, []));
177
+ }
178
+ }
179
+ return generatedTypes;
180
+ }
181
+ // ============================================================================
182
+ // UNION Types Generator
183
+ // ============================================================================
184
+ /**
185
+ * Add UNION types to source file
186
+ */
187
+ function addUnionTypes(sourceFile, typeRegistry, tableTypeNames, alreadyGenerated) {
188
+ const generatedTypes = new Set(alreadyGenerated);
189
+ const unionTypesToGenerate = new Set();
190
+ // Collect all UNION types
191
+ for (const [typeName, typeInfo] of typeRegistry) {
192
+ if (typeInfo.kind !== 'UNION')
193
+ continue;
194
+ if (shouldSkipType(typeName, tableTypeNames))
195
+ continue;
196
+ if (generatedTypes.has(typeName))
197
+ continue;
198
+ unionTypesToGenerate.add(typeName);
199
+ }
200
+ if (unionTypesToGenerate.size === 0)
201
+ return generatedTypes;
202
+ (0, ts_ast_1.addSectionComment)(sourceFile, 'Union Types');
203
+ for (const typeName of unionTypesToGenerate) {
204
+ const typeInfo = typeRegistry.get(typeName);
205
+ if (!typeInfo || typeInfo.kind !== 'UNION')
206
+ continue;
207
+ if (!typeInfo.possibleTypes || typeInfo.possibleTypes.length === 0)
208
+ continue;
209
+ // Generate union type as TypeScript union
210
+ const unionMembers = typeInfo.possibleTypes.join(' | ');
211
+ sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(typeName, unionMembers));
212
+ generatedTypes.add(typeName);
213
+ }
214
+ return generatedTypes;
215
+ }
216
+ /**
217
+ * Collect return types from Query and Mutation root types
218
+ * This dynamically discovers what OBJECT types need to be generated
219
+ * based on actual schema structure, not pattern matching
220
+ */
221
+ function collectReturnTypesFromRootTypes(typeRegistry, tableTypeNames) {
222
+ const returnTypes = new Set();
223
+ // Get Query and Mutation root types
224
+ const queryType = typeRegistry.get('Query');
225
+ const mutationType = typeRegistry.get('Mutation');
226
+ const processFields = (fields) => {
227
+ if (!fields)
228
+ return;
229
+ for (const field of fields) {
230
+ const baseType = (0, type_resolver_1.getTypeBaseName)(field.type);
231
+ if (baseType && !shouldSkipType(baseType, tableTypeNames)) {
232
+ const typeInfo = typeRegistry.get(baseType);
233
+ if (typeInfo?.kind === 'OBJECT') {
234
+ returnTypes.add(baseType);
235
+ }
236
+ }
237
+ }
238
+ };
239
+ if (queryType?.fields)
240
+ processFields(queryType.fields);
241
+ if (mutationType?.fields)
242
+ processFields(mutationType.fields);
243
+ return returnTypes;
244
+ }
245
+ /**
246
+ * Add Payload OBJECT types to source file
247
+ * These are return types from mutations (e.g., LoginPayload, BootstrapUserPayload)
248
+ *
249
+ * Also tracks which table entity types are referenced so they can be imported.
250
+ *
251
+ * Uses dynamic type discovery from Query/Mutation return types instead of pattern matching.
252
+ */
253
+ function addPayloadObjectTypes(sourceFile, typeRegistry, tableTypeNames, alreadyGenerated) {
254
+ const generatedTypes = new Set(alreadyGenerated);
255
+ const referencedTableTypes = new Set();
256
+ // Dynamically collect return types from Query and Mutation
257
+ const typesToGenerate = collectReturnTypesFromRootTypes(typeRegistry, tableTypeNames);
258
+ // Filter out already generated types
259
+ for (const typeName of Array.from(typesToGenerate)) {
260
+ if (generatedTypes.has(typeName)) {
261
+ typesToGenerate.delete(typeName);
262
+ }
263
+ }
264
+ if (typesToGenerate.size === 0) {
265
+ return { generatedTypes, referencedTableTypes };
266
+ }
267
+ (0, ts_ast_1.addSectionComment)(sourceFile, 'Payload/Return Object Types');
268
+ // Process all types - no artificial limit
269
+ while (typesToGenerate.size > 0) {
270
+ const typeNameResult = typesToGenerate.values().next();
271
+ if (typeNameResult.done)
272
+ break;
273
+ const typeName = typeNameResult.value;
274
+ typesToGenerate.delete(typeName);
275
+ if (generatedTypes.has(typeName))
276
+ continue;
277
+ const typeInfo = typeRegistry.get(typeName);
278
+ if (!typeInfo || typeInfo.kind !== 'OBJECT')
279
+ continue;
280
+ generatedTypes.add(typeName);
281
+ if (typeInfo.fields && typeInfo.fields.length > 0) {
282
+ const properties = [];
283
+ for (const field of typeInfo.fields) {
284
+ const baseType = (0, type_resolver_1.getTypeBaseName)(field.type);
285
+ // Skip Query and Mutation fields
286
+ if (baseType === 'Query' || baseType === 'Mutation')
287
+ continue;
288
+ const tsType = typeRefToTs(field.type);
289
+ const isNullable = !isRequired(field.type);
290
+ properties.push({
291
+ name: field.name,
292
+ type: isNullable ? `${tsType} | null` : tsType,
293
+ optional: isNullable,
294
+ docs: field.description ? [field.description] : undefined,
295
+ });
296
+ // Track table entity types that are referenced
297
+ if (baseType && tableTypeNames.has(baseType)) {
298
+ referencedTableTypes.add(baseType);
299
+ }
300
+ // Follow nested OBJECT types that aren't table types
301
+ if (baseType &&
302
+ !generatedTypes.has(baseType) &&
303
+ !shouldSkipType(baseType, tableTypeNames)) {
304
+ const nestedType = typeRegistry.get(baseType);
305
+ if (nestedType?.kind === 'OBJECT') {
306
+ typesToGenerate.add(baseType);
307
+ }
308
+ }
309
+ }
310
+ sourceFile.addInterface((0, ts_ast_1.createInterface)(typeName, properties));
311
+ }
312
+ else {
313
+ // Empty payload object
314
+ sourceFile.addInterface((0, ts_ast_1.createInterface)(typeName, []));
315
+ }
316
+ }
317
+ return { generatedTypes, referencedTableTypes };
318
+ }
319
+ // ============================================================================
320
+ // Main Generator
321
+ // ============================================================================
322
+ /**
323
+ * Generate comprehensive schema-types.ts file using ts-morph AST
324
+ *
325
+ * This generates all Input/Payload/Enum types from the TypeRegistry
326
+ * that are needed by custom mutation/query hooks.
327
+ */
328
+ function generateSchemaTypesFile(options) {
329
+ const { typeRegistry, tableTypeNames } = options;
330
+ const project = (0, ts_ast_1.createProject)();
331
+ const sourceFile = (0, ts_ast_1.createSourceFile)(project, 'schema-types.ts');
332
+ // Add file header
333
+ sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)('GraphQL schema types for custom operations') + '\n');
334
+ // Track all generated types
335
+ let generatedTypes = new Set();
336
+ // 1. Generate ENUM types
337
+ const enumTypes = addEnumTypes(sourceFile, typeRegistry, tableTypeNames);
338
+ generatedTypes = new Set([...generatedTypes, ...enumTypes]);
339
+ // 2. Generate UNION types
340
+ const unionTypes = addUnionTypes(sourceFile, typeRegistry, tableTypeNames, generatedTypes);
341
+ generatedTypes = new Set([...generatedTypes, ...unionTypes]);
342
+ // 3. Generate INPUT_OBJECT types
343
+ const inputTypes = addInputObjectTypes(sourceFile, typeRegistry, tableTypeNames, generatedTypes);
344
+ generatedTypes = new Set([...generatedTypes, ...inputTypes]);
345
+ // 4. Generate Payload OBJECT types
346
+ const payloadResult = addPayloadObjectTypes(sourceFile, typeRegistry, tableTypeNames, generatedTypes);
347
+ // 5. Add imports from types.ts (table entity types + base filter types)
348
+ const referencedTableTypes = Array.from(payloadResult.referencedTableTypes).sort();
349
+ // Always import base filter types since generated Filter interfaces reference them
350
+ const baseFilterImports = Array.from(scalars_1.BASE_FILTER_TYPE_NAMES).sort();
351
+ const allTypesImports = [...referencedTableTypes, ...baseFilterImports];
352
+ if (allTypesImports.length > 0) {
353
+ // Insert import after the file header comment
354
+ const importStatement = `import type { ${allTypesImports.join(', ')} } from './types';\n\n`;
355
+ // Find position after header (after first */ + newlines)
356
+ const headerEndIndex = sourceFile.getFullText().indexOf('*/') + 3;
357
+ sourceFile.insertText(headerEndIndex, '\n' + importStatement);
358
+ }
359
+ return {
360
+ fileName: 'schema-types.ts',
361
+ content: (0, ts_ast_1.getMinimalFormattedOutput)(sourceFile),
362
+ generatedEnums: Array.from(enumTypes).sort(),
363
+ referencedTableTypes,
364
+ };
365
+ }
@@ -66,7 +66,9 @@ export declare function createInterface(name: string, properties: InterfacePrope
66
66
  export declare function createFilterInterface(name: string, fieldFilters: Array<{
67
67
  fieldName: string;
68
68
  filterType: string;
69
- }>): InterfaceDeclarationStructure;
69
+ }>, options?: {
70
+ isExported?: boolean;
71
+ }): InterfaceDeclarationStructure;
70
72
  /**
71
73
  * Create type alias declaration structure
72
74
  */
@@ -163,7 +163,7 @@ function createInterface(name, properties, options) {
163
163
  /**
164
164
  * Create filter interface with standard PostGraphile operators
165
165
  */
166
- function createFilterInterface(name, fieldFilters) {
166
+ function createFilterInterface(name, fieldFilters, options) {
167
167
  const properties = [
168
168
  ...fieldFilters.map((f) => ({
169
169
  name: f.fieldName,
@@ -174,7 +174,7 @@ function createFilterInterface(name, fieldFilters) {
174
174
  { name: 'or', type: `${name}[]`, optional: true, docs: ['Logical OR'] },
175
175
  { name: 'not', type: name, optional: true, docs: ['Logical NOT'] },
176
176
  ];
177
- return createInterface(name, properties);
177
+ return createInterface(name, properties, { isExported: options?.isExported ?? true });
178
178
  }
179
179
  // ============================================================================
180
180
  // Type alias builders