-
Notifications
You must be signed in to change notification settings - Fork 227
feat: add support for requires in protographic #2439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Noroth
merged 76 commits into
main
from
ludwig/eng-8641-implement-proto-generation-in-protographic
Feb 26, 2026
Merged
Changes from 14 commits
Commits
Show all changes
76 commits
Select commit
Hold shift + click to select a range
a8790be
feat: add support for @requires
Noroth 2fcd43b
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 0fb9fb5
feat: include logic for requires in the proto generator
Noroth a9b5873
chore: lint format
Noroth 3af2531
chore: add more tests and prepare abstract selection rewriting
Noroth 90b89a7
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 8b36ed9
chore: add mapping and validation
Noroth fa7c602
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 925740b
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 03070da
chore: improve the mapping
Noroth d4abf0b
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 993c2ba
chore: add comments
Noroth d8dd473
chore: export constants
Noroth 977e1fd
chore: update import
Noroth 62e44a2
normalize keys
Noroth 5cd87b4
chore: apply normalization logic from composition
Noroth 576294e
chore: updates
Noroth a7b4e9b
chore: mimimi
Noroth f711133
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 6dc6997
chore: do not duplicate messages
Noroth 1974e6d
chore: remove unused options
Noroth 506dd1a
chore: pr reviews
Noroth 20b6f8c
chore: do not use type assertio9n
Noroth f9281b7
chore: remove unused function
Noroth 01f16fc
chore: return early
Noroth 9bac9ee
chore: import
Noroth 0a22c6b
chore: install dependencies in related projects
Noroth 02de360
chore: imports
Noroth 5a40d18
chore: mroe cleanup
Noroth 59f92b4
refactor: enable eslint to comply with rules in other packages
Noroth 5a040a2
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth c8b3469
chore: more lint updates
Noroth 4174625
chore: sanitize comments
Noroth cb8ee46
chore: update comment
Noroth 90a5b67
chore: properly handle composite types on field level
Noroth 9af0929
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth b0b4390
chore: lint
Noroth 20ca49d
chore: balance pop
Noroth d9169fb
chore: add eslint dependency
Noroth a7264e3
chore: make composite member type order deterministic
Noroth 43ab1a7
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 9378483
chore: implement abstract selection rewriting
Noroth 2b0b1d6
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 94fff18
chore: index won't be undefined
Noroth c82e9ba
chore: fix typo
Noroth 179d3a7
chore: remove duplicated type
Noroth a64c780
chore: remove other duplicate map
Noroth 4e53b55
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 5517a99
chore: fixes
Noroth 95a0871
chore: implement recursive handling for rewriting subselections
Noroth 712b2e9
chore: add more tests
Noroth d09ff5a
chore: nested inline fragments should only allow parent resolution
Noroth 5dd480f
chore: more tests
Noroth b1116b9
feat: implement support for deeply nested interface combinations
Noroth 486e70d
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 5a5b4d2
chore: target interface root to determine valid implementation types
Noroth d16e439
chore: remove typed return
Noroth e681144
chore: handle simple union normalization
Noroth 7a538a8
chore: improve rewriting logic
Noroth 460a9d9
chore: handle nested concrete fragments and unions
Noroth f123e53
Merge branch 'main' of github.com:wundergraph/cosmo into ludwig/eng-8…
Noroth ffb1a86
chore: update composition-go
Noroth ac580e0
chore: add requires docs to markdown file
Noroth baf5dcb
Merge branch 'main' of github.com:wundergraph/cosmo into ludwig/eng-8…
Noroth 7e69bc1
chore: update composition-go
Noroth cbf8280
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 08c0176
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth e26a19d
chore: lint fixes
Noroth f2eeb71
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 1ced10e
chore: remove obsolete code
Noroth 9fd085d
chore: add test for nested composite type
Noroth 0762cdd
chore: updates from PR reviews
Noroth 491848f
Merge branch 'main' of github.com:wundergraph/cosmo into ludwig/eng-8…
Noroth 2134831
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth 3f17925
Merge branch 'main' into ludwig/eng-8641-implement-proto-generation-i…
Noroth bdec880
chore: resolve PR comments
Noroth File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| /** | ||
| * @file abstract-selection-rewriter.ts | ||
| * | ||
| * This module provides functionality to normalize GraphQL field set selections | ||
| * when dealing with abstract types (interfaces). It ensures that fields selected | ||
Noroth marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * at the interface level are properly distributed into each inline fragment, | ||
| * maintaining correct selection semantics for proto mapping generation. | ||
| */ | ||
|
|
||
| import { | ||
| ASTVisitor, | ||
| DocumentNode, | ||
| GraphQLSchema, | ||
| GraphQLObjectType, | ||
| visit, | ||
| SelectionSetNode, | ||
| isInterfaceType, | ||
| Kind, | ||
| FieldNode, | ||
| ASTNode, | ||
| GraphQLField, | ||
| GraphQLType, | ||
| getNamedType, | ||
| } from 'graphql'; | ||
| import { VisitContext } from './types'; | ||
|
|
||
| /** | ||
| * Rewrites GraphQL selection sets to normalize abstract type selections. | ||
| * | ||
| * When a field returns an interface type, selections can be made both at the | ||
| * interface level and within inline fragments for concrete types. This class | ||
| * normalizes such selections by moving interface-level fields into each inline | ||
| * fragment, ensuring consistent selection structure for downstream processing. | ||
Noroth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * | ||
| * @example | ||
| * Input selection: | ||
| * ```graphql | ||
| * media { | ||
| * id # interface-level field | ||
| * ... on Book { title } | ||
| * ... on Movie { duration } | ||
| * } | ||
| * ``` | ||
| * | ||
| * Output after normalization: | ||
| * ```graphql | ||
| * media { | ||
| * ... on Book { id title } | ||
| * ... on Movie { id duration } | ||
| * } | ||
| * ``` | ||
| */ | ||
| export class AbstractSelectionRewriter { | ||
| private readonly visitor: ASTVisitor; | ||
| private readonly fieldSetDoc: DocumentNode; | ||
| public readonly schema: GraphQLSchema; | ||
| private currentType: GraphQLObjectType; | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Creates a new AbstractSelectionRewriter instance. | ||
| * | ||
| * @param fieldSetDoc - The parsed GraphQL document containing the field set to rewrite | ||
| * @param schema - The GraphQL schema used for type resolution | ||
| * @param objectType - The root object type where the field set originates | ||
| */ | ||
| constructor(fieldSetDoc: DocumentNode, schema: GraphQLSchema, objectType: GraphQLObjectType) { | ||
| this.fieldSetDoc = fieldSetDoc; | ||
| this.schema = schema; | ||
| this.currentType = objectType; | ||
| this.visitor = this.createASTVisitor(); | ||
| } | ||
|
|
||
| /** | ||
| * Creates the AST visitor that processes selection sets during traversal. | ||
| * | ||
| * @returns An ASTVisitor configured to handle SelectionSet nodes | ||
| */ | ||
| private createASTVisitor(): ASTVisitor { | ||
| return { | ||
| SelectionSet: { | ||
| enter: (node, key, parent, path, ancestors) => { | ||
| this.onEnterSelectionSet({ node, key, parent, path, ancestors }); | ||
| }, | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Executes the normalization process on the field set document. | ||
| * | ||
| * This method traverses the AST and rewrites any selection sets that target | ||
| * interface types, distributing interface-level fields into inline fragments. | ||
| * The modification is performed in-place on the provided document. | ||
| */ | ||
| public normalize(): void { | ||
| visit(this.fieldSetDoc, this.visitor); | ||
| } | ||
|
|
||
| /** | ||
| * Handles the entry into a SelectionSet node during AST traversal. | ||
| * | ||
| * If the selection set's parent field returns an interface type, this method: | ||
| * 1. Extracts all direct field selections (interface-level fields) | ||
| * 2. Removes them from the selection set, leaving only inline fragments | ||
| * 3. Prepends the interface-level fields to each inline fragment's selections | ||
| * (unless the fragment already contains that field) | ||
| * | ||
| * @param ctx - The visitor context containing the current node and its position in the AST | ||
| */ | ||
| private onEnterSelectionSet(ctx: VisitContext<SelectionSetNode>): void { | ||
| if (!ctx.parent) return; | ||
| if (!this.isFieldNode(ctx.parent)) return; | ||
|
|
||
| const currentType = this.findNamedTypeForField(ctx.parent.name.value); | ||
| if (!currentType) return; | ||
|
|
||
| // Only process selection sets for interface types | ||
Noroth marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (!isInterfaceType(currentType)) { | ||
| return; | ||
| } | ||
|
|
||
| const fields = ctx.node.selections.filter((s) => s.kind === Kind.FIELD); | ||
| const inlineFragments = ctx.node.selections.filter((s) => s.kind === Kind.INLINE_FRAGMENT); | ||
|
|
||
| // Remove the interface-level fields from the selection set, keeping only inline fragments | ||
| ctx.node.selections = [...inlineFragments]; | ||
|
|
||
| // Distribute interface-level fields into each inline fragment | ||
| for (const fragment of inlineFragments) { | ||
| const normalizedFields = fragment.selectionSet.selections.filter((s) => s.kind === Kind.FIELD) ?? []; | ||
|
|
||
| for (const field of fields) { | ||
| // Skip if the fragment already has this field to avoid duplicates | ||
| if (this.hasField(normalizedFields, field.name.value)) { | ||
| continue; | ||
| } | ||
|
|
||
| normalizedFields.unshift(field); | ||
| } | ||
|
|
||
| fragment.selectionSet.selections = [...normalizedFields]; | ||
| } | ||
Noroth marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
| * Checks if a field with the given name exists in the provided field array. | ||
| * | ||
| * @param fields - Array of FieldNode objects to search | ||
| * @param fieldName - The name of the field to look for | ||
| * @returns true if a field with the given name exists, false otherwise | ||
| */ | ||
| private hasField(fields: FieldNode[], fieldName: string): boolean { | ||
| return fields.some((f) => f.name.value === fieldName); | ||
| } | ||
|
|
||
| /** | ||
| * Type guard to check if an AST node is a FieldNode. | ||
| * | ||
| * @param node - The AST node or array of nodes to check | ||
| * @returns true if the node is a FieldNode, false otherwise | ||
| */ | ||
| private isFieldNode(node: ASTNode | ReadonlyArray<ASTNode>): node is FieldNode { | ||
| if (Array.isArray(node)) return false; | ||
| return (node as ASTNode).kind === Kind.FIELD; | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves the field definition for a given field name from the current type. | ||
| * | ||
| * @param fieldName - The name of the field to look up | ||
| * @returns The GraphQL field definition, or undefined if not found | ||
| */ | ||
| private fieldDefinition(fieldName: string): GraphQLField<any, any, any> | undefined { | ||
| return this.currentType.getFields()[fieldName]; | ||
| } | ||
|
|
||
| /** | ||
| * Finds the named (unwrapped) type for a field by its name. | ||
| * | ||
| * This method looks up the field in the current type's fields and returns | ||
| * the named type (stripping away any List or NonNull wrappers). | ||
| * | ||
| * @param fieldName - The name of the field to look up | ||
| * @returns The named GraphQL type, or undefined if the field doesn't exist | ||
| */ | ||
| private findNamedTypeForField(fieldName: string): GraphQLType | undefined { | ||
| const fields = this.currentType.getFields(); | ||
| const field = fields[fieldName]; | ||
| if (!field) return undefined; | ||
|
|
||
| return getNamedType(field.type); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.