diff --git a/packages/graphql-mesh/patches/@graphql-mesh+cli+0.88.5.patch b/packages/graphql-mesh/patches/@graphql-mesh+cli+0.88.5.patch deleted file mode 100644 index e0d3399..0000000 --- a/packages/graphql-mesh/patches/@graphql-mesh+cli+0.88.5.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff --git a/node_modules/@graphql-mesh/cli/cjs/commands/serve/serve.js b/node_modules/@graphql-mesh/cli/cjs/commands/serve/serve.js -index a2eb7a1..f822e03 100644 ---- a/node_modules/@graphql-mesh/cli/cjs/commands/serve/serve.js -+++ b/node_modules/@graphql-mesh/cli/cjs/commands/serve/serve.js -@@ -30,6 +30,7 @@ function portSelectorFn(sources, logger) { - return port; - } - async function serveMesh({ baseDir, argsPort, getBuiltMesh, logger, rawServeConfig = {}, playgroundTitle, }, cliParams) { -+ const start = Date.now(); - const { fork: configFork = cross_helpers_1.process.env.NODE_ENV?.toLowerCase() === 'production', port: configPort, hostname = os_1.default.platform() === 'win32' || - // is WSL? - os_1.default.release().toLowerCase().includes('microsoft') -@@ -105,6 +106,8 @@ async function serveMesh({ baseDir, argsPort, getBuiltMesh, logger, rawServeConf - if (mesh.schema.getType('BigInt')) { - await Promise.resolve().then(() => tslib_1.__importStar(require('json-bigint-patch'))); - } -+ const end = Date.now(); -+ logger.info(`Loaded ${mesh.rawSources.length} datasources in ${(end-start)/1000}s`); - logger.info(`${cliParams.serveMessage}: ${serverUrl}`); - (0, terminateHandler_js_1.registerTerminateHandler)(eventName => { - const eventLogger = logger.child(`${eventName} 💀`); diff --git a/packages/graphql-mesh/patches/@graphql-mesh+transform-rename+0.96.7.patch b/packages/graphql-mesh/patches/@graphql-mesh+transform-rename+0.96.7.patch index e8091ea..c7a61c0 100644 --- a/packages/graphql-mesh/patches/@graphql-mesh+transform-rename+0.96.7.patch +++ b/packages/graphql-mesh/patches/@graphql-mesh+transform-rename+0.96.7.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@graphql-mesh/transform-rename/cjs/bareRename.js b/node_modules/@graphql-mesh/transform-rename/cjs/bareRename.js -index 7e430cf..76a818e 100644 +index 7e430cf..3192400 100644 --- a/node_modules/@graphql-mesh/transform-rename/cjs/bareRename.js +++ b/node_modules/@graphql-mesh/transform-rename/cjs/bareRename.js @@ -84,6 +84,9 @@ class BareRename { @@ -7,7 +7,7 @@ index 7e430cf..76a818e 100644 return null; return mapKeys.reduce((newName, mapKey) => { + if (!newName) { -+ return null ++ return null; + } if (mapKeyIsString) { const str = map.get(mapKey); 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 750d9d1..a250573 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,96 +12,101 @@ 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 -old mode 100644 -new mode 100755 -index c915942..fd09fc4 +index c915942..ae923fe 100644 --- a/node_modules/@graphql-tools/stitch/cjs/typeCandidates.js +++ b/node_modules/@graphql-tools/stitch/cjs/typeCandidates.js -@@ -119,6 +119,29 @@ function buildTypes({ typeCandidates, directives, stitchingInfo, rootTypeNames, +@@ -119,6 +119,33 @@ function buildTypes({ typeCandidates, directives, stitchingInfo, rootTypeNames, (typeof mergeTypes === 'function' && mergeTypes(typeCandidates[typeName], typeName)) || (Array.isArray(mergeTypes) && mergeTypes.includes(typeName)) || (stitchingInfo != null && typeName in stitchingInfo.mergedTypes)) { + -+ // Custom object-interface stitching ++ // Special handling for object-interface stitching + const candidates = typeCandidates[typeName].sort((a, b) => a.subschema.name.localeCompare(b.subschema.name)); ++ // Check for candidates with different constructors (e.g., object and interface) + if (candidates.some(candidate => candidate.type.constructor !== candidates[0].type.constructor)) { + const candidatesI = candidates.filter((c) => c.type.constructor.name === "GraphQLInterfaceType"); + const candidatesObj = candidates.filter((c) => c.type.constructor.name === "GraphQLObjectType"); ++ // If all the candidates are either interfaces or objects + if (candidatesI.length + candidatesObj.length === candidates.length) { + let finalI = candidatesI[0]; + const otherCandidates = candidatesI.slice(1, candidatesI.length).concat(candidatesObj); + otherCandidates.forEach((otherCandidate) => { ++ // Add fields from other candidates to the final interface + Object.keys(otherCandidate.type._fields).forEach(field => { -+ if (!Object.keys(finalI.type._fields).includes(field)) { ++ if (finalI.type._fields[field] === undefined) { + finalI.type._fields[field] = otherCandidate.type._fields[field]; + } + }) + }); + typeCandidates[typeName] = [finalI]; + } ++ // Special handling for Date scalar type if it appears among the candidates + if (typeName === "Date") { -+ typeCandidates[typeName] = candidates.filter((c) => c.type.constructor.name === "GraphQLScalarType") ++ typeCandidates[typeName] = candidates.filter((c) => c.type.constructor.name === "GraphQLScalarType"); + } + } + typeMap[typeName] = (0, mergeCandidates_js_1.mergeCandidates)(typeName, typeCandidates[typeName], typeMergingOptions); } else { -@@ -128,6 +151,58 @@ function buildTypes({ typeCandidates, directives, stitchingInfo, rootTypeNames, +@@ -128,6 +155,61 @@ function buildTypes({ typeCandidates, directives, stitchingInfo, rootTypeNames, typeMap[typeName] = candidateSelector(typeCandidates[typeName]).type; } } + -+ /** -+ * When an object implements an interface, it needs to have every property -+ * of this interface. Because interfaces have been possibly merged earlier -+ * in this file, this statement is sometimes not true anymore. -+ */ ++ // Handle edge case: object implementing more than one interface + Object.values(typeMap).forEach((type) => { + if (type.constructor.name === "GraphQLObjectType") { -+ const typeInterfaces = type.getInterfaces() -+ -+ 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] -+ }) -+ }) -+ } ++ const typeInterfaces = type.getInterfaces(); + + if (typeInterfaces.length >= 2) { -+ let uniqueFields = {} -+ let duplicateFields = {} ++ let uniqueFields = {}; ++ let duplicateFields = {}; + + typeInterfaces.forEach(i => { -+ const iFields = typeMap[i.name].getFields() ++ const iFields = typeMap[i.name].getFields(); + Object.keys(iFields).forEach(keyName => { + if (uniqueFields[keyName] === undefined) { -+ uniqueFields[keyName] = "defined" ++ uniqueFields[keyName] = "defined"; + } + else { -+ duplicateFields[keyName] = iFields[keyName] ++ 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] ++ type._fields[field] = duplicateFields[field]; + } + typeInterfaces.forEach(i => { -+ const iFields = typeMap[i.name].getFields() ++ const iFields = typeMap[i.name].getFields(); + + if (iFields[field] !== undefined) { -+ typeMap[i.name]._fields[field] = duplicateFields[field] ++ 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 (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]; ++ }) ++ }) ++ } ++ } ++ }) + return (0, utils_1.rewireTypes)(typeMap, directives); } diff --git a/packages/graphql-mesh/patches/@omnigraph+openapi+0.97.5.patch b/packages/graphql-mesh/patches/@omnigraph+openapi+0.97.5.patch index ee3f108..7356f99 100644 --- a/packages/graphql-mesh/patches/@omnigraph+openapi+0.97.5.patch +++ b/packages/graphql-mesh/patches/@omnigraph+openapi+0.97.5.patch @@ -1,28 +1,8 @@ diff --git a/node_modules/@omnigraph/openapi/cjs/getJSONSchemaOptionsFromOpenAPIOptions.js b/node_modules/@omnigraph/openapi/cjs/getJSONSchemaOptionsFromOpenAPIOptions.js -index e815992..e7a949c 100644 +index e815992..9084b8f 100644 --- a/node_modules/@omnigraph/openapi/cjs/getJSONSchemaOptionsFromOpenAPIOptions.js +++ b/node_modules/@omnigraph/openapi/cjs/getJSONSchemaOptionsFromOpenAPIOptions.js -@@ -31,6 +31,19 @@ async function getJSONSchemaOptionsFromOpenAPIOptions(name, { source, fallbackFo - readFileOrUrl: readFileOrUrlForJsonMachete, - debugLogFn: logger.debug.bind(logger), - })); -+ // Add a "properties" key if a schema of object type doesn't have one -+ const schemas = oasOrSwagger.components?.schemas -+ for (const schemaName in schemas) { -+ try { -+ if (!JSON.stringify(schemas[schemaName]).includes('"properties"') && -+ schemas[schemaName].type === "object") { -+ schemas[schemaName]["properties"] = {"_null": {}} -+ } -+ } -+ catch (e) { -+ // ignore -+ } -+ } - } - else { - oasOrSwagger = await (0, json_machete_1.dereferenceObject)(source, { -@@ -278,7 +291,7 @@ async function getJSONSchemaOptionsFromOpenAPIOptions(name, { source, fallbackFo +@@ -278,7 +278,7 @@ async function getJSONSchemaOptionsFromOpenAPIOptions(name, { source, fallbackFo operationConfig.headers = operationConfig.headers || {}; operationConfig.headers.Accept = methodObj.produces.join(', '); } diff --git a/packages/graphql-mesh/patches/json-machete+0.97.1.patch b/packages/graphql-mesh/patches/json-machete+0.97.1.patch index 317276e..0e7eae9 100644 --- a/packages/graphql-mesh/patches/json-machete+0.97.1.patch +++ b/packages/graphql-mesh/patches/json-machete+0.97.1.patch @@ -1,7 +1,5 @@ diff --git a/node_modules/json-machete/cjs/healJSONSchema.js b/node_modules/json-machete/cjs/healJSONSchema.js -old mode 100644 -new mode 100755 -index 5d7e615..7d4ed0c +index 5d7e615..88ae5fb 100644 --- a/node_modules/json-machete/cjs/healJSONSchema.js +++ b/node_modules/json-machete/cjs/healJSONSchema.js @@ -40,12 +40,28 @@ exports.AnySchema = { @@ -39,7 +37,7 @@ index 5d7e615..7d4ed0c subSchema.enum) { - debugLogFn?.(`${path} has a pattern or maxLength or minLength or enum but no title. Setting it to ${pathBasedName}`); - subSchema.title = pathBasedName; -+ // Change enum names to be able to merge them ++ // Change the default enum names to be able to merge them + const newEnumName = `ENUM_${maybeDefinitionBasedPath.split('/')[maybeDefinitionBasedPath.split('/').length-1]}` + debugLogFn?.(`${path} has a enum but no title. Setting it to ${newEnumName}`); + subSchema.title = newEnumName; diff --git a/packages/graphql-mesh/scripts/download-sources.ts b/packages/graphql-mesh/scripts/download-sources.ts index 23ea0b2..7237b1f 100644 --- a/packages/graphql-mesh/scripts/download-sources.ts +++ b/packages/graphql-mesh/scripts/download-sources.ts @@ -28,7 +28,7 @@ const downSwaggerFromUrl = async (url: string | undefined, index: string): Promi importFn: (mod) => import(mod), logger }) - let fileName = getFileName(url) || `${index}-${url.split('/').pop()}` + let fileName = `${index}-${getFileName(url)}` if (!fileName.endsWith('.json')) { fileName += '.json' } diff --git a/packages/graphql-mesh/utils/ConfigFromSwaggers.ts b/packages/graphql-mesh/utils/ConfigFromSwaggers.ts index 95ec03b..2980cb0 100755 --- a/packages/graphql-mesh/utils/ConfigFromSwaggers.ts +++ b/packages/graphql-mesh/utils/ConfigFromSwaggers.ts @@ -23,6 +23,15 @@ export default class ConfigFromSwaggers { this.specs = this.swaggers.map( (swagger) => JSON.parse(readFileSync(swagger, { encoding: 'utf-8' })) ) + /** + * The following code builds a comprehensive catalog of all operations defined in a list of Swagger specifications. + * The resulting catalog is an object where: + * - Keys are operation paths (e.g. "foo/bar/endpoint"). + * - Values are objects with details about each operation, including: + * - operationIds: a list of operation IDs corresponding to this path. + * - type: the type returned by the 200 response of this path. + * - swaggers: a list of Swagger definitions where this path is present. + */ this.catalog = this.specs.reduce((acc, spec, i) => { Object.keys(spec.paths).forEach((path) => { const query = spec.paths[path]?.get @@ -47,7 +56,29 @@ export default class ConfigFromSwaggers { }, {} as Catalog) } - getInterfacesWithChildren() { + /** + * Extracts and returns all available schema types from the Swagger specifications. + * + * @returns {string[]} An array of schema type names. + * + * This function flattens the list of schemas from all Swagger specifications + * and extracts the keys (schema names) from the components' schemas. + */ + getAvailableTypes(): string[] { + return this.specs.flatMap((spec) => Object.keys(spec.components?.schemas ?? {})) + } + + /** + * Identifies and returns a map of interface along with their child types. + * + * @returns {Record} An object where keys are interface names + * and values are arrays of child schema names. + * + * This function iterates through all Swagger specifications to find schemas that + * use discriminators. For each schema with a discriminator, it collects the mapping + * of the discriminator and associates child schemas to the parent schema. + */ + getInterfacesWithChildren(): Record { this.specs.forEach((s) => { const { schemas } = s.components || {} const entries = Object.entries(schemas || {}).filter(([_, value]) => @@ -77,26 +108,37 @@ export default class ConfigFromSwaggers { return this.interfacesWithChildren } + /** + * Creates and returns GraphQL type definitions and resolvers based on the Swagger specifications. + * The function processes each Swagger specification and extracts or generates GraphQL types and resolvers. + * + * @returns {ConfigExtension} An object containing the type definitions and resolvers. + * + * This function supports configurations that allow for schema renaming based on the Swagger version. + */ createTypeDefsAndResolvers() { if (this.config.sources) { this.specs.forEach((spec, index) => { - // Suffix each schema name by the swagger version if there is a "rename" transform + // Apply naming transformations if specified in the configuration if ( spec.components && this.config.sources[index]?.transforms?.find( (transform) => transform.rename !== undefined ) ) { + // Extract the major version from the Swagger specification const xVersion = spec.info.version.split('.')[0] + // Create a new object to store schemas with version suffixes const schemasWithSuffix = {} + // Suffix each schema name with the major version number Object.entries(spec.components.schemas).forEach(([key, schema]) => { schemasWithSuffix[`${key}_v${xVersion}`] = schema }) + // Replace the original schemas with the suffixed schemas spec.components.schemas = schemasWithSuffix } }) } - const availableTypes = this.getAvailableTypes(this.specs) const typeDefs = /* GraphQL */ ` type LinkItem { rel: String @@ -107,14 +149,21 @@ export default class ConfigFromSwaggers { action: String } ` + + const availableTypes = this.getAvailableTypes() + const interfacesWithChildren = this.getInterfacesWithChildren() + const catalog = this.catalog + const config = this.config + + // Reduce over the specifications to generate and accumulate type definitions and resolvers return this.specs.reduce( (acc, spec) => { const { typeDefs, resolvers } = generateTypeDefsAndResolversFromSwagger( spec, availableTypes, - this.getInterfacesWithChildren(), - this.catalog, - this.config + interfacesWithChildren, + catalog, + config ) acc.typeDefs += typeDefs acc.resolvers = mergeObjects(acc.resolvers, resolvers) @@ -124,6 +173,15 @@ export default class ConfigFromSwaggers { ) } + /** + * Generates and returns an array of OpenAPI sources configured for the project. + * Each source includes a name, handler configurations, and possible transformations. + * + * @returns {OpenApiSource[]} An array of configured OpenAPI sources. + * + * This function maps over the `this.swaggers` array and constructs an object for each source, + * incorporating relevant configurations such as endpoint and headers. + */ getOpenApiSources() { return ( this.swaggers.map((source) => ({ @@ -145,7 +203,14 @@ export default class ConfigFromSwaggers { ) } - // Get sources that are not openapi + /** + * Filters and returns an array of sources that are not OpenAPI sources from the configuration. + * + * @returns {OtherSource[]} An array of sources that do not use OpenAPI handlers. + * + * This function checks the `this.config.sources` array and filters out any source + * that has an OpenAPI handler configuration. + */ getOtherSources() { return ( this.config.sources?.filter( @@ -154,7 +219,14 @@ export default class ConfigFromSwaggers { ) } - // Create Mesh config + /** + * Constructs and returns a complete Mesh configuration object based on the Swagger specifications and custom sources. + * + * @returns {MeshConfig} An object containing the default configuration, additional type definitions, resolvers, and sources. + * + * This function integrates type definitions and resolvers generated from the Swagger specifications + * and combines OpenAPI sources with other custom sources from the configuration. + */ getMeshConfigFromSwaggers(): { defaultConfig: any additionalTypeDefs: string @@ -169,8 +241,4 @@ export default class ConfigFromSwaggers { sources: [...this.getOpenApiSources(), ...this.getOtherSources()] } } - - // Get all schema names from all swaggers - getAvailableTypes = (specs: Spec[]) => - specs.flatMap((spec) => Object.keys(spec.components?.schemas ?? {})) } diff --git a/packages/graphql-mesh/utils/generateTypeDefsAndResolvers.ts b/packages/graphql-mesh/utils/generateTypeDefsAndResolvers.ts index be41b59..54d5dc3 100755 --- a/packages/graphql-mesh/utils/generateTypeDefsAndResolvers.ts +++ b/packages/graphql-mesh/utils/generateTypeDefsAndResolvers.ts @@ -10,13 +10,15 @@ import { } from './helpers' /** - * This function creates, for one Swagger file, the additional typeDefs and resolvers required to handle HATEOAS links and the prefixation of some schemas + * This function creates, for one Swagger file, the additional typeDefs and resolvers required to handle + * HATEOAS links and the prefixation of some schemas. + * * @param spec - A Swagger file - * @param availableTypes - An exhaustive list of the types available across the entire conf - * @param interfacesWithChildren - An exhaustive list of the all the interfaces, with their children - * @param catalog - An object, where the keys are operation paths and the values are: {the corresponding operationId, the type returned by the operation, the list of swaggers containing this path} + * @param availableTypes - An exhaustive list of the types available across the entire config + * @param interfacesWithChildren - An exhaustive list of all the interfaces, with their children + * @param catalog - An object, where the keys are operation paths and the values are: {the corresponding operationIds, the type returned by the operation, the list of swaggers containing this path} * @param config - The default config - * @returns an object with two elements: the additional typeDefs and the additional resolvers + * @returns {ConfigExtension} An object with two elements: the additional typeDefs and the additional resolvers */ export const generateTypeDefsAndResolversFromSwagger = ( spec: Spec, @@ -25,34 +27,33 @@ export const generateTypeDefsAndResolversFromSwagger = ( catalog: Catalog, config: any ): ConfigExtension => { - if (!spec.components) { + // If there are no schemas in the components section, log a warning and return empty typeDefs and resolvers + if (!spec.components?.schemas) { console.warn('No components found in the swagger file') return { typeDefs: '', resolvers: {} } } const { schemas } = spec.components - if (!schemas) { - console.warn('No schemas found in the swagger file') - return { typeDefs: '', resolvers: {} } - } let typeDefs = '' const resolvers: Resolvers = {} + // Iterate over each schema in the Swagger file Object.entries(schemas).forEach(([schemaKey, schemaValue]) => { Object.entries(schemaValue) + // Filter for special keys such as 'x-links' and 'x-graphql-prefix-schema-with' .filter(isSpecialKey) .forEach(([key, value]) => { - /** - * Prefix-schema processing: - * Add a prefixSchema directive for each schema having the "x-graphql-prefix-schema-with" key - */ + + // 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, prefix each of its children too if (schemaType === 'interface') { interfacesWithChildren[schemaKey].forEach((children) => { @@ -67,10 +68,7 @@ export const generateTypeDefsAndResolversFromSwagger = ( } } - /** - * xLinks processing: - * Add additional properties for each schema having the "x-links" key - */ + // HANDLE HATEOAS LINKS PROCESSING if (key === 'x-links') { const trimedSchemaKey = trimLinks(schemaKey) const schemaType = Object.keys(interfacesWithChildren).includes(trimedSchemaKey) @@ -90,11 +88,13 @@ export const generateTypeDefsAndResolversFromSwagger = ( swaggers: undefined } + // Process each HATEOAS link in the x-links array for (const xLink of xLinkList) { - // Replace illegal characters + // Replace illegal characters in the link name const xLinkName = xLink.rel.replaceAll('-', '_').replaceAll(' ', '') const xLinkPath = xLink.hrefPattern + // Find the corresponding path in the catalog const matchedPath = Object.keys(catalog).filter( (key) => anonymizePathAndGetParams(key).anonymizedPath === @@ -124,7 +124,7 @@ export const generateTypeDefsAndResolversFromSwagger = ( href } ` - // Add a new link for the highest version available & all the smaller versions available too + // Add a new link for the highest version available and all the smaller versions available too for (let currentVersion = highestVersion; currentVersion >= 0; currentVersion--) { if (availableTypes.includes(`${matchedLinkItems.type}_v${currentVersion}`)) { const currentLink = `${xLinkName}_v${currentVersion}` @@ -248,6 +248,7 @@ export const generateTypeDefsAndResolversFromSwagger = ( sourceSwaggers[0] .split('/') [sourceSwaggers[0].split('/').length - 1].split('.')[0] + .substring(2) ].Query[operationIds[0]]({ root, args, context, info }) } } diff --git a/patches/@graphql-tools+executor+1.2.1.patch b/patches/@graphql-tools+executor+1.2.1.patch index 8d34986..5daa021 100644 --- a/patches/@graphql-tools+executor+1.2.1.patch +++ b/patches/@graphql-tools+executor+1.2.1.patch @@ -1,14 +1,17 @@ diff --git a/node_modules/@graphql-tools/executor/cjs/execution/execute.js b/node_modules/@graphql-tools/executor/cjs/execution/execute.js old mode 100644 new mode 100755 -index 791e3df..b6a78f0 +index 791e3df..ae399d9 --- a/node_modules/@graphql-tools/executor/cjs/execution/execute.js +++ b/node_modules/@graphql-tools/executor/cjs/execution/execute.js -@@ -629,7 +629,10 @@ function completeAbstractValue(exeContext, returnType, fieldNodes, info, path, r +@@ -628,8 +628,13 @@ function completeAbstractValue(exeContext, returnType, fieldNodes, info, path, r + return completeObjectValue(exeContext, ensureValidRuntimeType(runtimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result, asyncPayloadRecord); } function ensureValidRuntimeType(runtimeTypeName, exeContext, returnType, fieldNodes, info, result) { ++ // If runtimeTypeName is null or undefined, derive it from the discriminator directive in the returnType if (runtimeTypeName == null) { - throw (0, utils_1.createGraphQLError)(`Abstract type "${returnType.name}" must resolve to an Object type at runtime for field "${info.parentType.name}.${info.fieldName}". Either the "${returnType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`, { nodes: fieldNodes }); ++ // Extract version suffix from return type name + const suffix = returnType.name.split('_')[1] + runtimeTypeName = Object.values(JSON.parse(returnType.astNode.directives + .find(d => d.name.value === "discriminator").arguments @@ -16,16 +19,18 @@ index 791e3df..b6a78f0 } // releases before 16.0.0 supported returning `GraphQLObjectType` from `resolveType` // TODO: remove in 17.0.0 release -@@ -640,6 +643,15 @@ function ensureValidRuntimeType(runtimeTypeName, exeContext, returnType, fieldNo +@@ -640,6 +645,17 @@ function ensureValidRuntimeType(runtimeTypeName, exeContext, returnType, fieldNo throw (0, utils_1.createGraphQLError)(`Abstract type "${returnType.name}" must resolve to an Object type at runtime for field "${info.parentType.name}.${info.fieldName}" with ` + `value ${(0, utils_1.inspect)(result)}, received "${(0, utils_1.inspect)(runtimeTypeName)}".`); } -+ // Resolve type from mapping of discriminator directive when possible ++ // Resolves the type from the mapping of the discriminator directive when possible. + const mappedTypes = JSON.parse(returnType.astNode.directives + .find(d => d.name.value === "discriminator").arguments + .find(a => a.name.value === "mapping").value.value) -+ Object.keys(mappedTypes).forEach((type) => { ++ Object.keys(mappedTypes).forEach((type) => { ++ // For each key in the mapping object, check if the runtimeTypeName includes the key + if (runtimeTypeName.includes(type)) { ++ // Replace the key with its corresponding mapped type in the runtimeTypeName + runtimeTypeName = runtimeTypeName.replace(type, mappedTypes[type]) + } + }) diff --git a/patches/@graphql-tools+merge+9.0.3.patch b/patches/@graphql-tools+merge+9.0.3.patch index 702f2d2..c6d5397 100644 --- a/patches/@graphql-tools+merge+9.0.3.patch +++ b/patches/@graphql-tools+merge+9.0.3.patch @@ -1,12 +1,13 @@ diff --git a/node_modules/@graphql-tools/merge/cjs/typedefs-mergers/fields.js b/node_modules/@graphql-tools/merge/cjs/typedefs-mergers/fields.js -index 442943d..e794bcc 100644 +index 442943d..8b65f34 100644 --- a/node_modules/@graphql-tools/merge/cjs/typedefs-mergers/fields.js +++ b/node_modules/@graphql-tools/merge/cjs/typedefs-mergers/fields.js -@@ -48,7 +48,7 @@ function preventConflicts(type, a, b, ignoreNullability = false) { +@@ -48,7 +48,8 @@ function preventConflicts(type, a, b, ignoreNullability = false) { const t1 = (0, utils_js_1.extractType)(a.type); const t2 = (0, utils_js_1.extractType)(b.type); if (t1.name.value !== t2.name.value) { - throw new Error(`Field "${b.name.value}" already defined with a different type. Declared as "${t1.name.value}", but you tried to override with "${t2.name.value}"`); ++ // Do not try to override fields, keep 1st field instead + return a; } if (!safeChangeForFieldType(a.type, b.type, !ignoreNullability)) { diff --git a/patches/graphql+16.8.1.patch b/patches/graphql+16.8.1.patch deleted file mode 100644 index 9276a95..0000000 --- a/patches/graphql+16.8.1.patch +++ /dev/null @@ -1,20 +0,0 @@ -diff --git a/node_modules/graphql/validation/rules/UniqueFieldDefinitionNamesRule.js b/node_modules/graphql/validation/rules/UniqueFieldDefinitionNamesRule.js -index f0d9fcb..fcb1291 100644 ---- a/node_modules/graphql/validation/rules/UniqueFieldDefinitionNamesRule.js -+++ b/node_modules/graphql/validation/rules/UniqueFieldDefinitionNamesRule.js -@@ -48,14 +48,7 @@ function UniqueFieldDefinitionNamesRule(context) { - const fieldName = fieldDef.name.value; - - if (hasField(existingTypeMap[typeName], fieldName)) { -- context.reportError( -- new _GraphQLError.GraphQLError( -- `Field "${typeName}.${fieldName}" already exists in the schema. It cannot also be defined in this type extension.`, -- { -- nodes: fieldDef.name, -- }, -- ), -- ); -+ // Ignore errors when fields are already defined - } else if (fieldNames[fieldName]) { - context.reportError( - new _GraphQLError.GraphQLError(