graphql-data-generator 0.4.1-alpha.4 → 0.4.1-alpha.6

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/esm/codegen.js CHANGED
@@ -175,6 +175,7 @@ const getSelectionsType = (name, selections, definitions, fragments, references,
175
175
  if (!fragment) {
176
176
  throw new Error(`Could not find fragment '${selection.name.value}' in '${name}'`);
177
177
  }
178
+ // Fragment is automatically considered used
178
179
  selectionTypes.push(...getSelectionsType(fragment.typeCondition.name.value, fragment.selectionSet.selections, definitions, fragments, references, includeTypenames).map(([name, type, group]) => [
179
180
  name,
180
181
  type,
@@ -561,6 +562,55 @@ ${usedTypes.map(([name]) => ` ${name}: ${rename(name)};`).join("\n")}
561
562
  ? `[${type.fields?.filter((f) => usage.has(f.name.value)).map((v) => `"${v.name.value}"`).join(", ")}]`
562
563
  : "[]"}`).join(",\n")},\n} as const;`);
563
564
  }
565
+ // Generate types for fragments
566
+ const fragmentTypes = Object.entries(fragments).map(([fragmentName, fragment]) => {
567
+ const fragmentSelections = getSelectionsType(fragment.typeCondition.name.value, fragment.selectionSet.selections, types, fragments, references, includeTypenames);
568
+ const fragmentFields = fragmentSelections.reduce((fields, [name, type]) => {
569
+ fields[name] = type;
570
+ return fields;
571
+ }, {});
572
+ // Add __typename for object types
573
+ const baseTypeDef = types[fragment.typeCondition.name.value];
574
+ if (includeTypenames && baseTypeDef?.[0].kind === "ObjectTypeDefinition") {
575
+ Object.defineProperty(fragmentFields, "__typename", {
576
+ enumerable: true,
577
+ value: {
578
+ kind: "StringLiteral",
579
+ value: fragment.typeCondition.name.value,
580
+ optional: false,
581
+ },
582
+ });
583
+ }
584
+ return [
585
+ fragmentName,
586
+ {
587
+ kind: "Object",
588
+ value: fragmentFields,
589
+ optional: false,
590
+ },
591
+ [...new Set(fragmentSelections.map(([name]) => name))]
592
+ ];
593
+ });
594
+ if (fragmentTypes.length) {
595
+ serializedTypes.unshift(...fragmentTypes.map(([name, type, _fields]) => `${exports.includes("types") ? "export " : ""}type ${name} = ${serializeType(type, false)};`).filter(filterOutputTypes));
596
+ // Update Types export to include fragments
597
+ const typesIndex = serializedTypes.findIndex(line => line?.startsWith("export type Types = {"));
598
+ if (typesIndex !== -1) {
599
+ const existingTypesExport = serializedTypes[typesIndex];
600
+ const updatedTypesExport = existingTypesExport.replace("export type Types = {", `export type Types = {
601
+ ${fragmentTypes.map(([name]) => ` ${name}: ${name};`).join("\n")}${usedTypes.length ? "\n" : ""}`);
602
+ serializedTypes[typesIndex] = updatedTypesExport;
603
+ }
604
+ // Update types const export to include fragments
605
+ const typesConstIndex = serializedTypes.findIndex(line => line?.startsWith("export const types = {"));
606
+ if (typesConstIndex !== -1) {
607
+ const existingTypesConstExport = serializedTypes[typesConstIndex];
608
+ const fragmentTypesConst = fragmentTypes.map(([name, , fields]) => ` ${name}: [${fields.map(f => `"${f}"`).join(", ")}]`).join(",\n");
609
+ const updatedTypesConstExport = existingTypesConstExport.replace("export const types = {", `export const types = {
610
+ ${fragmentTypesConst}${usedTypes.length && fragmentTypes.length ? "," : ""}${usedTypes.length ? "\n" : ""}`);
611
+ serializedTypes[typesConstIndex] = updatedTypesConstExport;
612
+ }
613
+ }
564
614
  const usedReferences = Object.values(references).filter((r) => r[1]).map((r) => r[0]);
565
615
  serializedTypes.unshift(...usedReferences.map((r) => {
566
616
  // TODO: warn if missing and use unknown instead
package/esm/init.js CHANGED
@@ -56,7 +56,7 @@ const getOperationContent = (path, operationName) => {
56
56
  // Helper: Unwraps non-null and list wrappers to get the named type.
57
57
  const getNamedType = (type) => type.kind === Kind.NAMED_TYPE ? type.name.value : getNamedType(type.type);
58
58
  // Helper: Find a type definition in the document by name.
59
- const getTypeDef = (doc, typeName) => doc.definitions.find((def) => "name" in def && def.name?.value === typeName);
59
+ const getTypeDef = (definitions, typeName) => definitions.find((def) => "name" in def && def.name?.value === typeName);
60
60
  /**
61
61
  * Initialize the data builder.
62
62
  * @param schema The plain text of your schema.
@@ -69,6 +69,32 @@ const getTypeDef = (doc, typeName) => doc.definitions.find((def) => "name" in de
69
69
  */
70
70
  export const init = (schema, queries, mutations, subscriptions, types, inputs, scalars, options) => (fn) => {
71
71
  const doc = parse(schema);
72
+ // Collect all fragment definitions from operation files
73
+ const fragmentDefinitions = [];
74
+ const collectFragments = (filePath, operationName) => {
75
+ try {
76
+ // If we have an operation name, use it; otherwise try to parse the file directly
77
+ let document;
78
+ if (operationName) {
79
+ document = getOperationContent(filePath, operationName);
80
+ }
81
+ else {
82
+ // Parse the file directly to get all definitions including fragments
83
+ const fileContent = loadFile(filePath);
84
+ document = parse(fileContent);
85
+ }
86
+ fragmentDefinitions.push(...document.definitions.filter((def) => def.kind === Kind.FRAGMENT_DEFINITION));
87
+ }
88
+ catch {
89
+ // Ignore files that can't be parsed
90
+ }
91
+ };
92
+ // Collect fragments from all operation files (parse entire files to get all fragments)
93
+ Object.values(queries).forEach(path => collectFragments(path));
94
+ Object.values(mutations).forEach(path => collectFragments(path));
95
+ Object.values(subscriptions).forEach(path => collectFragments(path));
96
+ // Combine schema definitions with fragment definitions
97
+ const allDefinitions = [...doc.definitions, ...fragmentDefinitions];
72
98
  const build = {};
73
99
  const transforms = fn(build);
74
100
  const addObjectTransforms = (type, obj) => {
@@ -94,7 +120,7 @@ export const init = (schema, queries, mutations, subscriptions, types, inputs, s
94
120
  const fields = types[type];
95
121
  if (!fields)
96
122
  return;
97
- const typeDef = getTypeDef(doc, type);
123
+ const typeDef = getTypeDef(allDefinitions, type);
98
124
  if (!typeDef || !("fields" in typeDef))
99
125
  return;
100
126
  const selectionSet = {
@@ -113,7 +139,7 @@ export const init = (schema, queries, mutations, subscriptions, types, inputs, s
113
139
  if (fieldDef) {
114
140
  // Get the inner (named) type of the field.
115
141
  const fieldTypeName = getNamedType(fieldDef.type);
116
- const fieldTypeDef = getTypeDef(doc, fieldTypeName);
142
+ const fieldTypeDef = getTypeDef(allDefinitions, fieldTypeName);
117
143
  // If the field is an Object, build its selection set recursively.
118
144
  if (fieldTypeDef &&
119
145
  fieldTypeDef.kind === Kind.OBJECT_TYPE_DEFINITION) {
@@ -123,7 +149,7 @@ export const init = (schema, queries, mutations, subscriptions, types, inputs, s
123
149
  } // If the field is an Interface, treat it like a union:
124
150
  // find all Object types that implement this interface and build inline fragments.
125
151
  else if (fieldTypeDef?.kind === Kind.INTERFACE_TYPE_DEFINITION) {
126
- const implementingTypes = doc.definitions.filter((def) => def.kind === Kind.OBJECT_TYPE_DEFINITION &&
152
+ const implementingTypes = allDefinitions.filter((def) => def.kind === Kind.OBJECT_TYPE_DEFINITION &&
127
153
  (def.interfaces?.some((iface) => iface.name.value === fieldTypeName) ?? false));
128
154
  const inlineFragments = implementingTypes.map((impl) => ({
129
155
  kind: Kind.INLINE_FRAGMENT,
@@ -164,7 +190,7 @@ export const init = (schema, queries, mutations, subscriptions, types, inputs, s
164
190
  });
165
191
  return selectionSet;
166
192
  };
167
- const wrap = (type, patches) => withGetDefaultPatch((type) => transforms[type]?.default, () => toObject(_proxy(doc.definitions, scalars, type, patches, {
193
+ const wrap = (type, patches) => withGetDefaultPatch((type) => transforms[type]?.default, () => toObject(_proxy(allDefinitions, scalars, type, patches, {
168
194
  selectionSet: getSelectionSet(type),
169
195
  })));
170
196
  const objectBuilder = (type) => addObjectTransforms(type, (...patches) => {
@@ -274,7 +300,7 @@ export const init = (schema, queries, mutations, subscriptions, types, inputs, s
274
300
  patches = [transforms[name].default, ...patches];
275
301
  }
276
302
  const { mock, parsedQuery } = withGetDefaultPatch((type) => transforms[type]?.default, () => {
277
- const { request: { query: parsedQuery, ...request }, ...raw } = operation(doc.definitions, scalars, query, ...patches);
303
+ const { request: { query: parsedQuery, ...request }, ...raw } = operation(allDefinitions, scalars, query, ...patches);
278
304
  const mock = toObject({
279
305
  request: { ...request },
280
306
  ...raw,
package/esm/proxy.js CHANGED
@@ -43,10 +43,24 @@ const resolveType = (definitions, path) => {
43
43
  name: { kind: Kind.NAME, value: name },
44
44
  };
45
45
  }
46
- type = {
47
- kind: Kind.NON_NULL_TYPE,
48
- type: { kind: Kind.NAMED_TYPE, name: { kind: Kind.NAME, value: name } },
49
- };
46
+ // Check if this is a fragment type
47
+ if (!definition) {
48
+ const fragment = definitions.find((d) => d.kind === Kind.FRAGMENT_DEFINITION && d.name.value === name);
49
+ if (fragment) {
50
+ // Use the fragment definition directly
51
+ definition = fragment;
52
+ type = {
53
+ kind: Kind.NON_NULL_TYPE,
54
+ type: { kind: Kind.NAMED_TYPE, name: { kind: Kind.NAME, value: name } },
55
+ };
56
+ }
57
+ }
58
+ if (!type) {
59
+ type = {
60
+ kind: Kind.NON_NULL_TYPE,
61
+ type: { kind: Kind.NAMED_TYPE, name: { kind: Kind.NAME, value: name } },
62
+ };
63
+ }
50
64
  }
51
65
  else {
52
66
  parent = definition.name.value;
@@ -594,6 +608,15 @@ export const _proxy = (definitions, scalars, path, patches, { prev, resolvedType
594
608
  return prev;
595
609
  return definition.values?.[0]?.name.value;
596
610
  }
611
+ case Kind.FRAGMENT_DEFINITION: {
612
+ // For fragments, create a proxy for the base type with the fragment's selection set
613
+ const baseTypeName = definition.typeCondition.name.value;
614
+ return _proxy(definitions, scalars, baseTypeName, patches, {
615
+ prev,
616
+ selectionSet: definition.selectionSet,
617
+ nonNull: true,
618
+ });
619
+ }
597
620
  default:
598
621
  throw new Error(`Unhandled definition kind '${definition.kind}'`);
599
622
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphql-data-generator",
3
- "version": "0.4.1-alpha.4",
3
+ "version": "0.4.1-alpha.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/voces/graphql-data-generator.git"
package/script/codegen.js CHANGED
@@ -181,6 +181,7 @@ const getSelectionsType = (name, selections, definitions, fragments, references,
181
181
  if (!fragment) {
182
182
  throw new Error(`Could not find fragment '${selection.name.value}' in '${name}'`);
183
183
  }
184
+ // Fragment is automatically considered used
184
185
  selectionTypes.push(...getSelectionsType(fragment.typeCondition.name.value, fragment.selectionSet.selections, definitions, fragments, references, includeTypenames).map(([name, type, group]) => [
185
186
  name,
186
187
  type,
@@ -567,6 +568,55 @@ ${usedTypes.map(([name]) => ` ${name}: ${rename(name)};`).join("\n")}
567
568
  ? `[${type.fields?.filter((f) => usage.has(f.name.value)).map((v) => `"${v.name.value}"`).join(", ")}]`
568
569
  : "[]"}`).join(",\n")},\n} as const;`);
569
570
  }
571
+ // Generate types for fragments
572
+ const fragmentTypes = Object.entries(fragments).map(([fragmentName, fragment]) => {
573
+ const fragmentSelections = getSelectionsType(fragment.typeCondition.name.value, fragment.selectionSet.selections, types, fragments, references, includeTypenames);
574
+ const fragmentFields = fragmentSelections.reduce((fields, [name, type]) => {
575
+ fields[name] = type;
576
+ return fields;
577
+ }, {});
578
+ // Add __typename for object types
579
+ const baseTypeDef = types[fragment.typeCondition.name.value];
580
+ if (includeTypenames && baseTypeDef?.[0].kind === "ObjectTypeDefinition") {
581
+ Object.defineProperty(fragmentFields, "__typename", {
582
+ enumerable: true,
583
+ value: {
584
+ kind: "StringLiteral",
585
+ value: fragment.typeCondition.name.value,
586
+ optional: false,
587
+ },
588
+ });
589
+ }
590
+ return [
591
+ fragmentName,
592
+ {
593
+ kind: "Object",
594
+ value: fragmentFields,
595
+ optional: false,
596
+ },
597
+ [...new Set(fragmentSelections.map(([name]) => name))]
598
+ ];
599
+ });
600
+ if (fragmentTypes.length) {
601
+ serializedTypes.unshift(...fragmentTypes.map(([name, type, _fields]) => `${exports.includes("types") ? "export " : ""}type ${name} = ${serializeType(type, false)};`).filter(filterOutputTypes));
602
+ // Update Types export to include fragments
603
+ const typesIndex = serializedTypes.findIndex(line => line?.startsWith("export type Types = {"));
604
+ if (typesIndex !== -1) {
605
+ const existingTypesExport = serializedTypes[typesIndex];
606
+ const updatedTypesExport = existingTypesExport.replace("export type Types = {", `export type Types = {
607
+ ${fragmentTypes.map(([name]) => ` ${name}: ${name};`).join("\n")}${usedTypes.length ? "\n" : ""}`);
608
+ serializedTypes[typesIndex] = updatedTypesExport;
609
+ }
610
+ // Update types const export to include fragments
611
+ const typesConstIndex = serializedTypes.findIndex(line => line?.startsWith("export const types = {"));
612
+ if (typesConstIndex !== -1) {
613
+ const existingTypesConstExport = serializedTypes[typesConstIndex];
614
+ const fragmentTypesConst = fragmentTypes.map(([name, , fields]) => ` ${name}: [${fields.map(f => `"${f}"`).join(", ")}]`).join(",\n");
615
+ const updatedTypesConstExport = existingTypesConstExport.replace("export const types = {", `export const types = {
616
+ ${fragmentTypesConst}${usedTypes.length && fragmentTypes.length ? "," : ""}${usedTypes.length ? "\n" : ""}`);
617
+ serializedTypes[typesConstIndex] = updatedTypesConstExport;
618
+ }
619
+ }
570
620
  const usedReferences = Object.values(references).filter((r) => r[1]).map((r) => r[0]);
571
621
  serializedTypes.unshift(...usedReferences.map((r) => {
572
622
  // TODO: warn if missing and use unknown instead
package/script/init.js CHANGED
@@ -82,7 +82,7 @@ const getOperationContent = (path, operationName) => {
82
82
  // Helper: Unwraps non-null and list wrappers to get the named type.
83
83
  const getNamedType = (type) => type.kind === graphql_1.Kind.NAMED_TYPE ? type.name.value : getNamedType(type.type);
84
84
  // Helper: Find a type definition in the document by name.
85
- const getTypeDef = (doc, typeName) => doc.definitions.find((def) => "name" in def && def.name?.value === typeName);
85
+ const getTypeDef = (definitions, typeName) => definitions.find((def) => "name" in def && def.name?.value === typeName);
86
86
  /**
87
87
  * Initialize the data builder.
88
88
  * @param schema The plain text of your schema.
@@ -95,6 +95,32 @@ const getTypeDef = (doc, typeName) => doc.definitions.find((def) => "name" in de
95
95
  */
96
96
  const init = (schema, queries, mutations, subscriptions, types, inputs, scalars, options) => (fn) => {
97
97
  const doc = (0, graphql_1.parse)(schema);
98
+ // Collect all fragment definitions from operation files
99
+ const fragmentDefinitions = [];
100
+ const collectFragments = (filePath, operationName) => {
101
+ try {
102
+ // If we have an operation name, use it; otherwise try to parse the file directly
103
+ let document;
104
+ if (operationName) {
105
+ document = getOperationContent(filePath, operationName);
106
+ }
107
+ else {
108
+ // Parse the file directly to get all definitions including fragments
109
+ const fileContent = loadFile(filePath);
110
+ document = (0, graphql_1.parse)(fileContent);
111
+ }
112
+ fragmentDefinitions.push(...document.definitions.filter((def) => def.kind === graphql_1.Kind.FRAGMENT_DEFINITION));
113
+ }
114
+ catch {
115
+ // Ignore files that can't be parsed
116
+ }
117
+ };
118
+ // Collect fragments from all operation files (parse entire files to get all fragments)
119
+ Object.values(queries).forEach(path => collectFragments(path));
120
+ Object.values(mutations).forEach(path => collectFragments(path));
121
+ Object.values(subscriptions).forEach(path => collectFragments(path));
122
+ // Combine schema definitions with fragment definitions
123
+ const allDefinitions = [...doc.definitions, ...fragmentDefinitions];
98
124
  const build = {};
99
125
  const transforms = fn(build);
100
126
  const addObjectTransforms = (type, obj) => {
@@ -120,7 +146,7 @@ const init = (schema, queries, mutations, subscriptions, types, inputs, scalars,
120
146
  const fields = types[type];
121
147
  if (!fields)
122
148
  return;
123
- const typeDef = getTypeDef(doc, type);
149
+ const typeDef = getTypeDef(allDefinitions, type);
124
150
  if (!typeDef || !("fields" in typeDef))
125
151
  return;
126
152
  const selectionSet = {
@@ -139,7 +165,7 @@ const init = (schema, queries, mutations, subscriptions, types, inputs, scalars,
139
165
  if (fieldDef) {
140
166
  // Get the inner (named) type of the field.
141
167
  const fieldTypeName = getNamedType(fieldDef.type);
142
- const fieldTypeDef = getTypeDef(doc, fieldTypeName);
168
+ const fieldTypeDef = getTypeDef(allDefinitions, fieldTypeName);
143
169
  // If the field is an Object, build its selection set recursively.
144
170
  if (fieldTypeDef &&
145
171
  fieldTypeDef.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION) {
@@ -149,7 +175,7 @@ const init = (schema, queries, mutations, subscriptions, types, inputs, scalars,
149
175
  } // If the field is an Interface, treat it like a union:
150
176
  // find all Object types that implement this interface and build inline fragments.
151
177
  else if (fieldTypeDef?.kind === graphql_1.Kind.INTERFACE_TYPE_DEFINITION) {
152
- const implementingTypes = doc.definitions.filter((def) => def.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION &&
178
+ const implementingTypes = allDefinitions.filter((def) => def.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION &&
153
179
  (def.interfaces?.some((iface) => iface.name.value === fieldTypeName) ?? false));
154
180
  const inlineFragments = implementingTypes.map((impl) => ({
155
181
  kind: graphql_1.Kind.INLINE_FRAGMENT,
@@ -190,7 +216,7 @@ const init = (schema, queries, mutations, subscriptions, types, inputs, scalars,
190
216
  });
191
217
  return selectionSet;
192
218
  };
193
- const wrap = (type, patches) => (0, proxy_js_1.withGetDefaultPatch)((type) => transforms[type]?.default, () => (0, util_js_1.toObject)((0, proxy_js_1._proxy)(doc.definitions, scalars, type, patches, {
219
+ const wrap = (type, patches) => (0, proxy_js_1.withGetDefaultPatch)((type) => transforms[type]?.default, () => (0, util_js_1.toObject)((0, proxy_js_1._proxy)(allDefinitions, scalars, type, patches, {
194
220
  selectionSet: getSelectionSet(type),
195
221
  })));
196
222
  const objectBuilder = (type) => addObjectTransforms(type, (...patches) => {
@@ -300,7 +326,7 @@ const init = (schema, queries, mutations, subscriptions, types, inputs, scalars,
300
326
  patches = [transforms[name].default, ...patches];
301
327
  }
302
328
  const { mock, parsedQuery } = (0, proxy_js_1.withGetDefaultPatch)((type) => transforms[type]?.default, () => {
303
- const { request: { query: parsedQuery, ...request }, ...raw } = (0, proxy_js_1.operation)(doc.definitions, scalars, query, ...patches);
329
+ const { request: { query: parsedQuery, ...request }, ...raw } = (0, proxy_js_1.operation)(allDefinitions, scalars, query, ...patches);
304
330
  const mock = (0, util_js_1.toObject)({
305
331
  request: { ...request },
306
332
  ...raw,
package/script/proxy.js CHANGED
@@ -47,10 +47,24 @@ const resolveType = (definitions, path) => {
47
47
  name: { kind: graphql_1.Kind.NAME, value: name },
48
48
  };
49
49
  }
50
- type = {
51
- kind: graphql_1.Kind.NON_NULL_TYPE,
52
- type: { kind: graphql_1.Kind.NAMED_TYPE, name: { kind: graphql_1.Kind.NAME, value: name } },
53
- };
50
+ // Check if this is a fragment type
51
+ if (!definition) {
52
+ const fragment = definitions.find((d) => d.kind === graphql_1.Kind.FRAGMENT_DEFINITION && d.name.value === name);
53
+ if (fragment) {
54
+ // Use the fragment definition directly
55
+ definition = fragment;
56
+ type = {
57
+ kind: graphql_1.Kind.NON_NULL_TYPE,
58
+ type: { kind: graphql_1.Kind.NAMED_TYPE, name: { kind: graphql_1.Kind.NAME, value: name } },
59
+ };
60
+ }
61
+ }
62
+ if (!type) {
63
+ type = {
64
+ kind: graphql_1.Kind.NON_NULL_TYPE,
65
+ type: { kind: graphql_1.Kind.NAMED_TYPE, name: { kind: graphql_1.Kind.NAME, value: name } },
66
+ };
67
+ }
54
68
  }
55
69
  else {
56
70
  parent = definition.name.value;
@@ -598,6 +612,15 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
598
612
  return prev;
599
613
  return definition.values?.[0]?.name.value;
600
614
  }
615
+ case graphql_1.Kind.FRAGMENT_DEFINITION: {
616
+ // For fragments, create a proxy for the base type with the fragment's selection set
617
+ const baseTypeName = definition.typeCondition.name.value;
618
+ return (0, exports._proxy)(definitions, scalars, baseTypeName, patches, {
619
+ prev,
620
+ selectionSet: definition.selectionSet,
621
+ nonNull: true,
622
+ });
623
+ }
601
624
  default:
602
625
  throw new Error(`Unhandled definition kind '${definition.kind}'`);
603
626
  }