@constructive-io/graphql-codegen 3.3.1 → 4.0.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 (67) hide show
  1. package/README.md +0 -4
  2. package/core/ast.js +6 -5
  3. package/core/codegen/custom-mutations.js +22 -22
  4. package/core/codegen/custom-queries.js +24 -17
  5. package/core/codegen/hooks-ast.d.ts +1 -1
  6. package/core/codegen/hooks-ast.js +22 -5
  7. package/core/codegen/index.d.ts +1 -1
  8. package/core/codegen/mutations.js +16 -12
  9. package/core/codegen/orm/custom-ops-generator.js +37 -3
  10. package/core/codegen/orm/input-types-generator.js +161 -89
  11. package/core/codegen/orm/model-generator.js +72 -73
  12. package/core/codegen/orm/select-types.d.ts +27 -17
  13. package/core/codegen/queries.js +37 -25
  14. package/core/codegen/schema-types-generator.js +21 -0
  15. package/core/codegen/templates/hooks-selection.ts +12 -0
  16. package/core/codegen/templates/query-builder.ts +103 -59
  17. package/core/codegen/templates/select-types.ts +59 -33
  18. package/core/codegen/types.js +26 -0
  19. package/core/codegen/utils.d.ts +1 -1
  20. package/core/codegen/utils.js +1 -1
  21. package/core/custom-ast.js +9 -8
  22. package/core/database/index.js +2 -3
  23. package/core/index.d.ts +2 -0
  24. package/core/index.js +2 -0
  25. package/core/introspect/infer-tables.js +144 -58
  26. package/core/introspect/transform-schema.d.ts +1 -1
  27. package/core/introspect/transform-schema.js +3 -1
  28. package/esm/core/ast.js +6 -5
  29. package/esm/core/codegen/custom-mutations.js +23 -23
  30. package/esm/core/codegen/custom-queries.js +25 -18
  31. package/esm/core/codegen/hooks-ast.d.ts +1 -1
  32. package/esm/core/codegen/hooks-ast.js +22 -5
  33. package/esm/core/codegen/index.d.ts +1 -1
  34. package/esm/core/codegen/mutations.js +16 -12
  35. package/esm/core/codegen/orm/custom-ops-generator.js +38 -4
  36. package/esm/core/codegen/orm/input-types-generator.js +163 -91
  37. package/esm/core/codegen/orm/model-generator.js +73 -74
  38. package/esm/core/codegen/orm/select-types.d.ts +27 -17
  39. package/esm/core/codegen/queries.js +37 -25
  40. package/esm/core/codegen/schema-types-generator.js +21 -0
  41. package/esm/core/codegen/types.js +26 -0
  42. package/esm/core/codegen/utils.d.ts +1 -1
  43. package/esm/core/codegen/utils.js +1 -1
  44. package/esm/core/custom-ast.js +9 -8
  45. package/esm/core/database/index.js +2 -3
  46. package/esm/core/index.d.ts +2 -0
  47. package/esm/core/index.js +2 -0
  48. package/esm/core/introspect/infer-tables.js +144 -58
  49. package/esm/core/introspect/transform-schema.d.ts +1 -1
  50. package/esm/core/introspect/transform-schema.js +3 -1
  51. package/esm/generators/field-selector.js +1 -0
  52. package/esm/generators/index.d.ts +3 -0
  53. package/esm/generators/index.js +3 -0
  54. package/esm/generators/mutations.js +4 -4
  55. package/esm/generators/select.js +4 -4
  56. package/esm/index.d.ts +1 -1
  57. package/esm/index.js +1 -1
  58. package/esm/types/schema.d.ts +5 -3
  59. package/generators/field-selector.js +1 -0
  60. package/generators/index.d.ts +3 -0
  61. package/generators/index.js +3 -0
  62. package/generators/mutations.js +3 -3
  63. package/generators/select.js +3 -3
  64. package/index.d.ts +1 -1
  65. package/index.js +1 -1
  66. package/package.json +11 -11
  67. package/types/schema.d.ts +5 -3
