UNPKG

charlotte-graphql

Version:

Generates GraphQL type definitions and resolvers off of a concise spec.

147 lines (116 loc) 3.85 kB
'use strict'; const _ = require('lodash'); const pluralize = require('pluralize'); const scalarTypeNames = ['String', 'ID', 'Int', 'Float', 'Boolean']; const defaultFields = { uuid: 'ID!', _created: 'String!', _updated: 'String!' }; const createType = typeName => { let plural = pluralize(typeName); const isScalar = isScalarType(typeName); if (typeName === plural) { plural = plural + '_s'; } return { name: typeName, nameCamel: _.camelCase(typeName), plural, pluralCamel: _.camelCase(plural), fields: {}, isScalar: isScalar }; }; const expandTypes = source => { const expanded = scalarTypeNames.reduce((idx, typeName) => { return Object.assign(idx, { [typeName]: createType(typeName) }); }, {}); const queue = []; const dequeue = (cursor = 0) => { const f = queue[cursor]; if (f) { f(); dequeue(cursor + 1); } }; // Expand source types into a tree of referencing types and fields _.each(source, (_sourceFields, typeName) => { const type = expanded[typeName] = createType(typeName); const sourceFields = Object.assign({}, defaultFields, _sourceFields); _.each(sourceFields, (typeDescriptor, fieldName) => { const isNullable = typeDescriptor.slice(-1) !== '!'; const nullableTypeName = unrequire(typeDescriptor); const field = type.fields[fieldName] = { name: fieldName, parentType: type, typeDescriptor, type: null, reciprocal: null, reciprocalOf: null, isNullable, appendRequirement: str => str + (isNullable? '': '!') }; if (expanded[nullableTypeName]) { field.type = expanded[nullableTypeName]; } else { queue.push(() => { field.type = expanded[nullableTypeName]; if (!field.type) { throw new Error(`${typeName}.${fieldName} refers to type "${nullableTypeName}", which is not defined`) } }); } if (isScalarType(typeDescriptor)) { return; } const addReciprocals = () => { const siblingFields = Object .values(field.parentType.fields) .filter(_field => _field.name !== field.name); const relatedFieldsStillNotResolved = siblingFields.some(_field => !_field.type); if (relatedFieldsStillNotResolved) { queue.push(addReciprocals); return; } const fieldNameWithIntStripped = field.name.replace(/[0-9]+$/, ''); const siblingsWithType = siblingFields.filter(_field => { return _field.type.name === field.type.name; }); const combinableSiblings = siblingsWithType.filter(_field => { return _field.name.replace(/[0-9]+$/, '') === fieldNameWithIntStripped; }); const _name = combinableSiblings.length === siblingsWithType.length ? type.pluralCamel : siblingsWithType.length === 0 ? type.pluralCamel : type.pluralCamel + _.upperFirst(fieldNameWithIntStripped); const _typeDescriptor = `[${field.parentType.name}!]!`; field.reciprocal = field.type.fields[_name] = { name: _name, parentType: field.type, typeDescriptor: _typeDescriptor, type: field.parentType, reciprocalOf: [field].concat(combinableSiblings), reciprocal: null, isNullable: false, appendRequirement: str => str.replace('!', '') + '!' } }; queue.push(addReciprocals); }); }); dequeue(); return _.omit(expanded, scalarTypeNames); }; const isScalarType = type => { return new RegExp(`^(${scalarTypeNames.join('|')})!?$`).test(type); }; const unrequire = str => str.replace('!', ''); module.exports = { expandTypes, isScalarType, unrequire };