@wundergraph/protographic 0.15.6 → 0.16.1

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.
Files changed (50) hide show
  1. package/dist/src/abstract-selection-rewriter.d.ts +265 -0
  2. package/dist/src/abstract-selection-rewriter.js +585 -0
  3. package/dist/src/abstract-selection-rewriter.js.map +1 -0
  4. package/dist/src/index.d.ts +2 -0
  5. package/dist/src/index.js +4 -2
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/naming-conventions.d.ts +59 -0
  8. package/dist/src/naming-conventions.js +82 -7
  9. package/dist/src/naming-conventions.js.map +1 -1
  10. package/dist/src/operation-to-proto.js +9 -6
  11. package/dist/src/operation-to-proto.js.map +1 -1
  12. package/dist/src/operations/field-numbering.js +6 -3
  13. package/dist/src/operations/field-numbering.js.map +1 -1
  14. package/dist/src/operations/message-builder.js +38 -23
  15. package/dist/src/operations/message-builder.js.map +1 -1
  16. package/dist/src/operations/proto-field-options.js.map +1 -1
  17. package/dist/src/operations/proto-text-generator.js +16 -27
  18. package/dist/src/operations/proto-text-generator.js.map +1 -1
  19. package/dist/src/operations/request-builder.js +5 -3
  20. package/dist/src/operations/request-builder.js.map +1 -1
  21. package/dist/src/operations/type-mapper.js +3 -22
  22. package/dist/src/operations/type-mapper.js.map +1 -1
  23. package/dist/src/proto-lock.js +19 -19
  24. package/dist/src/proto-lock.js.map +1 -1
  25. package/dist/src/proto-utils.d.ts +74 -0
  26. package/dist/src/proto-utils.js +286 -0
  27. package/dist/src/proto-utils.js.map +1 -0
  28. package/dist/src/required-fields-visitor.d.ts +230 -0
  29. package/dist/src/required-fields-visitor.js +513 -0
  30. package/dist/src/required-fields-visitor.js.map +1 -0
  31. package/dist/src/sdl-to-mapping-visitor.d.ts +9 -1
  32. package/dist/src/sdl-to-mapping-visitor.js +72 -12
  33. package/dist/src/sdl-to-mapping-visitor.js.map +1 -1
  34. package/dist/src/sdl-to-proto-visitor.d.ts +20 -66
  35. package/dist/src/sdl-to-proto-visitor.js +270 -390
  36. package/dist/src/sdl-to-proto-visitor.js.map +1 -1
  37. package/dist/src/sdl-validation-visitor.d.ts +2 -0
  38. package/dist/src/sdl-validation-visitor.js +85 -29
  39. package/dist/src/sdl-validation-visitor.js.map +1 -1
  40. package/dist/src/selection-set-validation-visitor.d.ts +112 -0
  41. package/dist/src/selection-set-validation-visitor.js +199 -0
  42. package/dist/src/selection-set-validation-visitor.js.map +1 -0
  43. package/dist/src/string-constants.d.ts +4 -0
  44. package/dist/src/string-constants.js +4 -0
  45. package/dist/src/string-constants.js.map +1 -1
  46. package/dist/src/types.d.ts +102 -0
  47. package/dist/src/types.js +37 -1
  48. package/dist/src/types.js.map +1 -1
  49. package/dist/tsconfig.tsbuildinfo +1 -1
  50. 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 {};