@@ -13,9 +13,9 @@
13
13
  import * as t from '@babel/types';
14
14
  import { pluralize } from 'inflekt';
15
15
  import { addLineComment, generateCode } from '../babel-ast';
16
- import { scalarToFilterType, scalarToTsType } from '../scalars';
16
+ import { SCALAR_NAMES, scalarToFilterType, scalarToTsType } from '../scalars';
17
17
  import { getTypeBaseName } from '../type-resolver';
18
- import { getConditionTypeName, getFilterTypeName, getGeneratedFileHeader, getOrderByTypeName, getPrimaryKeyInfo, getTableNames, isRelationField, } from '../utils';
18
+ import { getCreateInputTypeName, getConditionTypeName, getFilterTypeName, getGeneratedFileHeader, getOrderByTypeName, getPrimaryKeyInfo, getTableNames, isRelationField, lcFirst, } from '../utils';
19
19
  // ============================================================================
20
20
  // Constants
21
21
  // ============================================================================
@@ -327,6 +327,41 @@ function generateEnumTypes(typeRegistry, enumTypeNames) {
327
327
  }
328
328
  return statements;
329
329
  }
330
+ function collectCustomScalarTypes(typeRegistry, tables, excludedTypeNames) {
331
+ const customScalarTypes = new Set();
332
+ const tableTypeNames = new Set(tables.map((table) => table.name));
333
+ for (const [typeName, typeInfo] of typeRegistry) {
334
+ if (typeInfo.kind !== 'SCALAR')
335
+ continue;
336
+ if (SCALAR_NAMES.has(typeName))
337
+ continue;
338
+ if (excludedTypeNames.has(typeName))
339
+ continue;
340
+ customScalarTypes.add(typeName);
341
+ }
342
+ for (const table of tables) {
343
+ for (const field of table.fields) {
344
+ const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
345
+ if (SCALAR_NAMES.has(fieldType))
346
+ continue;
347
+ if (excludedTypeNames.has(fieldType))
348
+ continue;
349
+ if (tableTypeNames.has(fieldType))
350
+ continue;
351
+ if (isLikelyEnumType(fieldType, typeRegistry))
352
+ continue;
353
+ customScalarTypes.add(fieldType);
354
+ }
355
+ }
356
+ return Array.from(customScalarTypes).sort();
357
+ }
358
+ function generateCustomScalarTypes(customScalarTypes) {
359
+ if (customScalarTypes.length === 0)
360
+ return [];
361
+ const statements = customScalarTypes.map((scalarType) => createExportedTypeAlias(scalarType, 'unknown'));
362
+ addSectionComment(statements, 'Custom Scalar Types');
363
+ return statements;
364
+ }
330
365
  // ============================================================================
331
366
  // Entity Types Generator (AST-based)
332
367
  // ============================================================================
@@ -508,15 +543,12 @@ function buildSelectTypeLiteral(table, tableByName) {
508
543
  for (const relation of table.relations.belongsTo) {
509
544
  if (relation.fieldName) {
510
545
  const relatedTypeName = getRelatedTypeName(relation.referencesTable, tableByName);
511
- const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
512
- t.tsBooleanKeyword(),
513
- t.tsTypeLiteral([
514
- (() => {
515
- const selectProp = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
516
- selectProp.optional = true;
517
- return selectProp;
518
- })(),
519
- ]),
546
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsTypeLiteral([
547
+ (() => {
548
+ const selectProp = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
549
+ selectProp.optional = false;
550
+ return selectProp;
551
+ })(),
520
552
  ])));
521
553
  prop.optional = true;
522
554
  members.push(prop);
@@ -528,30 +560,27 @@ function buildSelectTypeLiteral(table, tableByName) {
528
560
  const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
529
561
  const filterName = getRelatedFilterName(relation.referencedByTable, tableByName);
530
562
  const orderByName = getRelatedOrderByName(relation.referencedByTable, tableByName);
531
- const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
532
- t.tsBooleanKeyword(),
533
- t.tsTypeLiteral([
534
- (() => {
535
- const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
536
- p.optional = true;
537
- return p;
538
- })(),
539
- (() => {
540
- const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
541
- p.optional = true;
542
- return p;
543
- })(),
544
- (() => {
545
- const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterName))));
546
- p.optional = true;
547
- return p;
548
- })(),
549
- (() => {
550
- const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByName)))));
551
- p.optional = true;
552
- return p;
553
- })(),
554
- ]),
563
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsTypeLiteral([
564
+ (() => {
565
+ const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
566
+ p.optional = false;
567
+ return p;
568
+ })(),
569
+ (() => {
570
+ const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
571
+ p.optional = true;
572
+ return p;
573
+ })(),
574
+ (() => {
575
+ const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterName))));
576
+ p.optional = true;
577
+ return p;
578
+ })(),
579
+ (() => {
580
+ const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByName)))));
581
+ p.optional = true;
582
+ return p;
583
+ })(),
555
584
  ])));
