@omnigraph/json-schema
Version:
This package generates GraphQL Schema from JSON Schema and sample JSON request and responses. You can define your root field endpoints like below in your GraphQL Config for example;
1,009 lines (1,006 loc) • 70.3 kB
JavaScript
/* eslint-disable no-case-declarations */
import { getNamedType, GraphQLBoolean, GraphQLFloat, GraphQLInt, GraphQLString, isNonNullType, } from 'graphql';
import { EnumTypeComposer, InputTypeComposer, InterfaceTypeComposer, isSomeInputTypeComposer, ListComposer, ObjectTypeComposer, ScalarTypeComposer, SchemaComposer, UnionTypeComposer, } from 'graphql-compose';
import { GraphQLBigInt, GraphQLByte, GraphQLDateTime, GraphQLEmailAddress, GraphQLIPv4, GraphQLIPv6, GraphQLJSON, GraphQLNegativeFloat, GraphQLNegativeInt, GraphQLNonEmptyString, GraphQLNonNegativeFloat, GraphQLNonNegativeInt, GraphQLNonPositiveFloat, GraphQLNonPositiveInt, GraphQLPositiveFloat, GraphQLPositiveInt, GraphQLTime, GraphQLTimestamp, GraphQLURL, GraphQLUUID, } from 'graphql-scalars';
import { visitJSONSchema } from 'json-machete';
import { sanitizeNameForGraphQL } from '@graphql-mesh/utils';
import { DictionaryDirective, DiscriminatorDirective, EnumDirective, ExampleDirective, FlattenDirective, LengthDirective, OneOfDirective, RegExpDirective, ResolveRootDirective, ResolveRootFieldDirective, TypeScriptDirective, } from './directives.js';
import { getJSONSchemaStringFormatScalarMap } from './getJSONSchemaStringFormatScalarMap.js';
import { getUnionTypeComposers } from './getUnionTypeComposers.js';
import { getValidTypeName } from './getValidTypeName.js';
import { GraphQLFile, GraphQLVoid } from './scalars.js';
const formatScalarMapWithoutAjv = {
byte: GraphQLByte,
binary: GraphQLFile,
'date-time': GraphQLDateTime,
time: GraphQLTime,
email: GraphQLEmailAddress,
ipv4: GraphQLIPv4,
ipv6: GraphQLIPv6,
uri: GraphQLURL,
uuid: GraphQLUUID,
'unix-time': GraphQLTimestamp,
int64: GraphQLBigInt,
int32: GraphQLInt,
decimal: GraphQLFloat,
float: GraphQLFloat,
};
const deepMergedInputTypeComposerFields = new WeakMap();
function deepMergeInputTypeComposerFields(existingInputTypeComposer, newInputTypeComposer) {
let mergedInputTypes = deepMergedInputTypeComposerFields.get(existingInputTypeComposer);
if (!mergedInputTypes) {
mergedInputTypes = new WeakSet();
deepMergedInputTypeComposerFields.set(existingInputTypeComposer, mergedInputTypes);
}
if (mergedInputTypes.has(newInputTypeComposer)) {
return;
}
mergedInputTypes.add(newInputTypeComposer);
const existingInputTypeComposerFields = existingInputTypeComposer.getFields();
const newInputTypeComposerFields = newInputTypeComposer.getFields();
for (const [newFieldKey, newFieldValue] of Object.entries(newInputTypeComposerFields)) {
const existingFieldValue = existingInputTypeComposerFields[newFieldKey];
if (!existingFieldValue) {
existingInputTypeComposerFields[newFieldKey] = newFieldValue;
}
else {
const existingFieldUnwrappedTC = typeof existingFieldValue.type?.getUnwrappedTC === 'function'
? existingFieldValue.type?.getUnwrappedTC()
: undefined;
const newFieldUnwrappedTC = typeof newFieldValue.type.getUnwrappedTC === 'function'
? newFieldValue.type.getUnwrappedTC()
: undefined;
if (existingFieldUnwrappedTC instanceof InputTypeComposer &&
newFieldUnwrappedTC instanceof InputTypeComposer) {
deepMergeInputTypeComposerFields(existingFieldUnwrappedTC, newFieldUnwrappedTC);
}
else {
existingInputTypeComposerFields[newFieldKey] = newFieldValue;
}
}
}
}
function deepMergeObjectTypeComposerFields(existingObjectTypeComposer, newObjectTypeComposer) {
const existingObjectTypeComposerFields = existingObjectTypeComposer.getFields();
const newObjectTypeComposerFields = newObjectTypeComposer.getFields();
for (const [newFieldKey, newFieldValue] of Object.entries(newObjectTypeComposerFields)) {
const existingFieldValue = existingObjectTypeComposerFields[newFieldKey];
if (!existingFieldValue) {
existingObjectTypeComposerFields[newFieldKey] = newFieldValue;
}
else {
const existingFieldUnwrappedTC = typeof existingFieldValue.type?.getUnwrappedTC === 'function'
? existingFieldValue.type?.getUnwrappedTC()
: undefined;
const newFieldUnwrappedTC = typeof newFieldValue.type.getUnwrappedTC === 'function'
? newFieldValue.type.getUnwrappedTC()
: undefined;
if (existingFieldUnwrappedTC instanceof ObjectTypeComposer &&
newFieldUnwrappedTC instanceof ObjectTypeComposer) {
deepMergeObjectTypeComposerFields(existingFieldUnwrappedTC, newFieldUnwrappedTC);
}
else {
if (newFieldUnwrappedTC &&
existingFieldUnwrappedTC &&
!isUnspecificType(newFieldUnwrappedTC) &&
isUnspecificType(existingFieldUnwrappedTC)) {
continue;
}
existingObjectTypeComposerFields[newFieldKey] = newFieldValue;
}
}
}
}
export function getComposerFromJSONSchema({ subgraphName, schema, logger, getScalarForFormat, }) {
const schemaComposer = new SchemaComposer();
const formatScalarMap = getJSONSchemaStringFormatScalarMap();
const getDefaultScalarForFormat = (format) => formatScalarMapWithoutAjv[format] || formatScalarMap.get(format);
const rootInputTypeNameComposerMap = {
QueryInput: () => schemaComposer.Query,
MutationInput: () => schemaComposer.Mutation,
SubscriptionInput: () => schemaComposer.Subscription,
};
return visitJSONSchema(schema, {
enter(subSchema, { path, visitedSubschemaResultMap }) {
if (typeof subSchema === 'boolean' || subSchema.title === 'Any') {
const typeComposer = schemaComposer.getAnyTC(GraphQLJSON);
return subSchema
? {
input: typeComposer,
output: typeComposer,
}
: undefined;
}
if (!subSchema) {
throw new Error(`Something is wrong with ${path}`);
}
if (subSchema.type === 'array') {
if (subSchema.items != null &&
typeof subSchema.items === 'object' &&
Object.keys(subSchema.items).length > 0) {
return {
// These are filled after enter
get input() {
const typeComposers = visitedSubschemaResultMap.get(subSchema.items);
return typeComposers.input.getTypePlural();
},
get output() {
const typeComposers = visitedSubschemaResultMap.get(subSchema.items);
return typeComposers.output.getTypePlural();
},
...subSchema,
};
}
if (subSchema.contains) {
// Scalars cannot be in union type
const typeComposer = schemaComposer.getAnyTC(GraphQLJSON).getTypePlural();
return {
input: typeComposer,
output: typeComposer,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
// If it doesn't have any clue
{
// const typeComposer = getGenericJSONScalar({
// schemaComposer,
// isInput: false,
// subSchema,
// validateWithJSONSchema,
// }).getTypePlural();
const typeComposer = schemaComposer.getAnyTC(GraphQLJSON).getTypePlural();
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
}
if (subSchema.pattern) {
let typeScriptType;
switch (subSchema.type) {
case 'number':
typeScriptType = 'number';
break;
case 'integer':
if (subSchema.format === 'int64') {
typeScriptType = 'bigint';
}
else {
typeScriptType = 'number';
}
break;
default:
typeScriptType = 'string';
break;
}
schemaComposer.addDirective(RegExpDirective);
schemaComposer.addDirective(TypeScriptDirective);
const typeComposer = schemaComposer.createScalarTC({
name: getValidTypeName({
schemaComposer,
isInput: false,
subSchema,
}),
directives: [
{
name: 'regexp',
args: {
subgraph: subgraphName,
pattern: subSchema.pattern,
},
},
{
name: 'typescript',
args: {
subgraph: subgraphName,
type: typeScriptType,
},
},
],
});
return {
input: typeComposer,
output: typeComposer,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
deprecated: subSchema.deprecated,
};
}
if (subSchema.const) {
const scalarTypeName = getValidTypeName({
schemaComposer,
isInput: false,
subSchema,
});
schemaComposer.addDirective(EnumDirective);
schemaComposer.addDirective(TypeScriptDirective);
schemaComposer.addDirective(ExampleDirective);
let enumValueName = sanitizeNameForGraphQL(subSchema.const.toString());
if (enumValueName === 'true' || enumValueName === 'false') {
enumValueName = enumValueName.toUpperCase();
}
const typeComposer = schemaComposer.createEnumTC({
name: scalarTypeName,
values: {
[enumValueName]: {
directives: [
{
name: 'enum',
args: {
subgraph: subgraphName,
value: JSON.stringify(subSchema.const),
},
},
],
},
},
directives: [
{
name: 'typescript',
args: {
subgraph: subgraphName,
type: JSON.stringify(subSchema.const),
},
},
{
name: 'example',
args: {
subgraph: subgraphName,
value: subSchema.const,
},
},
],
extensions: {
default: subSchema.const,
},
});
return {
input: typeComposer,
output: typeComposer,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
deprecated: subSchema.deprecated,
};
}
if (subSchema.enum && subSchema.type !== 'boolean') {
const values = {};
for (const value of subSchema.enum) {
let enumKey = sanitizeNameForGraphQL(value.toString());
if (enumKey === 'false' || enumKey === 'true' || enumKey === 'null') {
enumKey = enumKey.toUpperCase();
}
if (typeof enumKey === 'string' && enumKey.length === 0) {
enumKey = '_';
}
schemaComposer.addDirective(EnumDirective);
// Falsy values are ignored by GraphQL
// eslint-disable-next-line no-unneeded-ternary
const enumValue = value || value === 0 ? value : value?.toString();
const directives = [];
if (enumValue !== enumKey) {
directives.push({
name: 'enum',
args: {
subgraph: subgraphName,
value: JSON.stringify(enumValue),
},
});
}
values[enumKey] = {
directives,
value: enumValue,
};
}
const directives = [];
if (subSchema.examples?.length) {
schemaComposer.addDirective(ExampleDirective);
for (const example of subSchema.examples) {
directives.push({
name: 'example',
args: {
subgraph: subgraphName,
value: example,
},
});
}
}
const typeComposer = schemaComposer.createEnumTC({
name: getValidTypeName({
schemaComposer,
isInput: false,
subSchema,
}),
values,
description: subSchema.description,
directives,
extensions: {
default: subSchema.default,
},
});
return {
input: typeComposer,
output: typeComposer,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
if (Array.isArray(subSchema.type)) {
const validTypes = subSchema.type.filter((typeName) => typeName !== 'null');
if (validTypes.length === 1) {
subSchema.type = validTypes[0];
// continue with the single type
}
else {
const typeComposer = schemaComposer.getAnyTC(GraphQLJSON);
return {
input: typeComposer,
output: typeComposer,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
}
if (subSchema.format) {
const formatScalar = getScalarForFormat?.(subSchema.format) || getDefaultScalarForFormat(subSchema.format);
if (formatScalar) {
const typeComposer = schemaComposer.getAnyTC(formatScalar);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
}
if (subSchema.minimum === 0) {
const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLNonNegativeInt : GraphQLNonNegativeFloat);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
else if (subSchema.minimum > 0) {
const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLPositiveInt : GraphQLPositiveFloat);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
if (subSchema.maximum === 0) {
const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLNonPositiveInt : GraphQLNonPositiveFloat);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
else if (subSchema.maximum < 0) {
const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLNegativeInt : GraphQLNegativeFloat);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
if (subSchema.maximum > Number.MAX_SAFE_INTEGER ||
subSchema.minimum < Number.MIN_SAFE_INTEGER) {
const typeComposer = schemaComposer.getAnyTC(GraphQLBigInt);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
switch (subSchema.type) {
case 'boolean': {
const typeComposer = schemaComposer.getAnyTC(GraphQLBoolean);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
case 'null': {
const typeComposer = schemaComposer.getAnyTC(GraphQLVoid);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
case 'integer': {
const typeComposer = schemaComposer.getAnyTC(GraphQLInt);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
case 'number': {
const typeComposer = schemaComposer.getAnyTC(GraphQLFloat);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
case 'string': {
if (subSchema.minLength === 1 && subSchema.maxLength == null) {
const tc = schemaComposer.getAnyTC(GraphQLNonEmptyString);
return {
input: tc,
output: tc,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
if (subSchema.minLength || subSchema.maxLength) {
schemaComposer.addDirective(LengthDirective);
const typeComposer = schemaComposer.createScalarTC({
name: getValidTypeName({
schemaComposer,
isInput: false,
subSchema,
}),
description: subSchema.description,
directives: [
{
name: 'length',
args: {
subgraph: subgraphName,
min: subSchema.minLength,
max: subSchema.maxLength,
},
},
],
});
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
const typeComposer = schemaComposer.getAnyTC(GraphQLString);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
case 'object': {
switch (subSchema.title) {
case '_schema':
return {
output: schemaComposer,
...subSchema,
};
case 'Query':
return {
output: schemaComposer.Query,
...subSchema,
};
case 'Mutation':
return {
output: schemaComposer.Mutation,
...subSchema,
};
case 'Subscription': {
if (path === '/properties/subscription') {
return {
output: schemaComposer.Subscription,
...subSchema,
};
}
subSchema.title = 'Subscription_';
break;
}
}
}
}
if (subSchema.oneOf && !subSchema.properties) {
schemaComposer.addDirective(OneOfDirective);
const input = schemaComposer.createInputTC({
name: getValidTypeName({
schemaComposer,
isInput: true,
subSchema,
}),
fields: {},
directives: [
{
name: 'oneOf',
args: {
subgraph: subgraphName,
},
},
],
});
const extensions = {};
if (subSchema.$comment?.startsWith('statusCodeOneOfIndexMap:')) {
const statusCodeOneOfIndexMapStr = subSchema.$comment.replace('statusCodeOneOfIndexMap:', '');
const statusCodeOneOfIndexMap = JSON.parse(statusCodeOneOfIndexMapStr);
if (statusCodeOneOfIndexMap) {
extensions.statusCodeOneOfIndexMap = statusCodeOneOfIndexMap;
}
}
const output = schemaComposer.createUnionTC({
name: getValidTypeName({
schemaComposer,
isInput: false,
subSchema,
}),
description: subSchema.description,
types: [],
extensions,
});
return {
input,
output,
...subSchema,
};
}
if (subSchema.properties ||
subSchema.allOf ||
subSchema.anyOf ||
subSchema.additionalProperties) {
if (subSchema.title === 'Any') {
const typeComposer = schemaComposer.getAnyTC(GraphQLJSON);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
readOnly: subSchema.readOnly,
writeOnly: subSchema.writeOnly,
default: subSchema.default,
deprecated: subSchema.deprecated,
};
}
const config = {
name: getValidTypeName({
schemaComposer,
isInput: false,
subSchema,
}),
description: subSchema.description,
fields: {},
directives: [],
extensions: {
default: subSchema.default,
},
};
if (subSchema.examples?.length) {
schemaComposer.addDirective(ExampleDirective);
for (const example of subSchema.examples) {
config.directives.push({
name: 'example',
args: {
subgraph: subgraphName,
value: example,
},
});
}
}
if (subSchema.discriminator?.propertyName) {
schemaComposer.addDirective(DiscriminatorDirective);
}
const directives = [];
if (subSchema.examples?.length) {
schemaComposer.addDirective(ExampleDirective);
for (const example of subSchema.examples) {
directives.push({
name: 'example',
args: {
subgraph: subgraphName,
value: example,
},
});
}
}
return {
input: schemaComposer.createInputTC({
name: getValidTypeName({
schemaComposer,
isInput: true,
subSchema,
}),
description: subSchema.description,
fields: {},
directives,
extensions: {
default: subSchema.default,
},
}),
output: subSchema.discriminator
? schemaComposer.createInterfaceTC(config)
: schemaComposer.createObjectTC(config),
...subSchema,
...(subSchema.properties ? { properties: { ...subSchema.properties } } : {}),
...(subSchema.allOf ? { allOf: [...subSchema.allOf] } : {}),
...(subSchema.additionalProperties
? {
additionalProperties: subSchema.additionalProperties === true
? true
: { ...subSchema.additionalProperties },
}
: {}),
...(subSchema.discriminatorMapping
? { discriminatorMapping: { ...subSchema.discriminatorMapping } }
: {}),
};
}
return subSchema;
},
leave(subSchemaAndTypeComposers, { path }) {
// const validateWithJSONSchema = getValidateFnForSchemaPath(ajv, path, schema);
const subSchemaOnly = {
...subSchemaAndTypeComposers,
input: undefined,
output: undefined,
};
if (subSchemaOnly.discriminator?.propertyName) {
schemaComposer.addDirective(DiscriminatorDirective);
const discriminatorArgs = {
subgraph: subgraphName,
field: subSchemaOnly.discriminator.propertyName,
};
if (subSchemaOnly.discriminator.mapping) {
const mappingByName = {};
for (const discriminatorValue in subSchemaOnly.discriminatorMapping) {
const discType = subSchemaOnly.discriminatorMapping[discriminatorValue];
mappingByName[discriminatorValue] = discType.output.getTypeName();
}
discriminatorArgs.mapping = mappingByName;
}
subSchemaAndTypeComposers.output.setDirectiveByName('discriminator', discriminatorArgs);
}
if (subSchemaAndTypeComposers.oneOf && !subSchemaAndTypeComposers.properties) {
const isPlural = subSchemaAndTypeComposers.oneOf.some(({ output }) => output instanceof ListComposer);
if (isPlural) {
const { input, output, flatten } = getUnionTypeComposers({
subgraphName,
schemaComposer,
typeComposersList: subSchemaAndTypeComposers.oneOf.map(({ input, output }) => ({
input: input.ofType || input,
output: output.ofType || output,
})),
subSchemaAndTypeComposers,
logger,
});
return {
input: input.getTypePlural(),
output: output instanceof ListComposer
? output
: output.getTypePlural(),
nullable: subSchemaAndTypeComposers.nullable,
default: subSchemaAndTypeComposers.default,
readOnly: subSchemaAndTypeComposers.readOnly,
writeOnly: subSchemaAndTypeComposers.writeOnly,
flatten,
deprecated: subSchemaAndTypeComposers.deprecated,
};
}
return getUnionTypeComposers({
subgraphName,
schemaComposer,
typeComposersList: subSchemaAndTypeComposers.oneOf,
subSchemaAndTypeComposers,
logger,
});
}
const fieldMap = {};
const inputFieldMap = {};
let isList = false;
if (subSchemaAndTypeComposers.allOf) {
let ableToUseGraphQLInputObjectType = true;
for (const maybeTypeComposers of subSchemaAndTypeComposers.allOf) {
let { input: inputTypeComposer, output: outputTypeComposer } = maybeTypeComposers;
if (inputTypeComposer instanceof ListComposer) {
isList = true;
inputTypeComposer = inputTypeComposer.ofType;
}
if (outputTypeComposer instanceof ListComposer) {
isList = true;
outputTypeComposer = outputTypeComposer.ofType;
}
if (inputTypeComposer instanceof ScalarTypeComposer ||
inputTypeComposer instanceof EnumTypeComposer) {
ableToUseGraphQLInputObjectType = false;
}
else {
const inputTypeElemFieldMap = inputTypeComposer.getFields();
for (const fieldName in inputTypeElemFieldMap) {
const newInputField = inputTypeElemFieldMap[fieldName];
const existingInputField = inputFieldMap[fieldName];
if (!existingInputField) {
inputFieldMap[fieldName] = newInputField;
}
else {
/*
If the new field collides with an existing field:
- If both the existing and the new field have an input type composer, combine their subfields
- Otherwise, replace the existing field with the new one
*/
const existingInputFieldUnwrappedTC = typeof existingInputField.type?.getUnwrappedTC === 'function'
? existingInputField.type.getUnwrappedTC()
: undefined;
const newInputFieldUnwrappedTC = typeof newInputField.type?.getUnwrappedTC === 'function'
? newInputField.type.getUnwrappedTC()
: undefined;
if (existingInputFieldUnwrappedTC instanceof InputTypeComposer &&
newInputFieldUnwrappedTC instanceof InputTypeComposer) {
deepMergeInputTypeComposerFields(existingInputFieldUnwrappedTC, newInputFieldUnwrappedTC);
}
else {
inputFieldMap[fieldName] = newInputField;
}
}
}
}
if (isSomeInputTypeComposer(outputTypeComposer)) {
schemaComposer.addDirective(ResolveRootDirective);
fieldMap[outputTypeComposer.getTypeName()] = {
type: outputTypeComposer,
directives: [
{
name: 'resolveRoot',
args: {
subgraph: subgraphName,
},
},
],
};
}
else if (outputTypeComposer instanceof UnionTypeComposer) {
const outputTCElems = outputTypeComposer.getTypes();
for (const outputTCElem of outputTCElems) {
const outputTypeElemFieldMap = outputTCElem.getFields();
for (const fieldName in outputTypeElemFieldMap) {
const field = outputTypeElemFieldMap[fieldName];
fieldMap[fieldName] = field;
}
}
}
else {
if (outputTypeComposer instanceof InterfaceTypeComposer) {
subSchemaAndTypeComposers.output.addInterface(outputTypeComposer);
schemaComposer.addSchemaMustHaveType(subSchemaAndTypeComposers.output);
}
const typeElemFieldMap = outputTypeComposer.getFields();
for (const fieldName in typeElemFieldMap) {
const newField = typeElemFieldMap[fieldName];
const existingField = fieldMap[fieldName];
if (!existingField) {
fieldMap[fieldName] = newField;
}
else {
/*
If the new field collides with an existing field:
- If both the existing and the new field have an object type composer, combine their subfields
- Otherwise, replace the existing field with the new one
*/
const existingFieldUnwrappedTC = typeof existingField.type?.getUnwrappedTC === 'function'
? existingField.type.getUnwrappedTC()
: undefined;
const newFieldUnwrappedTC = typeof newField.type?.getUnwrappedTC === 'function'
? newField.type.getUnwrappedTC()
: undefined;
if (existingFieldUnwrappedTC instanceof ObjectTypeComposer &&
newFieldUnwrappedTC instanceof ObjectTypeComposer) {
deepMergeObjectTypeComposerFields(existingFieldUnwrappedTC, newFieldUnwrappedTC);
}
else {
if (newFieldUnwrappedTC &&
existingFieldUnwrappedTC &&
!isUnspecificType(newFieldUnwrappedTC) &&
isUnspecificType(existingFieldUnwrappedTC)) {
continue;
}
fieldMap[fieldName] = newField;
}
}
}
}
}
if (subSchemaAndTypeComposers.examples?.length) {
schemaComposer.addDirective(ExampleDirective);
const directives = subSchemaAndTypeComposers.output.getDirectives() || [];
for (const example of subSchemaAndTypeComposers.examples) {
directives.push({
name: 'example',
args: {
subgraph: subgraphName,
value: example,
},
});
}
subSchemaAndTypeComposers.output.setDirectives(directives);
}
subSchemaAndTypeComposers.output.addFields(fieldMap);
subSchemaAndTypeComposers.output.setExtensions({
// validateWithJSONSchema,
default: subSchemaAndTypeComposers.default,
});
if (ableToUseGraphQLInputObjectType) {
subSchemaAndTypeComposers.input.addFields(inputFieldMap);
if (subSchemaAndTypeComposers.examples?.length) {
schemaComposer.addDirective(ExampleDirective);
const directives = subSchemaAndTypeComposers.input.getDirectives() || [];
for (const example of subSchemaAndTypeComposers.examples) {
directives.push({
name: 'example',
args: {
subgraph: subgraphName,
value: example,
},
});
}
subSchemaAndTypeComposers.input.setDirectives(directives);
}
subSchemaAndTypeComposers.input.setExtensions({
default: subSchemaAndTypeComposers.default,
});
}
else {
subSchemaAndTypeComposers.input = schemaComposer.getAnyTC(GraphQLJSON);
}
}
if (subSchemaAndTypeComposers.anyOf) {
// It should not have `required` because it is `anyOf` not `allOf`
let ableToUseGraphQLInputObjectType = true;
for (const typeComposers of subSchemaAndTypeComposers.anyOf) {
let { input: inputTypeComposer, output: outputTypeComposer } = typeComposers;
if (inputTypeComposer instanceof ListComposer ||
outputTypeComposer instanceof ListComposer) {
isList = true;
inputTypeComposer = inputTypeComposer.ofType;
outputTypeComposer = outputTypeComposer.ofType;
}
if (inputTypeComposer instanceof ScalarTypeComposer ||
inputTypeComposer instanceof EnumTypeComposer) {
ableToUseGraphQLInputObjectType = false;
}
else {
const inputTypeElemFieldMap = inputTypeComposer.getFields();
for (const fieldName in inputTypeElemFieldMap) {
// In case of conflict set it to JSON
// TODO: But instead we can convert that field into a oneOf of all possible types
if (inputFieldMap[fieldName]) {
let existingType = inputFieldMap[fieldName].type;
if (typeof existingType === 'function') {
existingType = existingType();
}
let newType = inputTypeElemFieldMap[fieldName].type;
if (typeof newType === 'function') {
newType = newType();
}
const newTypeName = newType.getTypeName().replace('!', '');
const existingTypeName = existingType.getTypeName().replace('!', '');
if (existingTypeName !== newTypeName) {
if (newTypeName !== 'JSON') {
inputFieldMap[fieldName] = {
type: schemaComposer.getAnyTC(GraphQLJSON),
};
}
if (existingTypeName === 'JSON') {
const field = inputTypeElemFieldMap[fieldName];
inputFieldMap[fieldName] = isNonNullType(field.type.getType())
? {
...field,
type: () => field.type.ofType,
}
: field;
}
}
}
else {
const field = inputTypeElemFieldMap[fieldName];
inputFieldMap[fieldName] = isNonNullType(field.type.getType())
? {
...field,
type: () => field.type.ofType,
}
: field;
}
}
}
if (outputTypeComposer instanceof ScalarTypeComposer) {
const typeName = outputTypeComposer.getTypeName();
// In case of conflict set it to JSON
// TODO: But instead we can convert that field into a union of all possible types
if (fieldMap[typeName]) {
const existingTypeName = fieldMap[typeName]?.type?.getTypeName();
if (existingTypeName === 'JSON') {
schemaComposer.addDirective(ResolveRootDirective);
fieldMap[typeName] = {
type: outputTypeComposer,
directives: [
{
name: 'resolveRoot',
args: {
subgraph: subgraphName,
},
},