diff --git a/packages/graphql-mesh/patches/@graphql-tools+stitch+9.0.3.patch b/packages/graphql-mesh/patches/@graphql-tools+stitch+9.0.3.patch index a250573..ef78d76 100644 --- a/packages/graphql-mesh/patches/@graphql-tools+stitch+9.0.3.patch +++ b/packages/graphql-mesh/patches/@graphql-tools+stitch+9.0.3.patch @@ -12,7 +12,7 @@ index 5e752ad..3b487f5 100644 const currentNamedType = (0, graphql_1.getNamedType)(c.type); if (finalNamedType.toString() !== currentNamedType.toString()) { diff --git a/node_modules/@graphql-tools/stitch/cjs/typeCandidates.js b/node_modules/@graphql-tools/stitch/cjs/typeCandidates.js -index c915942..ae923fe 100644 +index c915942..f617985 100644 --- a/node_modules/@graphql-tools/stitch/cjs/typeCandidates.js +++ b/node_modules/@graphql-tools/stitch/cjs/typeCandidates.js @@ -119,6 +119,33 @@ function buildTypes({ typeCandidates, directives, stitchingInfo, rootTypeNames, @@ -33,9 +33,9 @@ index c915942..ae923fe 100644 + otherCandidates.forEach((otherCandidate) => { + // Add fields from other candidates to the final interface + Object.keys(otherCandidate.type._fields).forEach(field => { -+ if (finalI.type._fields[field] === undefined) { -+ finalI.type._fields[field] = otherCandidate.type._fields[field]; -+ } ++ if (finalI.type._fields[field] === undefined) { ++ finalI.type._fields[field] = otherCandidate.type._fields[field]; ++ } + }) + }); + typeCandidates[typeName] = [finalI]; @@ -49,7 +49,7 @@ index c915942..ae923fe 100644 typeMap[typeName] = (0, mergeCandidates_js_1.mergeCandidates)(typeName, typeCandidates[typeName], typeMergingOptions); } else { -@@ -128,6 +155,61 @@ function buildTypes({ typeCandidates, directives, stitchingInfo, rootTypeNames, +@@ -128,6 +155,72 @@ function buildTypes({ typeCandidates, directives, stitchingInfo, rootTypeNames, typeMap[typeName] = candidateSelector(typeCandidates[typeName]).type; } } @@ -64,48 +64,59 @@ index c915942..ae923fe 100644 + let duplicateFields = {}; + + typeInterfaces.forEach(i => { -+ const iFields = typeMap[i.name].getFields(); -+ Object.keys(iFields).forEach(keyName => { -+ if (uniqueFields[keyName] === undefined) { -+ uniqueFields[keyName] = "defined"; -+ } -+ else { -+ duplicateFields[keyName] = iFields[keyName]; -+ } -+ }) ++ const iFields = typeMap[i.name].getFields(); ++ Object.keys(iFields).forEach(keyName => { ++ if (uniqueFields[keyName] === undefined) { ++ uniqueFields[keyName] = "defined"; ++ } ++ else { ++ duplicateFields[keyName] = iFields[keyName]; ++ } ++ }) + }) + // Ensure duplicate fields are consistent across implementations + Object.keys(duplicateFields).forEach(field => { -+ if (type.getFields()[field] !== undefined) { -+ type._fields[field] = duplicateFields[field]; -+ } -+ typeInterfaces.forEach(i => { -+ const iFields = typeMap[i.name].getFields(); ++ if (type.getFields()[field] !== undefined) { ++ type._fields[field] = duplicateFields[field]; ++ } ++ typeInterfaces.forEach(i => { ++ const iFields = typeMap[i.name].getFields(); + -+ if (iFields[field] !== undefined) { -+ typeMap[i.name]._fields[field] = duplicateFields[field]; -+ } -+ }) ++ if (iFields[field] !== undefined) { ++ typeMap[i.name]._fields[field] = duplicateFields[field]; ++ } ++ }) + }) + } + } + }); + // Ensure object types implement all fields from their interfaces + Object.values(typeMap).forEach((type) => { -+ if (type.constructor.name === "GraphQLObjectType") { -+ const typeInterfaces = type.getInterfaces(); ++ if (type.constructor.name === "GraphQLObjectType") { ++ const typeInterfaces = type.getInterfaces(); + -+ if (typeInterfaces.length !== 0) { -+ type._fields = type.getFields(); ++ if (typeInterfaces.length !== 0) { ++ type._fields = type.getFields(); + -+ typeInterfaces.forEach(i => { -+ const iFields = typeMap[i.name].getFields(); -+ Object.keys(iFields).forEach(keyName => { -+ type._fields[keyName] = iFields[keyName]; -+ }) -+ }) -+ } -+ } ++ typeInterfaces.forEach(i => { ++ const iFields = typeMap[i.name].getFields(); ++ Object.keys(iFields).forEach(keyName => { ++ type._fields[keyName] = iFields[keyName]; ++ }) ++ }) ++ } ++ } ++ }) ++ // Add a default type for all the interfaces ++ Object.values(typeMap).forEach((type) => { ++ if (type.constructor.name === "GraphQLInterfaceType") { ++ typeMap[`Default__${type.name}`] = new graphql_1.GraphQLObjectType({ ++ name: `Default__${type.name}`, ++ fields: {}, ++ interfaces: [type] ++ }) ++ typeMap[`Default__${type.name}`]._fields = typeMap[`${type.name}`]._fields ++ } + }) + return (0, utils_1.rewireTypes)(typeMap, directives); diff --git a/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch b/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch index ddcb133..cbfaf5f 100644 --- a/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch +++ b/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch @@ -1,28 +1,28 @@ diff --git a/node_modules/@omnigraph/json-schema/cjs/addRootFieldResolver.js b/node_modules/@omnigraph/json-schema/cjs/addRootFieldResolver.js -index 8006747..dfc6825 100644 +old mode 100644 +new mode 100755 +index 8006747..f2af17c --- a/node_modules/@omnigraph/json-schema/cjs/addRootFieldResolver.js +++ b/node_modules/@omnigraph/json-schema/cjs/addRootFieldResolver.js -@@ -35,6 +35,23 @@ function addHTTPRootFieldResolver(schema, field, logger, globalFetch, { path, op +@@ -35,6 +35,21 @@ function addHTTPRootFieldResolver(schema, field, logger, globalFetch, { path, op const interpolatedBaseUrl = string_interpolation_1.stringInterpolator.parse(endpoint, interpolationData); const interpolatedPath = string_interpolation_1.stringInterpolator.parse(path, interpolationData); let fullPath = (0, url_join_1.default)(interpolatedBaseUrl, interpolatedPath); + let subPath; -+ /** -+ * FIXME: This is a hack to follow link in the response -+ * In case where the root object contains several links, -+ * we need to follow the correct link which match the index of field in the path -+ */ -+ if (root && root?.followsLink?.length) { ++ ++ // HANDLE HATEOAS LINKS ++ if (root && root?.followLinks?.length) { + const index = info?.path?.key?.match(/_(\d+)_/)?.[1]; -+ subPath = root.followsLink?.[index]?.followLink; ++ subPath = root.followLinks?.[index]?.followLink; + fullPath = interpolatedBaseUrl + subPath; + } else if (root && root.followLink) { + subPath = root.followLink; + fullPath = interpolatedBaseUrl + subPath; + } + if (root && !subPath) { -+ return ++ return; + } ++ const headers = {}; for (const headerName in globalOperationHeaders) { const nonInterpolatedValue = globalOperationHeaders[headerName]; diff --git a/packages/graphql-mesh/tests/cases/configFromSwaggers.test.ts b/packages/graphql-mesh/tests/cases/configFromSwaggers.test.ts index 976d0aa..ad1f4ca 100644 --- a/packages/graphql-mesh/tests/cases/configFromSwaggers.test.ts +++ b/packages/graphql-mesh/tests/cases/configFromSwaggers.test.ts @@ -234,7 +234,7 @@ describe('ConfigFromSwaggers tests', () => { // Test function to get interfaces with their children it('should return interfaces with children correctly', () => { const expectedInterfacesWithChildren: Record = { - Vehicle: ['Car', 'Bike'] + Vehicle: ['Car', 'Bike', 'Default__Vehicle'] } expect(instance.getInterfacesWithChildren()).toEqual(expectedInterfacesWithChildren) }) diff --git a/packages/graphql-mesh/utils/configFromSwaggers.ts b/packages/graphql-mesh/utils/configFromSwaggers.ts index 223f9d4..a37110d 100755 --- a/packages/graphql-mesh/utils/configFromSwaggers.ts +++ b/packages/graphql-mesh/utils/configFromSwaggers.ts @@ -135,6 +135,7 @@ export default class ConfigFromSwaggers { } }) } + this.interfacesWithChildren[schemaKey].push(`Default__${schemaKey.split('_')[0]}`) } }) return this.interfacesWithChildren diff --git a/packages/graphql-mesh/utils/generateTypeDefsAndResolvers.ts b/packages/graphql-mesh/utils/generateTypeDefsAndResolvers.ts index 6a67283..f40a9f3 100755 --- a/packages/graphql-mesh/utils/generateTypeDefsAndResolvers.ts +++ b/packages/graphql-mesh/utils/generateTypeDefsAndResolvers.ts @@ -44,16 +44,13 @@ export const generateTypeDefsAndResolversFromSwagger = ( // Filter for special keys such as 'x-links' and 'x-graphql-prefix-schema-with' .filter(isSpecialKey) .forEach(([key, value]) => { - // HANDLE SCHEMA PREFIXATION if (key === 'x-graphql-prefix-schema-with') { const schemaType = Object.keys(interfacesWithChildren).includes(schemaKey) ? 'interface' : 'type' - // Add a prefixSchema directive to the type definition typeDefs += `extend ${schemaType} ${schemaKey} @prefixSchema(prefix: "${value}") { dummy: String }\n` - // If it's an interface, add the dummy property to each of its children too if (schemaType === 'interface') { interfacesWithChildren[schemaKey].forEach((children) => { @@ -67,7 +64,6 @@ export const generateTypeDefsAndResolversFromSwagger = ( }) } } - // HANDLE HATEOAS LINKS PROCESSING if (key === 'x-links') { const trimedSchemaKey = trimLinks(schemaKey) @@ -151,12 +147,14 @@ export const generateTypeDefsAndResolversFromSwagger = ( }`, resolve: (root: any, args: any, context: any, info: any) => { - const hateoasLink: any = Object.entries(root._links).find( - (item) => item[0] === xLinkName - )?.[1] - - if (hateoasLink?.href) { - root = { ...root, followLink: hateoasLink.href } + if (root._links) { + const hateoasLink: any = Object.entries(root._links).find( + (item) => item[0] === xLinkName + )?.[1] + + if (hateoasLink?.href) { + root = { ...root, followLink: hateoasLink.href } + } } if (paramsToSend.length) { @@ -223,12 +221,14 @@ export const generateTypeDefsAndResolversFromSwagger = ( }`, resolve: (root: any, args: any, context: any, info: any) => { - const hateoasLink: any = Object.entries(root._links).find( - (item) => item[0] === xLinkName - )?.[1] + if (root._links) { + const hateoasLink: any = Object.entries(root._links).find( + (item) => item[0] === xLinkName + )?.[1] - if (hateoasLink?.href) { - root = { ...root, followLink: hateoasLink.href } + if (hateoasLink?.href) { + root = { ...root, followLink: hateoasLink.href } + } } if (paramsToSend.length) { @@ -257,21 +257,23 @@ export const generateTypeDefsAndResolversFromSwagger = ( // Resolvers for _linksList and _actionsList if (Object.keys(subResolver).length) { - subTypeDefs += /* GraphQL */ `_linksList: [LinkItem]\n` - subResolver['_linksList'] = { - selectionSet: /* GraphQL */ ` + if (_linksItems !== '') { + subTypeDefs += /* GraphQL */ `_linksList: [LinkItem]\n` + subResolver['_linksList'] = { + selectionSet: /* GraphQL */ ` { _links { ${_linksItems} } }`, - resolve: (root: any) => { - return Object.keys(root?._links || {}) - .filter((key) => root._links[key]?.href) - .map((key) => ({ - rel: key, - href: root._links[key]?.href - })) + resolve: (root: any) => { + return Object.keys(root?._links || {}) + .filter((key) => root._links[key]?.href) + .map((key) => ({ + rel: key, + href: root._links[key]?.href + })) + } } } if (_actionsItems !== '') { @@ -302,7 +304,7 @@ export const generateTypeDefsAndResolversFromSwagger = ( // Delete the additional typeDefs section if no new fields have been added subTypeDefs = subTypeDefs.replace(`extend ${schemaType} ${trimedSchemaKey} {\n}\n`, '') - if (subTypeDefs !== "") { + if (subTypeDefs !== '') { typeDefs += subTypeDefs resolvers[trimedSchemaKey] = subResolver } @@ -336,16 +338,12 @@ export const generateTypeDefsAndResolversFromSwagger = ( } }) - // Interfaces need to have the additional '__resolveType' property + // Resolvers of interfaces need to have the additional '__resolveType' property resolvers[trimedSchemaKey].__resolveType = (res, _, schema) => { - if (res.__typename) { + if (res.__typename && !interfacesWithChildren[res.__typename]) { return res.__typename } - const returnTypeName = schema.returnType.name - const parentVersion = returnTypeName.split('_')[returnTypeName.split('_').length - 1] - return parentVersion !== trimedSchemaKey - ? `${interfacesWithChildren[schema.returnType.name][1]}_${parentVersion}` - : `${interfacesWithChildren[schema.returnType.name][1]}` + return `Default__${schema.returnType.name}` } } } diff --git a/patches/@graphql-tools+batch-execute+9.0.2.patch b/patches/@graphql-tools+batch-execute+9.0.2.patch index f58a513..a4955ea 100644 --- a/patches/@graphql-tools+batch-execute+9.0.2.patch +++ b/patches/@graphql-tools+batch-execute+9.0.2.patch @@ -7,8 +7,8 @@ index dfe8afc..b60987c 100644 info: requests[0].info, operationType, - rootValue: requests[0].rootValue, -+ // FIXME: Extend the rootValue with the followsLink to save followLink for all requests that are merged and executed in parallel -+ rootValue: { ...requests[0].rootValue, followsLink: requests.map((request) => ({ followLink: request.rootValue.followLink })) }, ++ // Extend the rootValue with an array of the available links ++ rootValue: { ...requests[0].rootValue, followLinks: requests.map((request) => ({ followLink: request.rootValue.followLink })) }, }; } exports.mergeRequests = mergeRequests;