556
585
  prop.optional = true;
557
586
  members.push(prop);
@@ -563,30 +592,27 @@ function buildSelectTypeLiteral(table, tableByName) {
563
592
  const relatedTypeName = getRelatedTypeName(relation.rightTable, tableByName);
564
593
  const filterName = getRelatedFilterName(relation.rightTable, tableByName);
565
594
  const orderByName = getRelatedOrderByName(relation.rightTable, tableByName);
566
- const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
567
- t.tsBooleanKeyword(),
568
- t.tsTypeLiteral([
569
- (() => {
570
- const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
571
- p.optional = true;
572
- return p;
573
- })(),
574
- (() => {
575
- const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
576
- p.optional = true;
577
- return p;
578
- })(),
579
- (() => {
580
- const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterName))));
581
- p.optional = true;
582
- return p;
583
- })(),
584
- (() => {
585
- const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByName)))));
586
- p.optional = true;
587
- return p;
588
- })(),
589
- ]),
595
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsTypeLiteral([
596
+ (() => {
597
+ const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
598
+ p.optional = false;
599
+ return p;
600
+ })(),
601
+ (() => {
602
+ const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
603
+ p.optional = true;
604
+ return p;
605
+ })(),
606
+ (() => {
607
+ const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterName))));
608
+ p.optional = true;
609
+ return p;
610
+ })(),
611
+ (() => {
612
+ const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByName)))));
613
+ p.optional = true;
614
+ return p;
615
+ })(),
590
616
  ])));
591
617
  prop.optional = true;
592
618
  members.push(prop);
