@graphql-codegen/plugin-helpers
Advanced tools
Comparing version
@@ -6,6 +6,4 @@ "use strict"; | ||
exports.removeFederation = removeFederation; | ||
const tslib_1 = require("tslib"); | ||
const utils_1 = require("@graphql-tools/utils"); | ||
const graphql_1 = require("graphql"); | ||
const merge_js_1 = tslib_1.__importDefault(require("lodash/merge.js")); | ||
const index_js_1 = require("./index.js"); | ||
@@ -33,4 +31,28 @@ const utils_js_1 = require("./utils.js"); | ||
const setFederationMeta = ({ meta, typeName, update, }) => { | ||
meta[typeName] = { ...(meta[typeName] || { hasResolveReference: false, resolvableKeyDirectives: [] }), ...update }; | ||
meta[typeName] = { | ||
...(meta[typeName] || | ||
{ | ||
hasResolveReference: false, | ||
resolvableKeyDirectives: [], | ||
referenceSelectionSets: [], | ||
}), | ||
...update, | ||
}; | ||
}; | ||
const getReferenceSelectionSets = ({ resolvableKeyDirectives, fields, }) => { | ||
const referenceSelectionSets = []; | ||
// @key() @key() - "primary keys" in Federation | ||
// A reference may receive one primary key combination at a time, so they will be combined with `|` | ||
const primaryKeys = resolvableKeyDirectives.map(extractReferenceSelectionSet); | ||
referenceSelectionSets.push([...primaryKeys]); | ||
for (const fieldNode of Object.values(fields)) { | ||
// Look for @requires and see what the service needs and gets | ||
const directives = getDirectivesByName('requires', fieldNode.astNode); | ||
for (const directive of directives) { | ||
const requires = extractReferenceSelectionSet(directive); | ||
referenceSelectionSets.push([requires]); | ||
} | ||
} | ||
return referenceSelectionSets; | ||
}; | ||
const federationMeta = {}; | ||
@@ -48,2 +70,6 @@ const transformedSchema = (0, utils_1.mapSchema)(schema, { | ||
}; | ||
const referenceSelectionSets = getReferenceSelectionSets({ | ||
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives, | ||
fields: typeConfig.fields, | ||
}); | ||
setFederationMeta({ | ||
@@ -55,2 +81,3 @@ meta: federationMeta, | ||
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives, | ||
referenceSelectionSets, | ||
}, | ||
@@ -66,2 +93,6 @@ }); | ||
const typeConfig = type.toConfig(); | ||
const referenceSelectionSets = getReferenceSelectionSets({ | ||
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives, | ||
fields: typeConfig.fields, | ||
}); | ||
typeConfig.fields = { | ||
@@ -79,2 +110,3 @@ [resolveReferenceFieldName]: { | ||
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives, | ||
referenceSelectionSets, | ||
}, | ||
@@ -176,3 +208,2 @@ }); | ||
* Transforms a field's ParentType signature in ObjectTypes or InterfaceTypes involved in Federation | ||
* @param data | ||
*/ | ||
@@ -183,28 +214,15 @@ transformFieldParentType({ fieldNode, parentType, parentTypeSignature, federationTypeSignature, }) { | ||
} | ||
const parentTypeMeta = this.getMeta()[parentType.name]; | ||
if (!parentTypeMeta?.hasResolveReference) { | ||
const result = this.printReferenceSelectionSets({ | ||
typeName: parentType.name, | ||
baseFederationType: federationTypeSignature, | ||
}); | ||
// When `!result`, it means this is not a Federation entity, so we just return the parentTypeSignature | ||
if (!result) { | ||
return parentTypeSignature; | ||
} | ||
const isObjectFieldWithFederationRef = (0, graphql_1.isObjectType)(parentType) && | ||
(nodeHasTypeExtension(parentType, this.schema) || fieldNode.name.value === resolveReferenceFieldName); | ||
const isInterfaceFieldWithFederationRef = (0, graphql_1.isInterfaceType)(parentType) && fieldNode.name.value === resolveReferenceFieldName; | ||
if (!isObjectFieldWithFederationRef && !isInterfaceFieldWithFederationRef) { | ||
const isEntityResolveReferenceField = ((0, graphql_1.isObjectType)(parentType) || (0, graphql_1.isInterfaceType)(parentType)) && fieldNode.name.value === resolveReferenceFieldName; | ||
if (!isEntityResolveReferenceField) { | ||
return parentTypeSignature; | ||
} | ||
const outputs = [`{ __typename: '${parentType.name}' } &`]; | ||
// Look for @requires and see what the service needs and gets | ||
const requires = getDirectivesByName('requires', fieldNode).map(this.extractFieldSet); | ||
const requiredFields = this.translateFieldSet((0, merge_js_1.default)({}, ...requires), federationTypeSignature); | ||
// @key() @key() - "primary keys" in Federation | ||
const primaryKeys = parentTypeMeta.resolvableKeyDirectives.map(def => { | ||
const fields = this.extractFieldSet(def); | ||
return this.translateFieldSet(fields, federationTypeSignature); | ||
}); | ||
const [open, close] = primaryKeys.length > 1 ? ['(', ')'] : ['', '']; | ||
outputs.push([open, primaryKeys.join(' | '), close].join('')); | ||
// include required fields | ||
if (requires.length) { | ||
outputs.push(`& ${requiredFields}`); | ||
} | ||
return outputs.join(' '); | ||
return result; | ||
} | ||
@@ -234,27 +252,21 @@ addFederationTypeGenericIfApplicable({ genericTypes, typeName, federationTypesType, }) { | ||
} | ||
translateFieldSet(fields, parentTypeRef) { | ||
return `GraphQLRecursivePick<${parentTypeRef}, ${JSON.stringify(fields)}>`; | ||
printReferenceSelectionSet({ typeName, referenceSelectionSet, }) { | ||
return `GraphQLRecursivePick<${typeName}, ${JSON.stringify(referenceSelectionSet)}>`; | ||
} | ||
extractFieldSet(directive) { | ||
const arg = directive.arguments.find(arg => arg.name.value === 'fields'); | ||
const { value } = arg.value; | ||
return (0, index_js_1.oldVisit)((0, graphql_1.parse)(`{${value}}`), { | ||
leave: { | ||
SelectionSet(node) { | ||
return node.selections.reduce((accum, field) => { | ||
accum[field.name] = field.selection; | ||
return accum; | ||
}, {}); | ||
}, | ||
Field(node) { | ||
return { | ||
name: node.name.value, | ||
selection: node.selectionSet || true, | ||
}; | ||
}, | ||
Document(node) { | ||
return node.definitions.find((def) => def.kind === 'OperationDefinition' && def.operation === 'query').selectionSet; | ||
}, | ||
}, | ||
}); | ||
printReferenceSelectionSets({ typeName, baseFederationType, }) { | ||
const federationMeta = this.getMeta()[typeName]; | ||
if (!federationMeta?.hasResolveReference) { | ||
return false; | ||
} | ||
return `\n ( { __typename: '${typeName}' }\n & ${federationMeta.referenceSelectionSets | ||
.map(referenceSelectionSetArray => { | ||
const result = referenceSelectionSetArray.map(referenceSelectionSet => { | ||
return this.printReferenceSelectionSet({ | ||
referenceSelectionSet, | ||
typeName: baseFederationType, | ||
}); | ||
}); | ||
return result.length > 1 ? `( ${result.join(' | ')} )` : result.join(' | '); | ||
}) | ||
.join('\n & ')} )`; | ||
} | ||
@@ -268,3 +280,3 @@ createMapOfProvides() { | ||
const provides = getDirectivesByName('provides', field.astNode) | ||
.map(this.extractFieldSet) | ||
.map(extractReferenceSelectionSet) | ||
.reduce((prev, curr) => [...prev, ...Object.keys(curr)], []); | ||
@@ -324,10 +336,24 @@ const ofType = (0, utils_js_1.getBaseType)(field.type); | ||
} | ||
/** | ||
* Checks if the Object Type extends a federated type from a remote schema. | ||
* Based on if any of its fields contain the `@external` directive | ||
* @param node Type | ||
*/ | ||
function nodeHasTypeExtension(node, schema) { | ||
const definition = (0, graphql_1.isObjectType)(node) ? node.astNode || (0, utils_1.astFromObjectType)(node, schema) : node; | ||
return definition.fields?.some(field => getDirectivesByName('external', field).length); | ||
function extractReferenceSelectionSet(directive) { | ||
const arg = directive.arguments.find(arg => arg.name.value === 'fields'); | ||
const { value } = arg.value; | ||
return (0, index_js_1.oldVisit)((0, graphql_1.parse)(`{${value}}`), { | ||
leave: { | ||
SelectionSet(node) { | ||
return node.selections.reduce((accum, field) => { | ||
accum[field.name] = field.selection; | ||
return accum; | ||
}, {}); | ||
}, | ||
Field(node) { | ||
return { | ||
name: node.name.value, | ||
selection: node.selectionSet || true, | ||
}; | ||
}, | ||
Document(node) { | ||
return node.definitions.find((def) => def.kind === 'OperationDefinition' && def.operation === 'query').selectionSet; | ||
}, | ||
}, | ||
}); | ||
} |
import { astFromInterfaceType, astFromObjectType, getRootTypeNames, MapperKind, mapSchema } from '@graphql-tools/utils'; | ||
import { GraphQLInterfaceType, GraphQLObjectType, isInterfaceType, isObjectType, parse, } from 'graphql'; | ||
import merge from 'lodash/merge.js'; | ||
import { oldVisit } from './index.js'; | ||
@@ -26,4 +25,28 @@ import { getBaseType } from './utils.js'; | ||
const setFederationMeta = ({ meta, typeName, update, }) => { | ||
meta[typeName] = { ...(meta[typeName] || { hasResolveReference: false, resolvableKeyDirectives: [] }), ...update }; | ||
meta[typeName] = { | ||
...(meta[typeName] || | ||
{ | ||
hasResolveReference: false, | ||
resolvableKeyDirectives: [], | ||
referenceSelectionSets: [], | ||
}), | ||
...update, | ||
}; | ||
}; | ||
const getReferenceSelectionSets = ({ resolvableKeyDirectives, fields, }) => { | ||
const referenceSelectionSets = []; | ||
// @key() @key() - "primary keys" in Federation | ||
// A reference may receive one primary key combination at a time, so they will be combined with `|` | ||
const primaryKeys = resolvableKeyDirectives.map(extractReferenceSelectionSet); | ||
referenceSelectionSets.push([...primaryKeys]); | ||
for (const fieldNode of Object.values(fields)) { | ||
// Look for @requires and see what the service needs and gets | ||
const directives = getDirectivesByName('requires', fieldNode.astNode); | ||
for (const directive of directives) { | ||
const requires = extractReferenceSelectionSet(directive); | ||
referenceSelectionSets.push([requires]); | ||
} | ||
} | ||
return referenceSelectionSets; | ||
}; | ||
const federationMeta = {}; | ||
@@ -41,2 +64,6 @@ const transformedSchema = mapSchema(schema, { | ||
}; | ||
const referenceSelectionSets = getReferenceSelectionSets({ | ||
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives, | ||
fields: typeConfig.fields, | ||
}); | ||
setFederationMeta({ | ||
@@ -48,2 +75,3 @@ meta: federationMeta, | ||
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives, | ||
referenceSelectionSets, | ||
}, | ||
@@ -59,2 +87,6 @@ }); | ||
const typeConfig = type.toConfig(); | ||
const referenceSelectionSets = getReferenceSelectionSets({ | ||
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives, | ||
fields: typeConfig.fields, | ||
}); | ||
typeConfig.fields = { | ||
@@ -72,2 +104,3 @@ [resolveReferenceFieldName]: { | ||
resolvableKeyDirectives: federationDetails.resolvableKeyDirectives, | ||
referenceSelectionSets, | ||
}, | ||
@@ -169,3 +202,2 @@ }); | ||
* Transforms a field's ParentType signature in ObjectTypes or InterfaceTypes involved in Federation | ||
* @param data | ||
*/ | ||
@@ -176,28 +208,15 @@ transformFieldParentType({ fieldNode, parentType, parentTypeSignature, federationTypeSignature, }) { | ||
} | ||
const parentTypeMeta = this.getMeta()[parentType.name]; | ||
if (!parentTypeMeta?.hasResolveReference) { | ||
const result = this.printReferenceSelectionSets({ | ||
typeName: parentType.name, | ||
baseFederationType: federationTypeSignature, | ||
}); | ||
// When `!result`, it means this is not a Federation entity, so we just return the parentTypeSignature | ||
if (!result) { | ||
return parentTypeSignature; | ||
} | ||
const isObjectFieldWithFederationRef = isObjectType(parentType) && | ||
(nodeHasTypeExtension(parentType, this.schema) || fieldNode.name.value === resolveReferenceFieldName); | ||
const isInterfaceFieldWithFederationRef = isInterfaceType(parentType) && fieldNode.name.value === resolveReferenceFieldName; | ||
if (!isObjectFieldWithFederationRef && !isInterfaceFieldWithFederationRef) { | ||
const isEntityResolveReferenceField = (isObjectType(parentType) || isInterfaceType(parentType)) && fieldNode.name.value === resolveReferenceFieldName; | ||
if (!isEntityResolveReferenceField) { | ||
return parentTypeSignature; | ||
} | ||
const outputs = [`{ __typename: '${parentType.name}' } &`]; | ||
// Look for @requires and see what the service needs and gets | ||
const requires = getDirectivesByName('requires', fieldNode).map(this.extractFieldSet); | ||
const requiredFields = this.translateFieldSet(merge({}, ...requires), federationTypeSignature); | ||
// @key() @key() - "primary keys" in Federation | ||
const primaryKeys = parentTypeMeta.resolvableKeyDirectives.map(def => { | ||
const fields = this.extractFieldSet(def); | ||
return this.translateFieldSet(fields, federationTypeSignature); | ||
}); | ||
const [open, close] = primaryKeys.length > 1 ? ['(', ')'] : ['', '']; | ||
outputs.push([open, primaryKeys.join(' | '), close].join('')); | ||
// include required fields | ||
if (requires.length) { | ||
outputs.push(`& ${requiredFields}`); | ||
} | ||
return outputs.join(' '); | ||
return result; | ||
} | ||
@@ -227,27 +246,21 @@ addFederationTypeGenericIfApplicable({ genericTypes, typeName, federationTypesType, }) { | ||
} | ||
translateFieldSet(fields, parentTypeRef) { | ||
return `GraphQLRecursivePick<${parentTypeRef}, ${JSON.stringify(fields)}>`; | ||
printReferenceSelectionSet({ typeName, referenceSelectionSet, }) { | ||
return `GraphQLRecursivePick<${typeName}, ${JSON.stringify(referenceSelectionSet)}>`; | ||
} | ||
extractFieldSet(directive) { | ||
const arg = directive.arguments.find(arg => arg.name.value === 'fields'); | ||
const { value } = arg.value; | ||
return oldVisit(parse(`{${value}}`), { | ||
leave: { | ||
SelectionSet(node) { | ||
return node.selections.reduce((accum, field) => { | ||
accum[field.name] = field.selection; | ||
return accum; | ||
}, {}); | ||
}, | ||
Field(node) { | ||
return { | ||
name: node.name.value, | ||
selection: node.selectionSet || true, | ||
}; | ||
}, | ||
Document(node) { | ||
return node.definitions.find((def) => def.kind === 'OperationDefinition' && def.operation === 'query').selectionSet; | ||
}, | ||
}, | ||
}); | ||
printReferenceSelectionSets({ typeName, baseFederationType, }) { | ||
const federationMeta = this.getMeta()[typeName]; | ||
if (!federationMeta?.hasResolveReference) { | ||
return false; | ||
} | ||
return `\n ( { __typename: '${typeName}' }\n & ${federationMeta.referenceSelectionSets | ||
.map(referenceSelectionSetArray => { | ||
const result = referenceSelectionSetArray.map(referenceSelectionSet => { | ||
return this.printReferenceSelectionSet({ | ||
referenceSelectionSet, | ||
typeName: baseFederationType, | ||
}); | ||
}); | ||
return result.length > 1 ? `( ${result.join(' | ')} )` : result.join(' | '); | ||
}) | ||
.join('\n & ')} )`; | ||
} | ||
@@ -261,3 +274,3 @@ createMapOfProvides() { | ||
const provides = getDirectivesByName('provides', field.astNode) | ||
.map(this.extractFieldSet) | ||
.map(extractReferenceSelectionSet) | ||
.reduce((prev, curr) => [...prev, ...Object.keys(curr)], []); | ||
@@ -316,10 +329,24 @@ const ofType = getBaseType(field.type); | ||
} | ||
/** | ||
* Checks if the Object Type extends a federated type from a remote schema. | ||
* Based on if any of its fields contain the `@external` directive | ||
* @param node Type | ||
*/ | ||
function nodeHasTypeExtension(node, schema) { | ||
const definition = isObjectType(node) ? node.astNode || astFromObjectType(node, schema) : node; | ||
return definition.fields?.some(field => getDirectivesByName('external', field).length); | ||
function extractReferenceSelectionSet(directive) { | ||
const arg = directive.arguments.find(arg => arg.name.value === 'fields'); | ||
const { value } = arg.value; | ||
return oldVisit(parse(`{${value}}`), { | ||
leave: { | ||
SelectionSet(node) { | ||
return node.selections.reduce((accum, field) => { | ||
accum[field.name] = field.selection; | ||
return accum; | ||
}, {}); | ||
}, | ||
Field(node) { | ||
return { | ||
name: node.name.value, | ||
selection: node.selectionSet || true, | ||
}; | ||
}, | ||
Document(node) { | ||
return node.definitions.find((def) => def.kind === 'OperationDefinition' && def.operation === 'query').selectionSet; | ||
}, | ||
}, | ||
}); | ||
} |
{ | ||
"name": "@graphql-codegen/plugin-helpers", | ||
"version": "6.0.0-alpha-20250219094450-c0f73ba2266dfdde71725fb9a05c6286b0515874", | ||
"version": "6.0.0-alpha-20250305095421-b22d7edea4edf2476d193eccc74d5ff69373a898", | ||
"description": "GraphQL Code Generator common utils and types", | ||
@@ -5,0 +5,0 @@ "peerDependencies": { |
@@ -6,5 +6,26 @@ import { DirectiveNode, FieldDefinitionNode, GraphQLNamedType, GraphQLSchema } from 'graphql'; | ||
export declare const federationSpec: import("graphql").DocumentNode; | ||
/** | ||
* ReferenceSelectionSet | ||
* @description Each is a collection of fields that are available in a reference payload (originated from the Router) | ||
* @example | ||
* - resolvable fields marked with `@key` | ||
* - fields declared in `@provides` | ||
*/ | ||
interface ReferenceSelectionSet { | ||
name: string; | ||
selection: boolean | ReferenceSelectionSet[]; | ||
} | ||
interface TypeMeta { | ||
hasResolveReference: boolean; | ||
resolvableKeyDirectives: readonly DirectiveNode[]; | ||
/** | ||
* referenceSelectionSets | ||
* @description Each element can be `ReferenceSelectionSet[]`. | ||
* Elements at the root level are combined with `&` and nested elements are combined with `|`. | ||
* | ||
* @example: | ||
* - [[A, B], [C], [D]] -> (A | B) & C & D | ||
* - [[A, B], [C, D], [E]] -> (A | B) & (C | D) & E | ||
*/ | ||
referenceSelectionSets: ReferenceSelectionSet[][]; | ||
} | ||
@@ -72,3 +93,2 @@ export type FederationMeta = { | ||
* Transforms a field's ParentType signature in ObjectTypes or InterfaceTypes involved in Federation | ||
* @param data | ||
*/ | ||
@@ -90,6 +110,12 @@ transformFieldParentType({ fieldNode, parentType, parentTypeSignature, federationTypeSignature, }: { | ||
private hasProvides; | ||
private translateFieldSet; | ||
private extractFieldSet; | ||
printReferenceSelectionSet({ typeName, referenceSelectionSet, }: { | ||
typeName: string; | ||
referenceSelectionSet: ReferenceSelectionSet; | ||
}): string; | ||
printReferenceSelectionSets({ typeName, baseFederationType, }: { | ||
typeName: string; | ||
baseFederationType: string; | ||
}): string | false; | ||
private createMapOfProvides; | ||
} | ||
export {}; |
Sorry, the diff of this file is not supported yet
125040
3.13%2118
3.87%