@wundergraph/protographic 0.15.6 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/abstract-selection-rewriter.d.ts +265 -0
- package/dist/src/abstract-selection-rewriter.js +585 -0
- package/dist/src/abstract-selection-rewriter.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +4 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/naming-conventions.d.ts +59 -0
- package/dist/src/naming-conventions.js +82 -7
- package/dist/src/naming-conventions.js.map +1 -1
- package/dist/src/operation-to-proto.js +9 -6
- package/dist/src/operation-to-proto.js.map +1 -1
- package/dist/src/operations/field-numbering.js +6 -3
- package/dist/src/operations/field-numbering.js.map +1 -1
- package/dist/src/operations/message-builder.js +38 -23
- package/dist/src/operations/message-builder.js.map +1 -1
- package/dist/src/operations/proto-field-options.js.map +1 -1
- package/dist/src/operations/proto-text-generator.js +16 -27
- package/dist/src/operations/proto-text-generator.js.map +1 -1
- package/dist/src/operations/request-builder.js +5 -3
- package/dist/src/operations/request-builder.js.map +1 -1
- package/dist/src/operations/type-mapper.js +3 -22
- package/dist/src/operations/type-mapper.js.map +1 -1
- package/dist/src/proto-lock.js +19 -19
- package/dist/src/proto-lock.js.map +1 -1
- package/dist/src/proto-utils.d.ts +74 -0
- package/dist/src/proto-utils.js +286 -0
- package/dist/src/proto-utils.js.map +1 -0
- package/dist/src/required-fields-visitor.d.ts +230 -0
- package/dist/src/required-fields-visitor.js +513 -0
- package/dist/src/required-fields-visitor.js.map +1 -0
- package/dist/src/sdl-to-mapping-visitor.d.ts +9 -1
- package/dist/src/sdl-to-mapping-visitor.js +72 -12
- package/dist/src/sdl-to-mapping-visitor.js.map +1 -1
- package/dist/src/sdl-to-proto-visitor.d.ts +20 -66
- package/dist/src/sdl-to-proto-visitor.js +270 -390
- package/dist/src/sdl-to-proto-visitor.js.map +1 -1
- package/dist/src/sdl-validation-visitor.d.ts +2 -0
- package/dist/src/sdl-validation-visitor.js +85 -29
- package/dist/src/sdl-validation-visitor.js.map +1 -1
- package/dist/src/selection-set-validation-visitor.d.ts +112 -0
- package/dist/src/selection-set-validation-visitor.js +199 -0
- package/dist/src/selection-set-validation-visitor.js.map +1 -0
- package/dist/src/string-constants.d.ts +4 -0
- package/dist/src/string-constants.js +4 -0
- package/dist/src/string-constants.js.map +1 -1
- package/dist/src/types.d.ts +102 -0
- package/dist/src/types.js +37 -1
- package/dist/src/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -5
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file abstract-selection-rewriter.ts
|
|
3
|
+
*
|
|
4
|
+
* This module provides functionality to normalize GraphQL field set selections
|
|
5
|
+
* when dealing with abstract types (interfaces and unions). It ensures that fields selected
|
|
6
|
+
* at the interface level are properly distributed into each inline fragment,
|
|
7
|
+
* maintaining correct selection semantics for proto mapping generation.
|
|
8
|
+
*/
|
|
9
|
+
import { DocumentNode, GraphQLSchema, GraphQLObjectType, GraphQLInterfaceType } from 'graphql';
|
|
10
|
+
type GraphQLTypeWithFields = GraphQLObjectType | GraphQLInterfaceType;
|
|
11
|
+
/**
|
|
12
|
+
* Rewrites GraphQL selection sets to normalize abstract type selections.
|
|
13
|
+
*
|
|
14
|
+
* When a field returns an interface type, selections can be made both at the
|
|
15
|
+
* interface level and within inline fragments for concrete types. This class
|
|
16
|
+
* normalizes such selections by moving interface-level fields into each inline
|
|
17
|
+
* fragment, ensuring consistent selection structure for downstream processing.
|
|
18
|
+
* This is required to generate the proper proto message definitions as in protobuf
|
|
19
|
+
* we define abstract types as oneof fields in in a message definition, which defines
|
|
20
|
+
* the possible concrete types that can be selected.
|
|
21
|
+
*
|
|
22
|
+
* As we can select on Interfaces and Unions directly, we need to normalize the selection set
|
|
23
|
+
* to determine the allowed concrete types that can be selected.
|
|
24
|
+
*
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* Input selection:
|
|
28
|
+
* ```graphql
|
|
29
|
+
* media {
|
|
30
|
+
* id # interface-level field
|
|
31
|
+
* ... on Book { title }
|
|
32
|
+
* ... on Movie { duration }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* Output after normalization:
|
|
37
|
+
* ```graphql
|
|
38
|
+
* media {
|
|
39
|
+
* ... on Book { id title }
|
|
40
|
+
* ... on Movie { id duration }
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare class AbstractSelectionRewriter {
|
|
45
|
+
private readonly visitor;
|
|
46
|
+
private readonly fieldSetDoc;
|
|
47
|
+
readonly schema: GraphQLSchema;
|
|
48
|
+
private currentType;
|
|
49
|
+
private currentCompositeRoot?;
|
|
50
|
+
private compositeTypeStack;
|
|
51
|
+
private ancestorStacks;
|
|
52
|
+
/** Stack for tracking parent types during nested field traversal */
|
|
53
|
+
private typeStack;
|
|
54
|
+
/** Boolean stack indicating whether to restore type/selection context when leaving a selection set */
|
|
55
|
+
private rebalanceStack;
|
|
56
|
+
/** The current selection set being processed, used for in-place AST mutations */
|
|
57
|
+
private currentSelectionSet;
|
|
58
|
+
/** Stack for preserving parent selection set context during nested traversal */
|
|
59
|
+
private selectionSetStack;
|
|
60
|
+
private inlineFragmentStack;
|
|
61
|
+
/** Stack tracking enclosing concrete type inline fragments for correct parent resolution
|
|
62
|
+
* when unfolding unions/abstracts nested inside concrete type fragments */
|
|
63
|
+
private concreteFragmentStack;
|
|
64
|
+
/**
|
|
65
|
+
* Creates a new AbstractSelectionRewriter instance.
|
|
66
|
+
*
|
|
67
|
+
* @param fieldSetDoc - The parsed GraphQL document containing the field set to rewrite
|
|
68
|
+
* @param schema - The GraphQL schema used for type resolution
|
|
69
|
+
* @param objectType - The root object type where the field set originates
|
|
70
|
+
*/
|
|
71
|
+
constructor(fieldSetDoc: DocumentNode, schema: GraphQLSchema, objectType: GraphQLTypeWithFields);
|
|
72
|
+
/**
|
|
73
|
+
* Creates the AST visitor that processes selection sets during traversal.
|
|
74
|
+
*
|
|
75
|
+
* @returns An ASTVisitor configured to handle SelectionSet nodes
|
|
76
|
+
*/
|
|
77
|
+
private createASTVisitor;
|
|
78
|
+
/**
|
|
79
|
+
* Executes the normalization process on the field set document.
|
|
80
|
+
*
|
|
81
|
+
* This method traverses the AST and rewrites any selection sets that target
|
|
82
|
+
* interface types, distributing interface-level fields into inline fragments.
|
|
83
|
+
* The modification is performed in-place on the provided document.
|
|
84
|
+
*/
|
|
85
|
+
normalize(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Handles the entry into a SelectionSet node during AST traversal.
|
|
88
|
+
*
|
|
89
|
+
* If the selection set's parent field returns an interface type, this method:
|
|
90
|
+
* 1. Extracts all direct field selections (interface-level fields)
|
|
91
|
+
* 2. Removes them from the selection set, leaving only inline fragments
|
|
92
|
+
* 3. Prepends the interface-level fields to each inline fragment's selections
|
|
93
|
+
* (unless the fragment already contains that field)
|
|
94
|
+
*
|
|
95
|
+
* @param ctx - The visitor context containing the current node and its position in the AST
|
|
96
|
+
*/
|
|
97
|
+
private onEnterSelectionSet;
|
|
98
|
+
/**
|
|
99
|
+
* Handles leaving a SelectionSet node during AST traversal.
|
|
100
|
+
*
|
|
101
|
+
* This method performs cleanup operations:
|
|
102
|
+
* 1. Merges duplicate inline fragments with the same type condition
|
|
103
|
+
* 2. Removes empty inline fragments (those with no selections)
|
|
104
|
+
* 3. Restores the previous type and selection set context from stacks
|
|
105
|
+
*
|
|
106
|
+
* @param ctx - The visitor context containing the current node and its position in the AST
|
|
107
|
+
*/
|
|
108
|
+
private onLeaveSelectionSet;
|
|
109
|
+
/**
|
|
110
|
+
* Handles entering an InlineFragment node during AST traversal.
|
|
111
|
+
*
|
|
112
|
+
* This method processes inline fragments in two ways:
|
|
113
|
+
*
|
|
114
|
+
* 1. For concrete type fragments (e.g., `... on Book`):
|
|
115
|
+
* - Validates if the fragment type implements the current interface
|
|
116
|
+
* - Removes invalid fragments (those targeting types that don't implement the interface)
|
|
117
|
+
* - Tracks the fragment for nested union/abstract type resolution
|
|
118
|
+
*
|
|
119
|
+
* 2. For interface type conditions (e.g., `... on Employee`):
|
|
120
|
+
* - Recursively processes nested interface selections
|
|
121
|
+
* - Distributes interface-level fields into concrete type fragments
|
|
122
|
+
* - Unwraps the inline fragment by replacing it with its selections
|
|
123
|
+
* - Ensures nested interface fields are properly distributed
|
|
124
|
+
*
|
|
125
|
+
* @param ctx - The visitor context containing the current inline fragment node
|
|
126
|
+
* @returns undefined to continue traversal, or nothing to skip
|
|
127
|
+
*/
|
|
128
|
+
private onEnterInlineFragment;
|
|
129
|
+
private onLeaveInlineFragment;
|
|
130
|
+
private unfoldUnionType;
|
|
131
|
+
private unfoldSelectionNodeToParent;
|
|
132
|
+
/**
|
|
133
|
+
* Resolves the correct enclosing selection set for union/abstract type unfolding.
|
|
134
|
+
*
|
|
135
|
+
* The resolution order is:
|
|
136
|
+
* 1. If `inlineFragmentStack` has depth > 1, use the parent fragment's selection set
|
|
137
|
+
* 2. If the node can be found in `currentSelectionSet`, use it
|
|
138
|
+
* 3. If inside a concrete type inline fragment (tracked by concreteFragmentStack)
|
|
139
|
+
* and the node can be found there, use that fragment's selection set
|
|
140
|
+
* 4. Fall back to `currentSelectionSet`
|
|
141
|
+
*/
|
|
142
|
+
private getEnclosingSelectionSet;
|
|
143
|
+
/**
|
|
144
|
+
* Merges duplicate inline fragments with the same type condition.
|
|
145
|
+
*
|
|
146
|
+
* When multiple inline fragments target the same concrete type, this method
|
|
147
|
+
* combines them into a single fragment with all selections merged together.
|
|
148
|
+
* The merged fragment replaces all duplicate fragments in the selection set.
|
|
149
|
+
*
|
|
150
|
+
* @param node - The selection set node containing inline fragments to merge
|
|
151
|
+
*/
|
|
152
|
+
private mergeInlineFragments;
|
|
153
|
+
/**
|
|
154
|
+
* Collapses redundant nested inline fragments whose type condition matches
|
|
155
|
+
* the enclosing inline fragment's type condition.
|
|
156
|
+
*
|
|
157
|
+
* When processing abstract types inside a concrete type inline fragment,
|
|
158
|
+
* the normalization may produce nested fragments like:
|
|
159
|
+
* `... on Intern { ... on Intern { scope } }`
|
|
160
|
+
* This method unwraps the inner fragment, yielding:
|
|
161
|
+
* `... on Intern { scope }`
|
|
162
|
+
*
|
|
163
|
+
* @param node - The selection set node to clean up
|
|
164
|
+
* @param parent - The parent AST node (checked for InlineFragment type)
|
|
165
|
+
*/
|
|
166
|
+
private collapseRedundantFragments;
|
|
167
|
+
/**
|
|
168
|
+
* Removes empty inline fragments from a selection set.
|
|
169
|
+
*
|
|
170
|
+
* Filters out any inline fragment that has no selections in its selection set.
|
|
171
|
+
* This cleanup step is performed after field distribution and merging operations
|
|
172
|
+
* to ensure only meaningful fragments remain in the AST.
|
|
173
|
+
*
|
|
174
|
+
* @param node - The selection set node to clean up
|
|
175
|
+
*/
|
|
176
|
+
private removeEmptyInlineFragments;
|
|
177
|
+
/**
|
|
178
|
+
* Appends inline fragments for all possible types that implement the current interface.
|
|
179
|
+
*
|
|
180
|
+
* When processing an interface type selection with interface-level fields, this method
|
|
181
|
+
* ensures that every concrete type implementing the interface has a corresponding
|
|
182
|
+
* inline fragment. Creates new fragments only for types that don't already have one.
|
|
183
|
+
*
|
|
184
|
+
* This guarantees that interface-level fields can be distributed to all implementing
|
|
185
|
+
* types, even if the original query didn't explicitly include fragments for them.
|
|
186
|
+
*
|
|
187
|
+
* @param node - The selection set node to append inline fragments to
|
|
188
|
+
*/
|
|
189
|
+
private appendValidInlineFragments;
|
|
190
|
+
private getPossibleIntersectingTypes;
|
|
191
|
+
/**
|
|
192
|
+
* Creates an inline fragment AST node for a specific concrete type.
|
|
193
|
+
*
|
|
194
|
+
* Constructs a minimal InlineFragmentNode with:
|
|
195
|
+
* - Type condition targeting the specified object type
|
|
196
|
+
* - Filtered field selections (only includes fields that exist on the target type)
|
|
197
|
+
* - Simple field nodes with name only (no aliases, arguments, or directives)
|
|
198
|
+
*
|
|
199
|
+
* @param type - The concrete object type to create a fragment for
|
|
200
|
+
* @param fields - The interface-level fields to include in the fragment
|
|
201
|
+
* @returns A new InlineFragmentNode with the filtered field selections
|
|
202
|
+
*/
|
|
203
|
+
private createInlineFragment;
|
|
204
|
+
/**
|
|
205
|
+
* Distributes interface-level fields into all inline fragments.
|
|
206
|
+
*
|
|
207
|
+
* This is the core normalization logic that:
|
|
208
|
+
* 1. Extracts all direct field selections from the selection set
|
|
209
|
+
* 2. Removes those fields from the parent selection set
|
|
210
|
+
* 3. Adds each field to the inline fragments where it doesn't already exist
|
|
211
|
+
* 4. Validates that each field exists on the fragment's concrete type
|
|
212
|
+
*
|
|
213
|
+
* Fields are prepended to maintain their original order relative to fragment-specific fields.
|
|
214
|
+
* Duplicate fields within a fragment are avoided by checking existing selections.
|
|
215
|
+
*
|
|
216
|
+
* @param node - The selection set node containing fields and inline fragments
|
|
217
|
+
*/
|
|
218
|
+
private distributeFieldsIntoInlineFragments;
|
|
219
|
+
/**
|
|
220
|
+
* Checks if an inline fragment's type condition targets an interface type.
|
|
221
|
+
*
|
|
222
|
+
* @param node - The inline fragment node to check
|
|
223
|
+
* @returns true if the fragment targets an interface type, false otherwise
|
|
224
|
+
*/
|
|
225
|
+
private inlineFragmentIsAbstractType;
|
|
226
|
+
/**
|
|
227
|
+
* Validates whether an inline fragment is valid for the current interface type.
|
|
228
|
+
*
|
|
229
|
+
* An inline fragment is considered valid if:
|
|
230
|
+
* - The current type is not an interface (always valid)
|
|
231
|
+
* - The fragment's type is an object type that implements the current interface
|
|
232
|
+
*
|
|
233
|
+
* Invalid fragments (those targeting types that don't implement the interface)
|
|
234
|
+
* should be removed from the selection set.
|
|
235
|
+
*
|
|
236
|
+
* @param node - The inline fragment node to validate
|
|
237
|
+
* @returns true if the fragment is valid for the current interface root context, false otherwise
|
|
238
|
+
*/
|
|
239
|
+
private inlineFragmentIsValidForCurrentAbstractRoot;
|
|
240
|
+
/**
|
|
241
|
+
* Type guard to check if an AST node is a FieldNode.
|
|
242
|
+
*
|
|
243
|
+
* @param node - The AST node or array of nodes to check
|
|
244
|
+
* @returns true if the node is a FieldNode, false otherwise
|
|
245
|
+
*/
|
|
246
|
+
private isFieldNode;
|
|
247
|
+
/**
|
|
248
|
+
* Finds the named (unwrapped) type for a field by its name.
|
|
249
|
+
*
|
|
250
|
+
* This method looks up the field in the current type's fields and returns
|
|
251
|
+
* the named type (stripping away any List or NonNull wrappers).
|
|
252
|
+
*
|
|
253
|
+
* @param fieldName - The name of the field to look up
|
|
254
|
+
* @returns The named GraphQL type, or undefined if the field doesn't exist
|
|
255
|
+
*/
|
|
256
|
+
private findNamedTypeForField;
|
|
257
|
+
/**
|
|
258
|
+
* Type guard to check if a GraphQL type has fields.
|
|
259
|
+
*
|
|
260
|
+
* @param type - The GraphQL type to check
|
|
261
|
+
* @returns true if the type is an object type or interface type (both have getFields method)
|
|
262
|
+
*/
|
|
263
|
+
private isTypeWithFields;
|
|
264
|
+
}
|
|
265
|
+
export {};
|