@@ -596,15 +622,12 @@ function buildSelectTypeLiteral(table, tableByName) {
596
622
  for (const relation of table.relations.hasOne) {
597
623
  if (relation.fieldName) {
598
624
  const relatedTypeName = getRelatedTypeName(relation.referencedByTable, tableByName);
599
- const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsUnionType([
600
- t.tsBooleanKeyword(),
601
- t.tsTypeLiteral([
602
- (() => {
603
- const selectProp = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
604
- selectProp.optional = true;
605
- return selectProp;
606
- })(),
607
- ]),
625
+ const prop = t.tsPropertySignature(t.identifier(relation.fieldName), t.tsTypeAnnotation(t.tsTypeLiteral([
626
+ (() => {
627
+ const selectProp = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${relatedTypeName}Select`))));
628
+ selectProp.optional = false;
629
+ return selectProp;
630
+ })(),
608
631
  ])));
609
632
  prop.optional = true;
610
633
  members.push(prop);
@@ -746,7 +769,7 @@ function generateOrderByTypes(tables) {
746
769
  /**
747
770
  * Build the nested data object fields for Create input
748
771
  */
749
- function buildCreateDataFields(table) {
772
+ function buildCreateDataFieldsFromTable(table) {
750
773
  const fields = [];
751
774
  for (const field of table.fields) {
752
775
  if (EXCLUDED_MUTATION_FIELDS.includes(field.name))
@@ -760,12 +783,60 @@ function buildCreateDataFields(table) {
760
783
  }
761
784
  return fields;
762
785
  }
786
+ /**
787
+ * Build Create input fields from schema Input types when available.
788
+ *
789
+ * This follows the actual GraphQL input shape:
790
+ * `CreateXInput { clientMutationId?, x: XInput! }`
791
+ */
792
+ function buildCreateDataFieldsFromSchema(table, typeRegistry) {
793
+ const createInputTypeName = getCreateInputTypeName(table);
794
+ const createInputType = typeRegistry.get(createInputTypeName);
795
+ if (!createInputType ||
796
+ createInputType.kind !== 'INPUT_OBJECT' ||
797
+ !createInputType.inputFields) {
798
+ return null;
799
+ }
800
+ const { singularName } = getTableNames(table);
801
+ const entityArg = createInputType.inputFields.find((field) => field.name === singularName) ??
802
+ createInputType.inputFields.find((field) => field.name !== 'clientMutationId');
803
+ if (!entityArg)
804
+ return null;
805
+ const entityInputTypeName = getTypeBaseName(entityArg.type);
806
+ if (!entityInputTypeName)
807
+ return null;
808
+ const entityInputType = typeRegistry.get(entityInputTypeName);
809
+ if (!entityInputType ||
810
+ entityInputType.kind !== 'INPUT_OBJECT' ||
811
+ !entityInputType.inputFields) {
812
+ return null;
813
+ }
814
+ const fields = [];
815
+ for (const field of entityInputType.inputFields) {
816
+ if (EXCLUDED_MUTATION_FIELDS.includes(field.name)) {
817
+ continue;
818
+ }
819
+ fields.push({
820
+ name: field.name,
821
+ type: typeRefToTs(field.type),
822
+ optional: !isRequired(field.type),
823
+ });
824
+ }
825
+ return fields;
826
+ }
827
+ /**
828
+ * Build Create input fields, preferring schema-derived Input object fields.
829
+ */
830
+ function buildCreateDataFields(table, typeRegistry) {
831
+ return (buildCreateDataFieldsFromSchema(table, typeRegistry) ??
832
+ buildCreateDataFieldsFromTable(table));
833
+ }
763
834
  /**
764
835
  * Build Create input interface as AST
765
836
  */
766
- function buildCreateInputInterface(table) {
837
+ function buildCreateInputInterface(table, typeRegistry) {
767
838
  const { typeName, singularName } = getTableNames(table);
768
- const fields = buildCreateDataFields(table);
839
+ const fields = buildCreateDataFields(table, typeRegistry);
769
840
  // Build the nested object type for the entity data
770
841
  const nestedProps = fields.map((field) => {
771
842
  const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(parseTypeString(field.type)));
@@ -809,7 +880,7 @@ function buildPatchProperties(table) {
809
880
  /**
810
881
  * Generate CRUD input type statements for a table
811
882
  */
812
- function generateCrudInputTypes(table) {
883
+ function generateCrudInputTypes(table, typeRegistry) {
813
884
  const statements = [];
814
885
  const { typeName } = getTableNames(table);
815
886
  const patchName = `${typeName}Patch`;
@@ -818,14 +889,15 @@ function generateCrudInputTypes(table) {
818
889
  const pkFieldName = pkField?.name ?? 'id';
819
890
  const pkFieldTsType = pkField?.tsType ?? 'string';
820
891
  // Create input
821
- statements.push(buildCreateInputInterface(table));
892
+ statements.push(buildCreateInputInterface(table, typeRegistry));
822
893
  // Patch interface
823
894
  statements.push(createExportedInterface(patchName, buildPatchProperties(table)));
824
- // Update input
895
+ // Update input - v5 uses entity-specific patch field names (e.g., "userPatch")
896
+ const patchFieldName = table.query?.patchFieldName ?? lcFirst(typeName) + 'Patch';
825
897
  statements.push(createExportedInterface(`Update${typeName}Input`, [
826
898
  { name: 'clientMutationId', type: 'string', optional: true },
827
899
  { name: pkFieldName, type: pkFieldTsType, optional: false },
828
- { name: 'patch', type: patchName, optional: false },
900
+ { name: patchFieldName, type: patchName, optional: false },
829
901
  ]));
830
902
  // Delete input
831
903
  statements.push(createExportedInterface(`Delete${typeName}Input`, [
@@ -837,10 +909,10 @@ function generateCrudInputTypes(table) {
837
909
  /**
838
910
  * Generate all CRUD input type statements
839
911
  */
840
- function generateAllCrudInputTypes(tables) {
912
+ function generateAllCrudInputTypes(tables, typeRegistry) {
841
913
  const statements = [];
842
914
  for (const table of tables) {
843
- statements.push(...generateCrudInputTypes(table));
915
+ statements.push(...generateCrudInputTypes(table, typeRegistry));
844
916
  }
845
917
  if (statements.length > 0) {
846
918
  addSectionComment(statements, 'CRUD Input Types');
@@ -1043,15 +1115,12 @@ function generatePayloadTypes(typeRegistry, usedPayloadTypes, alreadyGeneratedTy
1043
1115
  const nestedType = baseType ? typeRegistry.get(baseType) : null;
1044
1116
  let propType;
1045
1117
  if (nestedType?.kind === 'OBJECT') {
1046
- propType = t.tsUnionType([
1047
- t.tsBooleanKeyword(),
1048
- t.tsTypeLiteral([
1049
- (() => {
1050
- const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${baseType}Select`))));
1051
- p.optional = true;
1052
- return p;
1053
- })(),
1054
- ]),
1118
+ propType = t.tsTypeLiteral([
1119
+ (() => {
1120
+ const p = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(`${baseType}Select`))));
1121
+ p.optional = false;
1122
+ return p;
1123
+ })(),
1055
1124
  ]);
