@aws-amplify/graphql-schema-generator
Version:
Amplify GraphQL schema generator
369 lines • 14.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.printSchema = exports.convertToGraphQLFieldName = exports.convertToGraphQLTypeName = exports.getRefersToDirective = exports.isComputeExpression = exports.generateGraphQLSchema = void 0;
const graphql_transformer_core_1 = require("@aws-amplify/graphql-transformer-core");
const graphql_1 = require("graphql");
const schema_overrides_1 = require("./schema-overrides");
const pluralize_1 = require("pluralize");
const graphql_transformer_common_1 = require("graphql-transformer-common");
const generateGraphQLSchema = (schema, existingSchemaDocument) => {
const models = schema.getModels();
const document = {
kind: graphql_1.Kind.DOCUMENT,
definitions: [],
};
const { includeTables, excludeTables } = getIncludeExcludeConfig(existingSchemaDocument);
models.forEach((model) => {
if (includeTables.length > 0 && !includeTables.includes(model.getName())) {
return;
}
if (excludeTables.length > 0 && excludeTables.includes(model.getName())) {
return;
}
const primaryKey = model.getPrimaryKey();
if (!primaryKey) {
return;
}
const type = constructObjectType(model);
const fields = model.getFields();
const primaryKeyFields = primaryKey === null || primaryKey === void 0 ? void 0 : primaryKey.getFields();
fields.forEach((f) => {
if (isEnum(f.type)) {
const enumType = constructEnumType(getBaseType(f.type));
if (!document.definitions.find((d) => d.name.value === enumType.name)) {
document.definitions.push(enumType.serialize());
}
}
const field = convertInternalFieldTypeToGraphQL(f, primaryKeyFields.includes(f.name));
type.fields.push(field);
});
addPrimaryKey(type, model.getPrimaryKey());
addIndexes(type, model.getIndexes());
document.definitions.push(type.serialize());
});
const documentWithOverrides = (0, schema_overrides_1.applySchemaOverrides)(document, existingSchemaDocument);
const schemaStr = (0, exports.printSchema)(documentWithOverrides);
return schemaStr;
};
exports.generateGraphQLSchema = generateGraphQLSchema;
const isEnum = (type) => {
if (type.kind === 'NonNull' || type.kind === 'List') {
return isEnum(type.type);
}
return type.kind === 'Enum';
};
const getBaseType = (type) => {
if (type.kind === 'NonNull' || type.kind === 'List') {
return getBaseType(type.type);
}
return type;
};
const convertInternalFieldTypeToGraphQL = (field, isPrimaryKeyField) => {
var _a;
const fieldName = field.name;
const typeWrappers = [];
let fieldType = field.type;
while (fieldType.kind !== 'Scalar' && fieldType.kind !== 'Custom' && fieldType.kind !== 'Enum') {
typeWrappers.push(fieldType.kind);
fieldType = fieldType.type;
}
const fieldDirectives = [];
const fieldTypeName = (0, exports.convertToGraphQLFieldName)(fieldName);
const fieldNameNeedsMapping = fieldTypeName !== fieldName;
if (fieldNameNeedsMapping) {
const fieldNameMappingDirective = (0, exports.getRefersToDirective)(fieldName);
fieldDirectives.push(fieldNameMappingDirective);
}
const fieldHasDefaultValue = (field === null || field === void 0 ? void 0 : field.default) && ((_a = field === null || field === void 0 ? void 0 : field.default) === null || _a === void 0 ? void 0 : _a.value);
const fieldIsOptional = fieldHasDefaultValue && !isPrimaryKeyField;
if (fieldHasDefaultValue) {
const defaultStringValue = String(field.default.value);
if (!(0, exports.isComputeExpression)(defaultStringValue)) {
fieldDirectives.push(new graphql_transformer_core_1.DirectiveWrapper({
kind: graphql_1.Kind.DIRECTIVE,
name: {
kind: 'Name',
value: 'default',
},
arguments: [
{
kind: 'Argument',
name: {
kind: 'Name',
value: 'value',
},
value: {
kind: 'StringValue',
value: defaultStringValue,
},
},
],
}));
}
}
const result = new graphql_transformer_core_1.FieldWrapper({
kind: 'FieldDefinition',
name: {
kind: 'Name',
value: fieldTypeName,
},
type: {
kind: 'NamedType',
name: {
kind: 'Name',
value: fieldType.name,
},
},
directives: fieldDirectives,
});
while (typeWrappers.length > 0) {
const wrapperType = typeWrappers.pop();
if (wrapperType === 'List') {
result.wrapListType();
}
else if (wrapperType === 'NonNull' && !fieldIsOptional) {
result.makeNonNullable();
}
}
return result;
};
const constructObjectType = (model) => {
const modelName = model.getName();
const directives = [];
const modelTypeName = (0, exports.convertToGraphQLTypeName)(modelName);
const modelNameNeedsMapping = modelTypeName !== modelName;
if (modelNameNeedsMapping) {
const modelNameMappingDirective = (0, exports.getRefersToDirective)(modelName);
directives.push(modelNameMappingDirective);
}
const modelDirective = {
kind: graphql_1.Kind.DIRECTIVE,
name: {
kind: 'Name',
value: 'model',
},
};
directives.push(modelDirective);
return new graphql_transformer_core_1.ObjectDefinitionWrapper({
kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION,
name: {
kind: 'Name',
value: modelTypeName,
},
fields: [],
directives: directives,
});
};
const constructEnumType = (type) => {
if (!validateEnumValues(type.values)) {
throw new Error(`Enum "${type.name}" (values: ${type.values.join(',')}) contains one or more invalid values. Enum values must match the regex [_A-Za-z][_0-9A-Za-z]*.`);
}
const enumValues = type.values.map((t) => {
return {
kind: graphql_1.Kind.ENUM_VALUE_DEFINITION,
name: {
kind: 'Name',
value: t,
},
};
});
const enumType = new graphql_transformer_core_1.EnumWrapper({
kind: graphql_1.Kind.ENUM_TYPE_DEFINITION,
name: {
kind: 'Name',
value: type.name,
},
values: enumValues,
});
return enumType;
};
const validateEnumValues = (values) => {
const regex = new RegExp(/^[_A-Za-z][_0-9A-Za-z]*$/);
const containsValidValues = values.every((value) => regex.test(value));
return containsValidValues;
};
const addIndexes = (type, indexes) => {
indexes.forEach((index) => {
const firstField = (0, exports.convertToGraphQLFieldName)(index.getFields()[0]);
const indexField = type.getField(firstField);
const indexArguments = [];
indexArguments.push({
kind: 'Argument',
name: {
kind: 'Name',
value: 'name',
},
value: {
kind: 'StringValue',
value: index.name,
},
});
if (index.getFields().length > 1) {
indexArguments.push({
kind: 'Argument',
name: {
kind: 'Name',
value: 'sortKeyFields',
},
value: {
kind: 'ListValue',
values: index
.getFields()
.slice(1)
.map((k) => {
return {
kind: 'StringValue',
value: (0, exports.convertToGraphQLFieldName)(k),
};
}),
},
});
}
indexField.directives.push(new graphql_transformer_core_1.DirectiveWrapper({
kind: graphql_1.Kind.DIRECTIVE,
name: {
kind: 'Name',
value: 'index',
},
arguments: indexArguments,
}));
});
};
const addPrimaryKey = (type, primaryKey) => {
if (!primaryKey) {
return;
}
const firstField = (0, exports.convertToGraphQLFieldName)(primaryKey.getFields()[0]);
const primaryKeyField = type.getField(firstField);
const keyArguments = [];
if (primaryKey.getFields().length > 1) {
keyArguments.push({
kind: 'Argument',
name: {
kind: 'Name',
value: 'sortKeyFields',
},
value: {
kind: 'ListValue',
values: primaryKey
.getFields()
.slice(1)
.map((k) => {
return {
kind: 'StringValue',
value: (0, exports.convertToGraphQLFieldName)(k),
};
}),
},
});
}
primaryKeyField.directives.push(new graphql_transformer_core_1.DirectiveWrapper({
kind: graphql_1.Kind.DIRECTIVE,
name: {
kind: 'Name',
value: 'primaryKey',
},
arguments: keyArguments,
}));
};
const getIncludeExcludeConfig = (document) => {
var _a, _b;
const emptyConfig = { includeTables: [], excludeTables: [] };
if (!document) {
return emptyConfig;
}
const amplifyInputType = document.definitions.find((d) => d.kind === 'InputObjectTypeDefinition' && d.name.value === 'AMPLIFY');
if (!amplifyInputType) {
return emptyConfig;
}
const includeFieldNodeValue = (_a = amplifyInputType.fields.find((f) => f.name.value === 'include')) === null || _a === void 0 ? void 0 : _a.defaultValue;
const excludeFieldNodeValue = (_b = amplifyInputType.fields.find((f) => f.name.value === 'exclude')) === null || _b === void 0 ? void 0 : _b.defaultValue;
if (includeFieldNodeValue && includeFieldNodeValue.kind !== 'ListValue') {
throw new Error('Invalid value for include option. Please check your GraphQL schema.');
}
if (excludeFieldNodeValue && excludeFieldNodeValue.kind !== 'ListValue') {
throw new Error('Invalid value for include option. Please check your GraphQL schema.');
}
const includeTables = includeFieldNodeValue === null || includeFieldNodeValue === void 0 ? void 0 : includeFieldNodeValue.values.map((v) => v.value);
const excludeTables = excludeFieldNodeValue === null || excludeFieldNodeValue === void 0 ? void 0 : excludeFieldNodeValue.values.map((v) => v.value);
if (includeTables && includeTables.length > 0 && excludeTables && excludeTables.length > 0) {
throw new Error('Cannot specify both include and exclude options. Please check your GraphQL schema.');
}
return {
includeTables: includeTables || [],
excludeTables: excludeTables || [],
};
};
const isComputeExpression = (value) => {
const isSimpleComputedExpression = value.match(/^[a-zA-Z0-9]+\(.*\)/);
const isComplexComputedExpression = value.match(/^\([a-zA-Z0-9]+\(.*\)\)/);
if (isSimpleComputedExpression || isComplexComputedExpression) {
return true;
}
return false;
};
exports.isComputeExpression = isComputeExpression;
const getRefersToDirective = (name) => {
return {
kind: graphql_1.Kind.DIRECTIVE,
name: {
kind: 'Name',
value: 'refersTo',
},
arguments: [
{
kind: 'Argument',
name: {
kind: 'Name',
value: 'name',
},
value: {
kind: 'StringValue',
value: name,
},
},
],
};
};
exports.getRefersToDirective = getRefersToDirective;
const convertToGraphQLTypeName = (modelName) => {
const cleanedInput = cleanMappedName(modelName);
return (0, pluralize_1.singular)((0, graphql_transformer_common_1.toPascalCase)(cleanedInput === null || cleanedInput === void 0 ? void 0 : cleanedInput.split('_')));
};
exports.convertToGraphQLTypeName = convertToGraphQLTypeName;
const convertToGraphQLFieldName = (fieldName) => {
const cleanedInput = cleanMappedName(fieldName, true);
return (0, graphql_transformer_common_1.toCamelCase)(cleanedInput === null || cleanedInput === void 0 ? void 0 : cleanedInput.split('_'));
};
exports.convertToGraphQLFieldName = convertToGraphQLFieldName;
const cleanMappedName = (name, isField = false) => {
if (!(name === null || name === void 0 ? void 0 : name.match(/[a-zA-Z]/))) {
const suffix = name === null || name === void 0 ? void 0 : name.replace(/[^0-9]+/g, '');
return isField ? `field${suffix}` : `Model${suffix}`;
}
const cleanedInput = name
.replace(/^[^a-zA-Z]+/, '')
.replace(/[^a-zA-Z0-9_]+/g, '_')
.trim();
return cleanedInput;
};
const printSchema = (document) => {
const sortedDocument = sortDocument(document);
return (0, graphql_1.print)(sortedDocument);
};
exports.printSchema = printSchema;
const sortDocument = (document) => {
const documentWrapper = document;
documentWrapper.definitions = [...document === null || document === void 0 ? void 0 : document.definitions].sort((def1, def2) => {
var _a, _b, _c, _d;
if (((_a = def1 === null || def1 === void 0 ? void 0 : def1.name) === null || _a === void 0 ? void 0 : _a.value) > ((_b = def2 === null || def2 === void 0 ? void 0 : def2.name) === null || _b === void 0 ? void 0 : _b.value)) {
return 1;
}
if (((_c = def1 === null || def1 === void 0 ? void 0 : def1.name) === null || _c === void 0 ? void 0 : _c.value) < ((_d = def2 === null || def2 === void 0 ? void 0 : def2.name) === null || _d === void 0 ? void 0 : _d.value)) {
return -1;
}
return 0;
});
return documentWrapper;
};
//# sourceMappingURL=generate-schema.js.map