@soda-gql/codegen 0.11.18 → 0.11.20

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/dist/index.cjs CHANGED
@@ -13,16 +13,6 @@ let node_crypto = require("node:crypto");
13
13
  const emitOperation = (operation, options) => {
14
14
  const lines = [];
15
15
  const schema = options.schemaDocument ? require_generator.createSchemaIndex(options.schemaDocument) : null;
16
- lines.push(`import { gql } from "${options.graphqlSystemPath}";`);
17
- if (operation.fragmentDependencies.length > 0 && options.fragmentImports) {
18
- for (const fragName of operation.fragmentDependencies) {
19
- const importPath = options.fragmentImports.get(fragName);
20
- if (importPath) {
21
- lines.push(`import { ${fragName}Fragment } from "${importPath}";`);
22
- }
23
- }
24
- }
25
- lines.push("");
26
16
  const exportName = `${operation.name}Compat`;
27
17
  const operationType = operation.kind;
28
18
  lines.push(`export const ${exportName} = gql.${options.schemaName}(({ ${operationType}, $var }) =>`);
@@ -49,16 +39,6 @@ const emitFragment = (fragment, options) => {
49
39
  const lines = [];
50
40
  const schema = options.schemaDocument ? require_generator.createSchemaIndex(options.schemaDocument) : null;
51
41
  const hasVariables = fragment.variables.length > 0;
52
- lines.push(`import { gql } from "${options.graphqlSystemPath}";`);
53
- if (fragment.fragmentDependencies.length > 0 && options.fragmentImports) {
54
- for (const fragName of fragment.fragmentDependencies) {
55
- const importPath = options.fragmentImports.get(fragName);
56
- if (importPath) {
57
- lines.push(`import { ${fragName}Fragment } from "${importPath}";`);
58
- }
59
- }
60
- }
61
- lines.push("");
62
42
  const exportName = `${fragment.name}Fragment`;
63
43
  const destructure = hasVariables ? "fragment, $var" : "fragment";
64
44
  lines.push(`export const ${exportName} = gql.${options.schemaName}(({ ${destructure} }) =>`);
@@ -190,6 +170,9 @@ const emitFieldSelection = (field, indent, variableNames, schema) => {
190
170
  const selections = field.selections;
191
171
  const hasArgs = args && args.length > 0;
192
172
  const hasSelections = selections && selections.length > 0;
173
+ if (!hasArgs && !hasSelections) {
174
+ return (0, neverthrow.ok)(`${padding}${field.name}: true,`);
175
+ }
193
176
  let line = `${padding}...f.${field.name}(`;
194
177
  if (hasArgs) {
195
178
  const argsResult = emitArguments(args, variableNames);
@@ -586,6 +569,52 @@ const buildModifier = (structure) => {
586
569
  return structure.inner + structure.lists.join("");
587
570
  };
588
571
  /**
572
+ * Check if source modifier can be assigned to target modifier.
573
+ * Implements GraphQL List Coercion: depth difference of 0 or 1 is allowed.
574
+ *
575
+ * Rules:
576
+ * - A single value can be coerced into a list (depth diff = 1)
577
+ * - At each level, non-null can be assigned to nullable (but not vice versa)
578
+ *
579
+ * @param source - The modifier of the value being assigned (variable's type)
580
+ * @param target - The modifier expected by the position (field argument's type)
581
+ * @returns true if assignment is valid
582
+ */
583
+ const isModifierAssignable = (source, target) => {
584
+ const srcStruct = parseModifierStructure(source);
585
+ const tgtStruct = parseModifierStructure(target);
586
+ const depthDiff = tgtStruct.lists.length - srcStruct.lists.length;
587
+ if (depthDiff < 0 || depthDiff > 1) return false;
588
+ const tgtListsToCompare = depthDiff === 1 ? tgtStruct.lists.slice(1) : tgtStruct.lists;
589
+ if (depthDiff === 1 && srcStruct.lists.length === 0 && srcStruct.inner === "?" && tgtStruct.lists[0] === "[]!") {
590
+ return false;
591
+ }
592
+ if (srcStruct.inner === "?" && tgtStruct.inner === "!") return false;
593
+ for (let i = 0; i < srcStruct.lists.length; i++) {
594
+ const srcList = srcStruct.lists[i];
595
+ const tgtList = tgtListsToCompare[i];
596
+ if (srcList === "[]?" && tgtList === "[]!") return false;
597
+ }
598
+ return true;
599
+ };
600
+ /**
601
+ * Derive minimum modifier needed to satisfy expected modifier.
602
+ * When List Coercion can apply, returns one level shallower.
603
+ *
604
+ * @param expectedModifier - The modifier expected by the field argument
605
+ * @returns The minimum modifier the variable must have
606
+ */
607
+ const deriveMinimumModifier = (expectedModifier) => {
608
+ const struct = parseModifierStructure(expectedModifier);
609
+ if (struct.lists.length > 0) {
610
+ return buildModifier({
611
+ inner: struct.inner,
612
+ lists: struct.lists.slice(1)
613
+ });
614
+ }
615
+ return expectedModifier;
616
+ };
617
+ /**
589
618
  * Merge two modifiers by taking the stricter constraint at each level.
590
619
  * - Non-null (!) is stricter than nullable (?)
591
620
  * - List depths must match
@@ -667,7 +696,8 @@ const collectVariablesFromValue = (value, expectedTypeName, expectedModifier, sc
667
696
  usages.push({
668
697
  name: value.name,
669
698
  typeName: expectedTypeName,
670
- modifier: expectedModifier,
699
+ expectedModifier,
700
+ minimumModifier: deriveMinimumModifier(expectedModifier),
671
701
  typeKind
672
702
  });
673
703
  return null;
@@ -777,7 +807,12 @@ const getFieldReturnType = (schema, parentTypeName, fieldName) => {
777
807
  };
778
808
  /**
779
809
  * Merge multiple variable usages into a single InferredVariable.
780
- * Validates type compatibility and merges modifiers using stricter constraint.
810
+ * Validates type compatibility and merges modifiers using List Coercion rules.
811
+ *
812
+ * The algorithm:
813
+ * 1. Validate all usages have the same type name
814
+ * 2. Merge minimumModifiers to find the candidate (shallowest type that could work)
815
+ * 3. Verify the candidate can satisfy ALL expected modifiers via isModifierAssignable
781
816
  */
782
817
  const mergeVariableUsages = (variableName, usages) => {
783
818
  if (usages.length === 0) {
@@ -797,9 +832,9 @@ const mergeVariableUsages = (variableName, usages) => {
797
832
  });
798
833
  }
799
834
  }
800
- let mergedModifier = first.modifier;
835
+ let candidateModifier = first.minimumModifier;
801
836
  for (let i = 1; i < usages.length; i++) {
802
- const result = mergeModifiers(mergedModifier, usages[i].modifier);
837
+ const result = mergeModifiers(candidateModifier, usages[i].minimumModifier);
803
838
  if (!result.ok) {
804
839
  return (0, neverthrow.err)({
805
840
  code: "GRAPHQL_VARIABLE_MODIFIER_INCOMPATIBLE",
@@ -807,12 +842,21 @@ const mergeVariableUsages = (variableName, usages) => {
807
842
  variableName
808
843
  });
809
844
  }
810
- mergedModifier = result.value;
845
+ candidateModifier = result.value;
846
+ }
847
+ for (const usage of usages) {
848
+ if (!isModifierAssignable(candidateModifier, usage.expectedModifier)) {
849
+ return (0, neverthrow.err)({
850
+ code: "GRAPHQL_VARIABLE_MODIFIER_INCOMPATIBLE",
851
+ message: `Variable "$${variableName}" with modifier "${candidateModifier}" cannot satisfy expected "${usage.expectedModifier}"`,
852
+ variableName
853
+ });
854
+ }
811
855
  }
812
856
  return (0, neverthrow.ok)({
813
857
  name: variableName,
814
858
  typeName: first.typeName,
815
- modifier: mergedModifier,
859
+ modifier: candidateModifier,
816
860
  typeKind: first.typeKind
817
861
  });
818
862
  };
@@ -1008,7 +1052,8 @@ const transformFragment = (frag, schema, resolvedFragmentVariables) => {
1008
1052
  const allUsages = [...directUsages, ...spreadVariables.map((v) => ({
1009
1053
  name: v.name,
1010
1054
  typeName: v.typeName,
1011
- modifier: v.modifier,
1055
+ expectedModifier: v.modifier,
1056
+ minimumModifier: v.modifier,
1012
1057
  typeKind: v.typeKind
1013
1058
  }))];
1014
1059
  const variablesResult = inferVariablesFromUsages(allUsages);
@@ -1296,6 +1341,23 @@ const generateDefsStructure = (schemaName, categoryVars, chunkSize) => {
1296
1341
 
1297
1342
  //#endregion
1298
1343
  //#region packages/codegen/src/file.ts
1344
+ const removeDirectory = (dirPath) => {
1345
+ const targetPath = (0, node_path.resolve)(dirPath);
1346
+ try {
1347
+ (0, node_fs.rmSync)(targetPath, {
1348
+ recursive: true,
1349
+ force: true
1350
+ });
1351
+ return (0, neverthrow.ok)(undefined);
1352
+ } catch (error) {
1353
+ const message = error instanceof Error ? error.message : String(error);
1354
+ return (0, neverthrow.err)({
1355
+ code: "REMOVE_FAILED",
1356
+ message,
1357
+ outPath: targetPath
1358
+ });
1359
+ }
1360
+ };
1299
1361
  const writeModule = (outPath, contents) => {
1300
1362
  const targetPath = (0, node_path.resolve)(outPath);
1301
1363
  try {
@@ -1492,6 +1554,13 @@ export * from "./_internal";
1492
1554
  const defsPaths = [];
1493
1555
  if (categoryVars) {
1494
1556
  const outDir = (0, node_path.dirname)(outPath);
1557
+ const defsDir = (0, node_path.join)(outDir, "_defs");
1558
+ if ((0, node_fs.existsSync)(defsDir)) {
1559
+ const removeResult = removeDirectory(defsDir);
1560
+ if (removeResult.isErr()) {
1561
+ return (0, neverthrow.err)(removeResult.error);
1562
+ }
1563
+ }
1495
1564
  const combinedVars = {
1496
1565
  enums: [],
1497
1566
  inputs: [],
@@ -1547,6 +1616,7 @@ exports.emitFragment = emitFragment;
1547
1616
  exports.emitOperation = emitOperation;
1548
1617
  exports.hashSchema = hashSchema;
1549
1618
  exports.inferVariablesFromUsages = inferVariablesFromUsages;
1619
+ exports.isModifierAssignable = isModifierAssignable;
1550
1620
  exports.loadSchema = loadSchema;
1551
1621
  exports.mergeModifiers = mergeModifiers;
1552
1622
  exports.mergeVariableUsages = mergeVariableUsages;