1056
1125
  }
1057
1126
  else {
@@ -1122,13 +1191,16 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
1122
1191
  const tablesList = tables ?? [];
1123
1192
  const hasTables = tablesList.length > 0;
1124
1193
  const tableByName = new Map(tablesList.map((table) => [table.name, table]));
1194
+ const enumTypes = hasTables
1195
+ ? collectEnumTypesFromTables(tablesList, typeRegistry)
1196
+ : new Set();
1197
+ const customScalarTypes = collectCustomScalarTypes(typeRegistry, tablesList, enumTypes);
1125
1198
  // 1. Scalar filter types
1126
1199
  statements.push(...generateScalarFilterTypes());
1127
1200
  // 2. Enum types used by table fields
1128
- if (hasTables) {
1129
- const enumTypes = collectEnumTypesFromTables(tablesList, typeRegistry);
1130
- statements.push(...generateEnumTypes(typeRegistry, enumTypes));
1131
- }
1201
+ statements.push(...generateEnumTypes(typeRegistry, enumTypes));
1202
+ // 2b. Unknown/custom scalar aliases for schema-specific scalars
1203
+ statements.push(...generateCustomScalarTypes(customScalarTypes));
1132
1204
  // 3. Entity and relation types (if tables provided)
1133
1205
  if (hasTables) {
1134
1206
  statements.push(...generateEntityTypes(tablesList));
@@ -1143,7 +1215,7 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
1143
1215
  // 5. OrderBy types
1144
1216
  statements.push(...generateOrderByTypes(tablesList));
1145
1217
  // 6. CRUD input types
1146
- statements.push(...generateAllCrudInputTypes(tablesList));
1218
+ statements.push(...generateAllCrudInputTypes(tablesList, typeRegistry));
1147
1219
  }
1148
1220
  // 6b. Connection fields map (runtime metadata for buildSelections)
1149
1221
  // Always emit this export so generated model/custom-op imports stay valid.