graphql-codegen-faker-fixtures
Version:
Generate faker fixtures for your fields
310 lines (305 loc) • 11.6 kB
JavaScript
// src/plugin.ts
import { Kind, parse, printSchema, visit } from "graphql";
import { resolveExternalModuleAndFn } from "@graphql-codegen/plugin-helpers";
var pluginConfig;
var plugin = (schema, documents, config) => {
pluginConfig = config;
const { typeImport, skipFragments, buildersOnly, fakerjsSeed } = pluginConfig;
const disclaimer = `
/* NOTE: This file is auto-generated. DO NOT EDIT. */
`;
const imports = [
`import * as types from '${typeImport || "CONFIGURE_TYPE_IMPORT"}';`,
`import { deepmergeCustom } from 'deepmerge-ts'`,
`import { faker } from '@faker-js/faker';`
].join("\n");
const utils = `
export type DeepPartial<T> = T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T
export const repeat = <T>(numTimes: number, callback: (i: number) => T) =>
Array.from(Array(numTimes).keys()).map((i) => callback(i))
const deepmerge = deepmergeCustom({ mergeArrays: false })
export const merge = <T>(fixture: T, override: unknown) =>
deepmerge(fixture, override) as T
export const random = (min: number, max: number) => faker.number.int({ min, max })
faker.seed(${fakerjsSeed || 12345654321})
`;
const astSchema = parse(printSchema(schema));
const documentNodes = documents.map((document) => document.document).filter(Boolean);
const { enums, fields, unions } = collectFromSchema(astSchema);
const fragments = documentNodes.flatMap(
(documentNode) => collectFragments(documentNode, fields)
);
const code = fragments.filter((fragment) => !(skipFragments == null ? void 0 : skipFragments.includes(fragment.fragmentName))).map((fragment) => createFragmentBuilder(fragment, enums, unions)).join("\n");
const parts = buildersOnly ? [code] : [disclaimer, imports, utils, code];
return parts.join("\n\n");
};
var collectFromSchema = (astNode) => {
const fields = [];
const enums = [];
const unions = [];
const visitObjectTypeOrInterfaceType = (node) => {
var _a;
const objectName = node.name.value;
(_a = node == null ? void 0 : node.fields) == null ? void 0 : _a.forEach((field) => {
const fieldName = field.name.value;
const fieldType = collectFieldType(field.type);
const isArray = field.type.kind === Kind.NON_NULL_TYPE ? field.type.type.kind === Kind.LIST_TYPE : field.type.kind === Kind.LIST_TYPE;
fields.push({
objectName,
fieldType,
fieldName,
isArray
});
});
};
visit(astNode, {
EnumTypeDefinition: {
leave: (node) => {
var _a;
const enumName = node.name.value;
const enumValues = ((_a = node == null ? void 0 : node.values) == null ? void 0 : _a.map((value) => value.name.value)) || [];
enums.push({ enumName, enumValues });
}
},
// Gets called every time the visitor visits an UnionTypeDefinition
UnionTypeDefinition: {
leave: (node) => {
var _a;
const unionName = node.name.value;
const unionValues = ((_a = node.types) == null ? void 0 : _a.map((namedTypeNode) => namedTypeNode.name.value)) || [];
unions.push({ unionName, unionValues });
}
},
// Gets called every time the visitor visits an ObjectTypeDefinition
ObjectTypeDefinition: {
leave: visitObjectTypeOrInterfaceType
},
// Gets called every time the visitor visits an InterfaceTypeDefinition
InterfaceTypeDefinition: {
leave: visitObjectTypeOrInterfaceType
}
});
return { fields, enums, unions };
};
var collectFieldType = (node) => {
if (node.kind === Kind.NON_NULL_TYPE || node.kind === Kind.LIST_TYPE) {
return collectFieldType(node.type);
}
return node.name.value;
};
var collectFragments = (astNode, collectedFields) => {
const fragments = [];
const collectFieldsInFragment = (node, objectName) => {
const fields = [];
node.selections.map((selection) => {
var _a, _b;
if (selection.kind === Kind.FIELD) {
const fieldName = selection.name.value;
const collectedField = fieldName === "__typename" ? (
// We have encountered a '__typename' field.
// These are special as they should have a hardcoded value: objectName
{
fieldType: "__typename",
fieldName: "__typename",
objectName
}
) : (
// field was not a __typename.
// Let's see if we can find the Field we have collected earlier.
// It should contain more info on its type.
collectedFields.find(
(collectedField2) => collectedField2.objectName === objectName && collectedField2.fieldName === fieldName
)
);
if (!collectedField) {
throw new Error(
`Could not find type for field ${fieldName} in fragment for ${objectName}`
);
}
let field = { ...collectedField };
const fieldAlias = (_a = selection.alias) == null ? void 0 : _a.value;
if (fieldAlias) {
field = { ...field, fieldName: fieldAlias };
}
if (selection.selectionSet) {
field = {
...field,
fields: collectFieldsInFragment(
selection.selectionSet,
field.fieldType
)
};
}
fields.push(field);
} else if (selection.kind === Kind.FRAGMENT_SPREAD) {
fields.push({
spreadName: selection.name.value,
isSpread: true,
fieldName: "",
fieldType: "",
objectName: ""
});
} else if (selection.kind === Kind.INLINE_FRAGMENT) {
const objectName2 = ((_b = selection.typeCondition) == null ? void 0 : _b.name.value) || "";
fields.push({
fieldName: "",
fieldType: "",
objectName: objectName2,
isSpread: true,
fields: collectFieldsInFragment(selection.selectionSet, objectName2)
});
}
});
return fields;
};
visit(astNode, {
FragmentDefinition: {
leave: (node) => {
const fragmentName = node.name.value;
const objectName = node.typeCondition.name.value;
const fields = collectFieldsInFragment(node.selectionSet, objectName);
fragments.push({ fragmentName, objectName, fields });
}
}
});
return fragments;
};
var convertToCorrectCaseType = (fragmentName) => {
const { namingConvention } = pluginConfig;
if (!namingConvention) {
return resolveExternalModuleAndFn("change-case-all#pascalCase")(
fragmentName
);
}
if (namingConvention === "keep") {
return fragmentName;
}
return resolveExternalModuleAndFn(namingConvention)(fragmentName);
};
var createFragmentBuilder = (fragment, enums, unions) => {
const name = fragment.fragmentName;
const casedName = convertToCorrectCaseType(name);
const typeName = `types.${casedName}Fragment`;
const partialTypeName = `DeepPartial<${typeName}>`;
const isUnion = unions.some(
(union) => union.unionName === fragment.objectName
);
if (isUnion) {
return `
export const fake${casedName} = ():${typeName} =>
faker.helpers.arrayElement([
${fragment.fields.filter((field) => field.spreadName).map((field) => convertToCorrectCaseType(field.spreadName || "")).map((casedName2) => `fake${casedName2}()`).join(",")}
])
`;
}
return `
export const fake${casedName} = (override?:${partialTypeName}):${typeName} => {
const fixture:${typeName} = {
${createFakerFields(fragment.fields, enums, casedName)}
}
return override
? merge(fixture, override)
: fixture
}
`;
};
var createFakerFields = (fields, enums, casedName) => {
const { skipFields } = pluginConfig;
const shouldSkipField = (fieldName) => skipFields == null ? void 0 : skipFields.includes(`${casedName}Fragment.${fieldName}`);
const nonSpreads = fields.filter((field) => !field.isSpread);
const spreads = fields.filter((field) => field.isSpread);
const nonSpreadString = nonSpreads.filter((field) => !shouldSkipField(field.fieldName)).map((field) => createFakerValue(field, enums, casedName)).join(",");
const spreadValues = spreads.filter((field) => !shouldSkipField(field.fieldName)).map((field) => createFakerValue(field, enums, casedName));
let spreadString = "";
if (spreadValues.length === 1) {
spreadString = `...${spreadValues[0]}`;
} else if (spreadValues.length > 1) {
spreadString = `...faker.helpers.arrayElement([${spreadValues.join(",")}])`;
}
return [nonSpreadString, spreadString].filter(Boolean).join(",");
};
var createFakerValue = (field, enums, casedName) => {
if (field.fieldName === "__typename") {
return `__typename: '${field.objectName}'`;
}
const prefix = field.isArray ? "repeat(random(1 , 5), () => (" : "";
const suffix = field.isArray ? "))" : "";
if (field.spreadName) {
const fragmentName = convertToCorrectCaseType(field.spreadName);
return `${prefix}fake${fragmentName}()${suffix}`;
}
if (field.fields && field.isSpread && !field.spreadName) {
const subFields = createFakerFields(field.fields, enums, casedName);
return `${prefix}{${subFields}}${suffix}`;
}
if (field.fields) {
if (field.fields.length === 0) {
throw new Error("Field should have subFields");
}
const subFields = createFakerFields(field.fields, enums, casedName);
return `${field.fieldName}: ${prefix}{ ${subFields} }${suffix}`;
}
const fakerMethod = createFakerMethod(field, enums);
if (fakerMethod) {
return `${field.fieldName}: ${prefix}${fakerMethod}${suffix}`;
}
return "";
};
var findFakerMethodOfScalar = (scalar, defaultMethod, field) => {
var _a;
const { scalars } = pluginConfig;
const scalarConfig = scalars == null ? void 0 : scalars[scalar];
const defaultFieldMethod = ((_a = scalars == null ? void 0 : scalars[scalar]) == null ? void 0 : _a._default) || defaultMethod;
if (!scalarConfig) {
return defaultFieldMethod;
}
if (`${field.objectName}.${field.fieldName}` in scalarConfig) {
if (typeof scalarConfig[`${field.objectName}.${field.fieldName}`] !== "string") {
throw Error(
`Configuration error: value of ${field.objectName}.${field.fieldName} in ${scalar} is not of type "string".`
);
}
return scalarConfig[`${field.objectName}.${field.fieldName}`];
}
if (field.fieldName in scalarConfig) {
if (typeof scalarConfig[field.fieldName] !== "string") {
throw Error(
`Configuration error: value of ${field.fieldName} in ${scalar} is not of type "string".`
);
}
return scalarConfig[field.fieldName];
}
return defaultFieldMethod;
};
var findFieldAsExistingEnum = (field, enums) => {
return enums.find((myEnum) => myEnum.enumName === field.fieldType);
};
var findDefaultFakerMethodForScalar = (scalar) => {
switch (scalar) {
case "Int":
return "faker.number.int()";
case "Float":
return "faker.number.float()";
case "Boolean":
return "faker.datatype.boolean()";
case "ID":
return "faker.string.uuid()";
case "String":
default:
return "faker.lorem.words()";
}
};
var createFakerMethod = (field, enums) => {
const myEnum = findFieldAsExistingEnum(field, enums);
if (myEnum) {
return `faker.helpers.arrayElement(${JSON.stringify(myEnum.enumValues)})`;
} else {
return findFakerMethodOfScalar(field.fieldType, findDefaultFakerMethodForScalar(field.fieldType), field);
}
};
export {
plugin
};
//# sourceMappingURL=index.mjs.map