UNPKG

graphql-codegen-typescript-mock-data

Version:

GraphQL Codegen plugin for building mock data

626 lines 29.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.plugin = void 0; const tslib_1 = require("tslib"); const graphql_1 = require("graphql"); const allFakerLocales = tslib_1.__importStar(require("@faker-js/faker")); const casual_1 = tslib_1.__importDefault(require("casual")); const plugin_helpers_1 = require("@graphql-codegen/plugin-helpers"); const sentence_case_1 = require("sentence-case"); const indefinite_1 = tslib_1.__importDefault(require("indefinite")); const utils_1 = require("@graphql-tools/utils"); const mockValueGenerator_1 = require("./mockValueGenerator"); const getTerminateCircularRelationshipsConfig = ({ terminateCircularRelationships }) => terminateCircularRelationships ? terminateCircularRelationships : false; const convertName = (value, fn, transformUnderscore) => { if (transformUnderscore) { return fn(value); } return value .split('_') .map((s) => fn(s)) .join('_'); }; const createNameConverter = (convention, transformUnderscore) => (value, prefix = '') => { if (convention === 'keep') { return `${prefix}${value}`; } return `${prefix}${convertName(value, (0, plugin_helpers_1.resolveExternalModuleAndFn)(convention), transformUnderscore)}`; }; const renameImports = (list, typeNamesMapping) => { return list.map((type) => { if (typeNamesMapping && typeNamesMapping[type]) { return `${type} as ${typeNamesMapping[type]}`; } return type; }); }; const toMockName = (typedName, casedName, prefix) => { if (prefix) { return `${prefix}${casedName}`; } const firstWord = (0, sentence_case_1.sentenceCase)(typedName).split(' ')[0]; return `${(0, indefinite_1.default)(firstWord, { articleOnly: true })}${casedName}`; }; const hashedString = (value) => { let hash = 0; if (value.length === 0) { return hash; } for (let i = 0; i < value.length; i++) { const char = value.charCodeAt(i); // eslint-disable-next-line no-bitwise hash = (hash << 5) - hash + char; // eslint-disable-next-line no-bitwise hash = hash & hash; // Convert to 32bit integer } return hash; }; const getGeneratorDefinition = (opts, generatorMode) => { if (isAnInputOutputGeneratorOptions(opts)) { return buildGeneratorDefinition(opts[generatorMode]); } return buildGeneratorDefinition(opts); }; const buildGeneratorDefinition = (opts) => { if (typeof opts === 'string') { return { generator: opts, arguments: [], }; } return opts; }; const getCasualCustomValue = (generatorDefinition, opts) => { // If there is a mapping to a `casual` type, then use it and make sure // to call it if it's a function const embeddedGenerator = casual_1.default[generatorDefinition.generator]; if (!embeddedGenerator && generatorDefinition.generator) { return generatorDefinition.generator; } const generatorArgs = Array.isArray(generatorDefinition.arguments) ? generatorDefinition.arguments : [generatorDefinition.arguments]; let extraArguments = []; const hasExtra = generatorDefinition.extra; if (hasExtra && generatorDefinition.extra.arguments) { extraArguments = Array.isArray(generatorDefinition.extra.arguments) ? generatorDefinition.extra.arguments : [generatorDefinition.extra.arguments]; } if (opts.dynamicValues) { const extraCall = generatorDefinition.extra ? extraArguments.length ? `.${generatorDefinition.extra.function}(...${JSON.stringify(extraArguments)})` : `.${generatorDefinition.extra.function}()` : ''; let functionCall = ''; if (typeof embeddedGenerator === 'function') { functionCall = generatorArgs.length ? `(...${JSON.stringify(generatorArgs)})` : '()'; } return `casual['${generatorDefinition.generator}']${functionCall}${extraCall}`; } let value = typeof embeddedGenerator === 'function' ? embeddedGenerator(...generatorArgs) : embeddedGenerator; if (hasExtra) { value = value[generatorDefinition.extra.function](...(extraArguments ? extraArguments : [])); } if (typeof value === 'string') { return `'${value}'`; } if (typeof value === 'object') { return `${JSON.stringify(value)}`; } return value; }; const getFakerGenerators = (generatorName, locale) => { let embeddedGenerator = allFakerLocales[`faker${locale.toUpperCase()}`]; let dynamicGenerator = 'faker'; if (typeof generatorName === 'string') { const generatorPath = generatorName.split('.'); for (const key of generatorPath) { if (typeof embeddedGenerator === 'object' && key in embeddedGenerator) { embeddedGenerator = embeddedGenerator[key]; dynamicGenerator = `${dynamicGenerator}['${key}']`; } } } // If the faker generator is not a function, we can assume the path is wrong if (typeof embeddedGenerator === 'function') { return { embeddedGenerator, dynamicGenerator }; } return { embeddedGenerator: null, dynamicGenerator: null }; }; const getFakerCustomValue = (generatorDefinition, opts) => { // If there is a mapping to a `faker` type, then use it const { embeddedGenerator, dynamicGenerator } = getFakerGenerators(generatorDefinition.generator, opts.generatorLocale); if (!embeddedGenerator && generatorDefinition.generator) { return generatorDefinition.generator; } const generatorArgs = Array.isArray(generatorDefinition.arguments) ? generatorDefinition.arguments : [generatorDefinition.arguments]; let extraArguments = []; const hasExtra = generatorDefinition.extra; if (hasExtra && generatorDefinition.extra.arguments) { extraArguments = Array.isArray(generatorDefinition.extra.arguments) ? generatorDefinition.extra.arguments : [generatorDefinition.extra.arguments]; } if (opts.dynamicValues) { const extraCall = hasExtra ? extraArguments.length ? `.${generatorDefinition.extra.function}(...${JSON.stringify(extraArguments)})` : `.${generatorDefinition.extra.function}()` : ''; return `${dynamicGenerator}${generatorArgs.length ? `(...${JSON.stringify(generatorArgs)})${extraCall}` : `()${extraCall}`}`; } const value = hasExtra ? embeddedGenerator(...generatorArgs)[generatorDefinition.extra.function](...(extraArguments ? extraArguments : [])) : embeddedGenerator(...generatorArgs); if (typeof value === 'string') { return `'${value}'`; } if (typeof value === 'object') { return `${JSON.stringify(value)}`; } return value; }; const getCustomValue = (generatorDefinition, opts) => { if (opts.generateLibrary === 'casual') { return getCasualCustomValue(generatorDefinition, opts); } if (opts.generateLibrary === 'faker') { return getFakerCustomValue(generatorDefinition, opts); } throw `Unknown generator library: ${opts.generateLibrary}`; }; const handleValueGeneration = (opts, customScalar, baseGenerator) => { if (opts.fieldGeneration) { // Check for a specific generation for the type & field if (opts.typeName in opts.fieldGeneration && opts.fieldName in opts.fieldGeneration[opts.typeName]) { const generatorDefinition = getGeneratorDefinition(opts.fieldGeneration[opts.typeName][opts.fieldName], opts.generatorMode); return getCustomValue(generatorDefinition, opts); } // Check for a general field generation definition if ('_all' in opts.fieldGeneration && opts.fieldName in opts.fieldGeneration['_all']) { const generatorDefinition = getGeneratorDefinition(opts.fieldGeneration['_all'][opts.fieldName], opts.generatorMode); return getCustomValue(generatorDefinition, opts); } } if (customScalar) { return getCustomValue(customScalar, opts); } if (opts.defaultNullableToNull && !opts.nonNull) { return null; } return baseGenerator(); }; const getNamedImplementType = (opts) => { const { currentType } = opts; if (!currentType || !('name' in currentType)) { return ''; } return getNamedType({ ...opts, currentType, }); }; const getNamedType = (opts) => { if (!opts.currentType) { return ''; } const mockValueGenerator = (0, mockValueGenerator_1.setupMockValueGenerator)({ generateLibrary: opts.generateLibrary, dynamicValues: opts.dynamicValues, generatorLocale: opts.generatorLocale, }); if (!opts.dynamicValues) mockValueGenerator.seed(hashedString(opts.typeName + opts.fieldName)); const name = opts.currentType.name.value; const casedName = createNameConverter(opts.typeNamesConvention, opts.transformUnderscore)(name); switch (name) { case 'String': { const customScalar = opts.customScalars ? getGeneratorDefinition(opts.customScalars['String'], opts.generatorMode) : null; return handleValueGeneration(opts, customScalar, mockValueGenerator.word); } case 'Float': { const customScalar = opts.customScalars ? getGeneratorDefinition(opts.customScalars['Float'], opts.generatorMode) : null; return handleValueGeneration(opts, customScalar, mockValueGenerator.float); } case 'ID': { const customScalar = opts.customScalars ? getGeneratorDefinition(opts.customScalars['ID'], opts.generatorMode) : null; return handleValueGeneration(opts, customScalar, mockValueGenerator.uuid); } case 'Boolean': { const customScalar = opts.customScalars ? getGeneratorDefinition(opts.customScalars['Boolean'], opts.generatorMode) : null; return handleValueGeneration(opts, customScalar, mockValueGenerator.boolean); } case 'Int': { const customScalar = opts.customScalars ? getGeneratorDefinition(opts.customScalars['Int'], opts.generatorMode) : null; return handleValueGeneration(opts, customScalar, mockValueGenerator.integer); } default: { const foundTypes = opts.types.filter((foundType) => { if (foundType.types && 'interfaces' in foundType.types) return foundType.types.interfaces.some((item) => item.name.value === name); return foundType.name === name; }); if (foundTypes.length) { const foundType = foundTypes[0]; switch (foundType.type) { case 'enum': { // It's an enum const typenameConverter = createNameConverter(opts.typeNamesConvention, opts.transformUnderscore); const enumConverter = createNameConverter(opts.enumValuesConvention, !opts.enumsAsTypes); const value = foundType.values ? foundType.values[0] : ''; return handleValueGeneration(opts, undefined, () => opts.enumsAsTypes ? opts.useTypeImports ? `('${value}' as ${typenameConverter(foundType.name, opts.enumsPrefix)})` : `'${value}'` : `${typenameConverter(foundType.name, opts.enumsPrefix)}.${enumConverter(value)}`); } case 'union': // Return the first union type node. return getNamedType({ ...opts, currentType: foundType.types && foundType.types[0], }); case 'scalar': { const customScalar = opts.customScalars ? getGeneratorDefinition(opts.customScalars[foundType.name], opts.generatorMode) : null; // it's a scalar, let's use a string as a value if there is no custom // mapping for this particular scalar return handleValueGeneration(opts, customScalar, foundType.name === 'Date' ? mockValueGenerator.date : mockValueGenerator.word); } case 'implement': if (opts.fieldGeneration && opts.fieldGeneration[opts.typeName] && opts.fieldGeneration[opts.typeName][opts.fieldName]) break; return (foundTypes .map((implementType) => getNamedImplementType({ ...opts, currentType: implementType.types, })) .filter((value) => value !== null) .join(' || ') || null); default: throw `foundType is unknown: ${foundType.name}: ${foundType.type}`; } } if (opts.terminateCircularRelationships) { return handleValueGeneration(opts, null, () => { if (opts.typesPrefix) { const typeNameConverter = createNameConverter(opts.typeNamesConvention, opts.transformUnderscore); const renamedType = renameImports([name], opts.typeNamesMapping)[0]; const casedNameWithPrefix = typeNameConverter(renamedType, opts.typesPrefix); return `relationshipsToOmit.has('${casedName}') ? {} as ${casedNameWithPrefix} : ${toMockName(name, casedName, opts.prefix)}({}, relationshipsToOmit)`; } else { const renamedType = renameImports([name], opts.typeNamesMapping)[0]; const renamedCasedName = createNameConverter(opts.typeNamesConvention, opts.transformUnderscore)(renamedType); return `relationshipsToOmit.has('${casedName}') ? {} as ${renamedCasedName} : ${toMockName(name, casedName, opts.prefix)}({}, relationshipsToOmit)`; } }); } else { return handleValueGeneration(opts, null, () => `${toMockName(name, casedName, opts.prefix)}()`); } } } }; const generateMockValue = (opts) => { var _a, _b; switch (opts.currentType.kind) { case 'NamedType': return getNamedType({ ...opts, currentType: opts.currentType, }); case 'NonNullType': return generateMockValue({ ...opts, currentType: opts.currentType.type, nonNull: true, }); case 'ListType': { const hasOverride = (_b = (_a = opts.fieldGeneration) === null || _a === void 0 ? void 0 : _a[opts.typeName]) === null || _b === void 0 ? void 0 : _b[opts.fieldName]; if (!hasOverride && opts.defaultNullableToNull && !opts.nonNull) { return null; } const listElements = Array.from({ length: opts.listElementCount }, (_, index) => generateMockValue({ ...opts, fieldName: opts.listElementCount === 1 ? opts.fieldName : `${opts.fieldName}${index}`, currentType: opts.currentType.type, })); return `[${listElements.join(', ')}]`; } default: throw new Error('unreached'); } }; const getMockString = (typeName, fields, typeNamesConvention, terminateCircularRelationships, addTypename = false, prefix, typesPrefix = '', transformUnderscore, typeNamesMapping, hasOneOfDirective = false) => { const typeNameConverter = createNameConverter(typeNamesConvention, transformUnderscore); const NewTypeName = typeNamesMapping[typeName] || typeName; const casedName = typeNameConverter(typeName); const casedNameWithPrefix = typeNameConverter(NewTypeName, typesPrefix); const typename = addTypename ? `\n __typename: '${typeName}',` : ''; const typenameReturnType = addTypename ? `{ __typename: '${typeName}' } & ` : ''; const overridesArgumentString = !hasOneOfDirective ? `overrides?: Partial<${casedNameWithPrefix}>` : `override?: ${casedNameWithPrefix}`; if (terminateCircularRelationships) { const relationshipsToOmitInit = terminateCircularRelationships === 'immediate' ? '_relationshipsToOmit' : 'new Set(_relationshipsToOmit)'; return ` export const ${toMockName(typeName, casedName, prefix)} = (${overridesArgumentString}, _relationshipsToOmit: Set<string> = new Set()): ${typenameReturnType}${casedNameWithPrefix} => { const relationshipsToOmit: Set<string> = ${relationshipsToOmitInit}; relationshipsToOmit.add('${casedName}'); return {${typename} ${fields} }; };`; } else { return ` export const ${toMockName(typeName, casedName, prefix)} = (${overridesArgumentString}): ${typenameReturnType}${casedNameWithPrefix} => { return {${typename} ${fields} }; };`; } }; const getImportTypes = ({ typeNamesConvention, definitions, types, typesFile, typesPrefix, enumsPrefix, transformUnderscore, enumsAsTypes, useTypeImports, typeNamesMapping, }) => { const typenameConverter = createNameConverter(typeNamesConvention, transformUnderscore); const typeImports = (typesPrefix === null || typesPrefix === void 0 ? void 0 : typesPrefix.endsWith('.')) ? [typesPrefix.slice(0, -1)] : definitions .filter(({ typeName }) => !!typeName) .map(({ typeName }) => typenameConverter(typeName, typesPrefix)); const enumTypes = (enumsPrefix === null || enumsPrefix === void 0 ? void 0 : enumsPrefix.endsWith('.')) ? [enumsPrefix.slice(0, -1)] : types.filter(({ type }) => type === 'enum').map(({ name }) => typenameConverter(name, enumsPrefix)); const renamedTypeImports = renameImports(typeImports, typeNamesMapping); if (!enumsAsTypes || useTypeImports) { renamedTypeImports.push(...enumTypes); } function onlyUnique(value, index, self) { return self.indexOf(value) === index; } const importPrefix = `import ${useTypeImports ? 'type ' : ''}`; return typesFile ? `${importPrefix}{ ${renamedTypeImports.filter(onlyUnique).join(', ')} } from '${typesFile}';\n` : ''; }; const isAnInputOutputGeneratorOptions = (opts) => opts !== undefined && typeof opts !== 'string' && 'input' in opts && 'output' in opts; // This plugin was generated with the help of ast explorer. // https://astexplorer.net // Paste your graphql schema in it, and you'll be able to see what the `astNode` will look like const plugin = (schema, documents, config) => { var _a, _b, _c, _d, _e, _f; const printedSchema = (0, utils_1.printSchemaWithDirectives)(schema); // Returns a string representation of the schema const astNode = (0, graphql_1.parse)(printedSchema); // Transforms the string into ASTNode if ('typenames' in config) { throw new Error('Config `typenames` was renamed to `typeNames`. Please update your config'); } const enumValuesConvention = config.enumValues || 'change-case-all#pascalCase'; const typeNamesConvention = config.typeNames || 'change-case-all#pascalCase'; const transformUnderscore = (_a = config.transformUnderscore) !== null && _a !== void 0 ? _a : true; const listElementCount = Math.max(0, (_b = config.listElementCount) !== null && _b !== void 0 ? _b : 1); const dynamicValues = !!config.dynamicValues; const generateLibrary = config.generateLibrary || 'faker'; const enumsAsTypes = (_c = config.enumsAsTypes) !== null && _c !== void 0 ? _c : false; const useTypeImports = (_d = config.useTypeImports) !== null && _d !== void 0 ? _d : false; const useImplementingTypes = (_e = config.useImplementingTypes) !== null && _e !== void 0 ? _e : false; const defaultNullableToNull = (_f = config.defaultNullableToNull) !== null && _f !== void 0 ? _f : false; const generatorLocale = config.locale || 'en'; const typeNamesMapping = config.typeNamesMapping || {}; // List of types that are enums const types = []; const typeVisitor = { EnumTypeDefinition: (node) => { const name = node.name.value; if (!types.find((enumType) => enumType.name === name)) { types.push({ name, type: 'enum', values: node.values ? node.values.map((node) => node.name.value) : [], }); } }, UnionTypeDefinition: (node) => { const name = node.name.value; if (!types.find((enumType) => enumType.name === name)) { types.push({ name, type: 'union', types: node.types, }); } }, ObjectTypeDefinition: (node) => { // This function triggered per each type const typeName = node.name.value; if (config.useImplementingTypes) { if (!types.find((objectType) => objectType.name === typeName)) { node.interfaces.length && types.push({ name: typeName, type: 'implement', types: node, }); } } }, ScalarTypeDefinition: (node) => { const name = node.name.value; if (!types.find((scalarType) => scalarType.name === name)) { types.push({ name, type: 'scalar', }); } }, }; const sharedGenerateMockOpts = { customScalars: config.scalars, defaultNullableToNull, dynamicValues, enumsAsTypes, enumsPrefix: config.enumsPrefix, enumValuesConvention, fieldGeneration: config.fieldGeneration, generateLibrary, generatorLocale, listElementCount, nonNull: false, prefix: config.prefix, terminateCircularRelationships: getTerminateCircularRelationshipsConfig(config), transformUnderscore, typeNamesConvention, typeNamesMapping, types, typesPrefix: config.typesPrefix, useImplementingTypes, useTypeImports, }; const visitor = { FieldDefinition: (node) => { const fieldName = node.name.value; return { name: fieldName, mockFn: (typeName) => { const value = generateMockValue({ typeName, fieldName, generatorMode: 'output', currentType: node.type, ...sharedGenerateMockOpts, }); return ` ${fieldName}: overrides && overrides.hasOwnProperty('${fieldName}') ? overrides.${fieldName}! : ${value},`; }, }; }, InputObjectTypeDefinition: (node) => { const fieldName = node.name.value; return { typeName: fieldName, mockFn: () => { let mockFieldsString = ''; const { directives } = node; const hasOneOfDirective = directives.some((directive) => directive.name.value === 'oneOf'); if (node.fields && node.fields.length > 0 && hasOneOfDirective) { const field = node.fields[0]; const value = generateMockValue({ typeName: fieldName, fieldName: field.name.value, currentType: field.type, generatorMode: 'input', ...sharedGenerateMockOpts, }); mockFieldsString = ` ...(override ? override : {${field.name.value} : ${value}}),`; } else if (node.fields) { mockFieldsString = node.fields .map((field) => { const value = generateMockValue({ typeName: fieldName, fieldName: field.name.value, currentType: field.type, generatorMode: 'input', ...sharedGenerateMockOpts, }); const valueWithOverride = `overrides && overrides.hasOwnProperty('${field.name.value}') ? overrides.${field.name.value}! : ${value}`; return ` ${field.name.value}: ${valueWithOverride},`; }) .join('\n'); } return getMockString(fieldName, mockFieldsString, typeNamesConvention, getTerminateCircularRelationshipsConfig(config), false, config.prefix, config.typesPrefix, transformUnderscore, typeNamesMapping, hasOneOfDirective); }, }; }, ObjectTypeDefinition: (node) => { // This function triggered per each type const typeName = node.name.value; const { fields } = node; return { typeName, mockFn: () => { const mockFields = fields ? fields.map(({ mockFn }) => mockFn(typeName)).join('\n') : ''; return getMockString(typeName, mockFields, typeNamesConvention, getTerminateCircularRelationshipsConfig(config), !!config.addTypename, config.prefix, config.typesPrefix, transformUnderscore, typeNamesMapping); }, }; }, InterfaceTypeDefinition: (node) => { const typeName = node.name.value; const { fields } = node; return { typeName, mockFn: () => { const mockFields = fields ? fields.map(({ mockFn }) => mockFn(typeName)).join('\n') : ''; return getMockString(typeName, mockFields, typeNamesConvention, getTerminateCircularRelationshipsConfig(config), !!config.addTypename, config.prefix, config.typesPrefix, transformUnderscore, typeNamesMapping); }, }; }, }; // run on the types first (0, plugin_helpers_1.oldVisit)(astNode, { leave: typeVisitor }); const result = (0, plugin_helpers_1.oldVisit)(astNode, { leave: visitor }); const { includedTypes, excludedTypes } = config; const shouldGenerateMockForType = (typeName) => { if (!typeName) { return true; } if (includedTypes && includedTypes.length > 0) { return includedTypes.includes(typeName); } if (excludedTypes && excludedTypes.length > 0) { return !excludedTypes.includes(typeName); } return true; }; const definitions = result.definitions.filter((definition) => !!definition && shouldGenerateMockForType(definition.typeName)); const typesFile = config.typesFile ? config.typesFile.replace(/\.[\w]+$/, '') : null; const typesFileImport = getImportTypes({ typeNamesConvention, definitions, types, typesFile, typesPrefix: config.typesPrefix, enumsPrefix: config.enumsPrefix, transformUnderscore: transformUnderscore, useTypeImports: config.useTypeImports, enumsAsTypes, typeNamesMapping, }); // Function that will generate the mocks. // We generate it after having visited because we need to distinct types from enums const mockFns = definitions .map(({ mockFn }) => mockFn) .filter((mockFn) => !!mockFn) .map((mockFn) => mockFn()) .join('\n'); const functionTokens = (0, mockValueGenerator_1.setupFunctionTokens)(generateLibrary, generatorLocale); let mockFile = ''; if (dynamicValues) mockFile += `${functionTokens.import}\n`; mockFile += typesFileImport; if (dynamicValues) mockFile += `\n${functionTokens.seed}\n`; mockFile += mockFns; if (dynamicValues) mockFile += `\n\n${functionTokens.seedFunction}`; mockFile += '\n'; return mockFile; }; exports.plugin = plugin; //# sourceMappingURL=index.js.map