@wundergraph/protographic 0.11.0 → 0.12.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.
Files changed (36) hide show
  1. package/README.md +34 -3
  2. package/dist/src/index.d.ts +15 -0
  3. package/dist/src/index.js +10 -0
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/operation-to-proto.d.ts +48 -0
  6. package/dist/src/operation-to-proto.js +378 -0
  7. package/dist/src/operation-to-proto.js.map +1 -0
  8. package/dist/src/operations/field-numbering.d.ts +73 -0
  9. package/dist/src/operations/field-numbering.js +134 -0
  10. package/dist/src/operations/field-numbering.js.map +1 -0
  11. package/dist/src/operations/list-type-utils.d.ts +28 -0
  12. package/dist/src/operations/list-type-utils.js +49 -0
  13. package/dist/src/operations/list-type-utils.js.map +1 -0
  14. package/dist/src/operations/message-builder.d.ts +58 -0
  15. package/dist/src/operations/message-builder.js +377 -0
  16. package/dist/src/operations/message-builder.js.map +1 -0
  17. package/dist/src/operations/proto-text-generator.d.ts +74 -0
  18. package/dist/src/operations/proto-text-generator.js +336 -0
  19. package/dist/src/operations/proto-text-generator.js.map +1 -0
  20. package/dist/src/operations/request-builder.d.ts +56 -0
  21. package/dist/src/operations/request-builder.js +194 -0
  22. package/dist/src/operations/request-builder.js.map +1 -0
  23. package/dist/src/operations/type-mapper.d.ts +66 -0
  24. package/dist/src/operations/type-mapper.js +236 -0
  25. package/dist/src/operations/type-mapper.js.map +1 -0
  26. package/dist/src/proto-options.d.ts +23 -0
  27. package/dist/src/proto-options.js +45 -0
  28. package/dist/src/proto-options.js.map +1 -0
  29. package/dist/src/sdl-to-proto-visitor.d.ts +2 -14
  30. package/dist/src/sdl-to-proto-visitor.js +25 -38
  31. package/dist/src/sdl-to-proto-visitor.js.map +1 -1
  32. package/dist/src/types.d.ts +12 -0
  33. package/dist/src/types.js +2 -0
  34. package/dist/src/types.js.map +1 -0
  35. package/dist/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +2 -2
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Creates a new field number manager instance
3
+ *
4
+ * @param lockManager - Optional ProtoLockManager for field number stability
5
+ * @returns A new field number manager
6
+ */
7
+ export function createFieldNumberManager(lockManager) {
8
+ // Map of message name to field name to field number
9
+ const fieldNumbers = new Map();
10
+ // Map of message name to the next available field number
11
+ const nextFieldNumbers = new Map();
12
+ return {
13
+ getNextFieldNumber(messageName) {
14
+ // If we have a lock manager and this message has been reconciled,
15
+ // check if we already have a field number assigned
16
+ if (lockManager) {
17
+ const lockData = lockManager.getLockData();
18
+ const messageData = lockData.messages[messageName];
19
+ if (messageData) {
20
+ // Find the highest assigned number
21
+ const assignedNumbers = Object.values(messageData.fields);
22
+ const reservedNumbers = messageData.reservedNumbers || [];
23
+ const allNumbers = [...assignedNumbers, ...reservedNumbers];
24
+ if (allNumbers.length > 0) {
25
+ const maxNumber = Math.max(...allNumbers);
26
+ // Initialize next field number to be after the max
27
+ if (!nextFieldNumbers.has(messageName)) {
28
+ nextFieldNumbers.set(messageName, maxNumber + 1);
29
+ }
30
+ }
31
+ }
32
+ }
33
+ // Initialize if needed
34
+ if (!nextFieldNumbers.has(messageName)) {
35
+ nextFieldNumbers.set(messageName, 1);
36
+ }
37
+ const current = nextFieldNumbers.get(messageName);
38
+ nextFieldNumbers.set(messageName, current + 1);
39
+ return current;
40
+ },
41
+ assignFieldNumber(messageName, fieldName, fieldNumber) {
42
+ // Initialize message map if needed
43
+ if (!fieldNumbers.has(messageName)) {
44
+ fieldNumbers.set(messageName, new Map());
45
+ }
46
+ const messageFields = fieldNumbers.get(messageName);
47
+ messageFields.set(fieldName, fieldNumber);
48
+ // Update next field number if this assignment affects it
49
+ const currentNext = nextFieldNumbers.get(messageName) || 1;
50
+ if (fieldNumber >= currentNext) {
51
+ nextFieldNumbers.set(messageName, fieldNumber + 1);
52
+ }
53
+ },
54
+ getFieldNumber(messageName, fieldName) {
55
+ var _a;
56
+ return (_a = fieldNumbers.get(messageName)) === null || _a === void 0 ? void 0 : _a.get(fieldName);
57
+ },
58
+ resetMessage(messageName) {
59
+ fieldNumbers.delete(messageName);
60
+ nextFieldNumbers.set(messageName, 1);
61
+ },
62
+ resetAll() {
63
+ fieldNumbers.clear();
64
+ nextFieldNumbers.clear();
65
+ },
66
+ getMessageFields(messageName) {
67
+ const messageFields = fieldNumbers.get(messageName);
68
+ if (!messageFields) {
69
+ return {};
70
+ }
71
+ const result = {};
72
+ for (const [fieldName, fieldNumber] of messageFields.entries()) {
73
+ result[fieldName] = fieldNumber;
74
+ }
75
+ return result;
76
+ },
77
+ reconcileFieldOrder(messageName, fieldNames) {
78
+ if (!lockManager) {
79
+ // No lock manager, return fields in original order
80
+ return fieldNames;
81
+ }
82
+ // Use lock manager to reconcile field order
83
+ const orderedFields = lockManager.reconcileMessageFieldOrder(messageName, fieldNames);
84
+ // Update our internal tracking with the reconciled numbers
85
+ const lockData = lockManager.getLockData();
86
+ const messageData = lockData.messages[messageName];
87
+ if (messageData) {
88
+ // Initialize message map if needed
89
+ if (!fieldNumbers.has(messageName)) {
90
+ fieldNumbers.set(messageName, new Map());
91
+ }
92
+ const messageFields = fieldNumbers.get(messageName);
93
+ // Update field numbers from lock data
94
+ for (const fieldName of orderedFields) {
95
+ const fieldNumber = messageData.fields[fieldName];
96
+ if (fieldNumber !== undefined) {
97
+ messageFields.set(fieldName, fieldNumber);
98
+ // Update next field number
99
+ const currentNext = nextFieldNumbers.get(messageName) || 1;
100
+ if (fieldNumber >= currentNext) {
101
+ nextFieldNumbers.set(messageName, fieldNumber + 1);
102
+ }
103
+ }
104
+ }
105
+ }
106
+ return orderedFields;
107
+ },
108
+ getLockManager() {
109
+ return lockManager;
110
+ },
111
+ };
112
+ }
113
+ /**
114
+ * Assigns field numbers to a message from lock data
115
+ * @param messageName - The name of the message
116
+ * @param fieldNames - The field names to assign numbers to
117
+ * @param fieldNumberManager - The field number manager to use
118
+ */
119
+ export function assignFieldNumbersFromLockData(messageName, fieldNames, fieldNumberManager) {
120
+ var _a;
121
+ const lockData = (_a = fieldNumberManager === null || fieldNumberManager === void 0 ? void 0 : fieldNumberManager.getLockManager()) === null || _a === void 0 ? void 0 : _a.getLockData();
122
+ if (!lockData || !fieldNumberManager)
123
+ return;
124
+ const messageData = lockData.messages[messageName];
125
+ if (!messageData)
126
+ return;
127
+ for (const protoFieldName of fieldNames) {
128
+ const fieldNumber = messageData.fields[protoFieldName];
129
+ if (!fieldNumber)
130
+ continue;
131
+ fieldNumberManager.assignFieldNumber(messageName, protoFieldName, fieldNumber);
132
+ }
133
+ }
134
+ //# sourceMappingURL=field-numbering.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-numbering.js","sourceRoot":"","sources":["../../../src/operations/field-numbering.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { GraphQLType, GraphQLList, GraphQLNonNull } from 'graphql';
2
+ /**
3
+ * Unwraps a GraphQL type from a GraphQLNonNull wrapper
4
+ *
5
+ * @param graphqlType - The GraphQL type to unwrap
6
+ * @returns The unwrapped type
7
+ */
8
+ export declare function unwrapNonNullType<T extends GraphQLType>(graphqlType: T | GraphQLNonNull<T>): T;
9
+ /**
10
+ * Checks if a GraphQL list type contains nested lists
11
+ * Type guard that narrows the input type when nested lists are detected
12
+ *
13
+ * @param listType - The GraphQL list type to check
14
+ * @returns True if the list contains nested lists
15
+ */
16
+ export declare function isNestedListType(listType: GraphQLList<GraphQLType>): listType is GraphQLList<GraphQLList<GraphQLType> | GraphQLNonNull<GraphQLList<GraphQLType>>>;
17
+ /**
18
+ * Calculates the nesting level of a GraphQL list type
19
+ *
20
+ * Examples:
21
+ * - [String] → 1
22
+ * - [[String]] → 2
23
+ * - [[[String]]] → 3
24
+ *
25
+ * @param listType - The GraphQL list type to analyze
26
+ * @returns The nesting level (1 for simple list, 2+ for nested lists)
27
+ */
28
+ export declare function calculateNestingLevel(listType: GraphQLList<GraphQLType>): number;
@@ -0,0 +1,49 @@
1
+ import { isListType, isNonNullType } from 'graphql';
2
+ /**
3
+ * Unwraps a GraphQL type from a GraphQLNonNull wrapper
4
+ *
5
+ * @param graphqlType - The GraphQL type to unwrap
6
+ * @returns The unwrapped type
7
+ */
8
+ export function unwrapNonNullType(graphqlType) {
9
+ return isNonNullType(graphqlType) ? graphqlType.ofType : graphqlType;
10
+ }
11
+ /**
12
+ * Checks if a GraphQL list type contains nested lists
13
+ * Type guard that narrows the input type when nested lists are detected
14
+ *
15
+ * @param listType - The GraphQL list type to check
16
+ * @returns True if the list contains nested lists
17
+ */
18
+ export function isNestedListType(listType) {
19
+ return isListType(listType.ofType) || (isNonNullType(listType.ofType) && isListType(listType.ofType.ofType));
20
+ }
21
+ /**
22
+ * Calculates the nesting level of a GraphQL list type
23
+ *
24
+ * Examples:
25
+ * - [String] → 1
26
+ * - [[String]] → 2
27
+ * - [[[String]]] → 3
28
+ *
29
+ * @param listType - The GraphQL list type to analyze
30
+ * @returns The nesting level (1 for simple list, 2+ for nested lists)
31
+ */
32
+ export function calculateNestingLevel(listType) {
33
+ let level = 1;
34
+ let currentType = listType.ofType;
35
+ while (true) {
36
+ if (isNonNullType(currentType)) {
37
+ currentType = currentType.ofType;
38
+ }
39
+ else if (isListType(currentType)) {
40
+ currentType = currentType.ofType;
41
+ level++;
42
+ }
43
+ else {
44
+ break;
45
+ }
46
+ }
47
+ return level;
48
+ }
49
+ //# sourceMappingURL=list-type-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-type-utils.js","sourceRoot":"","sources":["../../../src/operations/list-type-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE9F;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAwB,WAAkC;IACzF,OAAO,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,CAAE,WAAW,CAAC,MAAY,CAAC,CAAC,CAAC,WAAW,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAkC;IAElC,OAAO,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/G,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAkC;IACtE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,WAAW,GAAgB,QAAQ,CAAC,MAAM,CAAC;IAE/C,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,aAAa,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/B,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC;QACnC,CAAC;aAAM,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YACnC,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC;YACjC,KAAK,EAAE,CAAC;QACV,CAAC;aAAM,CAAC;YACN,MAAM;QACR,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,58 @@
1
+ import protobuf from 'protobufjs';
2
+ import { SelectionSetNode, GraphQLObjectType, GraphQLType, GraphQLSchema, TypeInfo, FragmentDefinitionNode, GraphQLOutputType, GraphQLInterfaceType, GraphQLUnionType } from 'graphql';
3
+ import { FieldNumberManager } from './field-numbering.js';
4
+ /**
5
+ * Options for building proto messages
6
+ */
7
+ export interface MessageBuilderOptions {
8
+ /** Whether to include comments/descriptions */
9
+ includeComments?: boolean;
10
+ /** Root object for adding nested types */
11
+ root?: protobuf.Root;
12
+ /** Field number manager for consistent numbering */
13
+ fieldNumberManager?: FieldNumberManager;
14
+ /** Map of fragment definitions for resolving fragment spreads */
15
+ fragments?: Map<string, FragmentDefinitionNode>;
16
+ /** Schema for type lookups */
17
+ schema?: GraphQLSchema;
18
+ /** Set to track created enums (to avoid duplicates) */
19
+ createdEnums?: Set<string>;
20
+ /** Custom scalar type mappings (scalar name -> proto type) */
21
+ customScalarMappings?: Record<string, string>;
22
+ /** Maximum recursion depth to prevent stack overflow (default: 50) */
23
+ maxDepth?: number;
24
+ /** Internal: Current recursion depth */
25
+ _depth?: number;
26
+ /** Callback to ensure nested list wrapper messages are created */
27
+ ensureNestedListWrapper?: (graphqlType: GraphQLOutputType) => string;
28
+ }
29
+ /**
30
+ * Builds a Protocol Buffer message type from a GraphQL selection set
31
+ *
32
+ * @param messageName - The name for the proto message
33
+ * @param selectionSet - The GraphQL selection set to convert
34
+ * @param parentType - The GraphQL type that contains these selections
35
+ * @param typeInfo - TypeInfo for resolving field types
36
+ * @param options - Optional configuration
37
+ * @returns A protobuf Type object
38
+ */
39
+ export declare function buildMessageFromSelectionSet(messageName: string, selectionSet: SelectionSetNode, parentType: GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType, typeInfo: TypeInfo, options?: MessageBuilderOptions): protobuf.Type;
40
+ /**
41
+ * Builds a field definition for a proto message
42
+ *
43
+ * @param fieldName - The name of the field
44
+ * @param fieldType - The GraphQL type of the field
45
+ * @param fieldNumber - The proto field number
46
+ * @param options - Optional configuration
47
+ * @returns A protobuf Field object
48
+ */
49
+ export declare function buildFieldDefinition(fieldName: string, fieldType: GraphQLType, fieldNumber: number, options?: MessageBuilderOptions): protobuf.Field;
50
+ /**
51
+ * Builds a nested message type
52
+ *
53
+ * @param messageName - The name for the nested message
54
+ * @param fields - Map of field names to their GraphQL types
55
+ * @param options - Optional configuration
56
+ * @returns A protobuf Type object
57
+ */
58
+ export declare function buildNestedMessage(messageName: string, fields: Map<string, GraphQLType>, options?: MessageBuilderOptions): protobuf.Type;
@@ -0,0 +1,377 @@
1
+ import protobuf from 'protobufjs';
2
+ import { isObjectType, isEnumType, getNamedType, isInterfaceType, isUnionType, } from 'graphql';
3
+ import { mapGraphQLTypeToProto } from './type-mapper.js';
4
+ import { assignFieldNumbersFromLockData } from './field-numbering.js';
5
+ import { graphqlFieldToProtoField } from '../naming-conventions.js';
6
+ import { buildEnumType } from './request-builder.js';
7
+ import { upperFirst, camelCase } from 'lodash-es';
8
+ /**
9
+ * Default maximum recursion depth to prevent stack overflow
10
+ */
11
+ const DEFAULT_MAX_DEPTH = 50;
12
+ /**
13
+ * Default starting depth for recursion tracking
14
+ */
15
+ const DEFAULT_STARTING_DEPTH = 0;
16
+ /**
17
+ * Builds a Protocol Buffer message type from a GraphQL selection set
18
+ *
19
+ * @param messageName - The name for the proto message
20
+ * @param selectionSet - The GraphQL selection set to convert
21
+ * @param parentType - The GraphQL type that contains these selections
22
+ * @param typeInfo - TypeInfo for resolving field types
23
+ * @param options - Optional configuration
24
+ * @returns A protobuf Type object
25
+ */
26
+ export function buildMessageFromSelectionSet(messageName, selectionSet, parentType, typeInfo, options) {
27
+ var _a, _b;
28
+ const message = new protobuf.Type(messageName);
29
+ const fieldNumberManager = options === null || options === void 0 ? void 0 : options.fieldNumberManager;
30
+ // First pass: collect all field names that will be in this message
31
+ const fieldNames = [];
32
+ const fieldSelections = new Map();
33
+ // Maximum recursion depth to prevent stack overflow
34
+ const maxDepth = (_a = options === null || options === void 0 ? void 0 : options.maxDepth) !== null && _a !== void 0 ? _a : DEFAULT_MAX_DEPTH;
35
+ const currentDepth = (_b = options === null || options === void 0 ? void 0 : options._depth) !== null && _b !== void 0 ? _b : DEFAULT_STARTING_DEPTH;
36
+ // Check depth limit at the start of building each message
37
+ if (currentDepth > maxDepth) {
38
+ throw new Error(`Maximum recursion depth (${maxDepth}) exceeded while processing selection set. ` +
39
+ `This may indicate deeply nested selections or circular fragment references. ` +
40
+ `You can increase the limit using the maxDepth option.`);
41
+ }
42
+ /**
43
+ * Recursively collects fields from selections with protection against excessive recursion depth.
44
+ *
45
+ * Note: Circular fragment references are invalid GraphQL per the spec's NoFragmentCyclesRule.
46
+ * GraphQL validation should catch these before reaching proto compilation.
47
+ */
48
+ const collectFields = (selections, currentType, depth) => {
49
+ // Stop condition: Check depth limit
50
+ if (depth > maxDepth) {
51
+ throw new Error(`Maximum recursion depth (${maxDepth}) exceeded while processing selection set. ` +
52
+ `This may indicate deeply nested selections or circular fragment references. ` +
53
+ `You can increase the limit using the maxDepth option.`);
54
+ }
55
+ for (const selection of selections) {
56
+ switch (selection.kind) {
57
+ case 'Field':
58
+ // Only object and interface types have fields that can be selected
59
+ // Union types require inline fragments to access their constituent types
60
+ if (isObjectType(currentType) || isInterfaceType(currentType)) {
61
+ const fieldName = selection.name.value;
62
+ const protoFieldName = graphqlFieldToProtoField(fieldName);
63
+ if (!fieldNames.includes(protoFieldName)) {
64
+ fieldNames.push(protoFieldName);
65
+ fieldSelections.set(protoFieldName, { selection, type: currentType });
66
+ }
67
+ }
68
+ break;
69
+ case 'InlineFragment':
70
+ if (selection.typeCondition && (options === null || options === void 0 ? void 0 : options.schema)) {
71
+ const typeName = selection.typeCondition.name.value;
72
+ const type = options.schema.getType(typeName);
73
+ if (type && (isObjectType(type) || isInterfaceType(type))) {
74
+ collectFields(selection.selectionSet.selections, type, depth + 1);
75
+ }
76
+ }
77
+ else if (isObjectType(currentType) || isInterfaceType(currentType)) {
78
+ // No type condition, but parent type supports fields
79
+ collectFields(selection.selectionSet.selections, currentType, depth + 1);
80
+ }
81
+ break;
82
+ case 'FragmentSpread':
83
+ if (options === null || options === void 0 ? void 0 : options.fragments) {
84
+ const fragmentDef = options.fragments.get(selection.name.value);
85
+ if (fragmentDef && (options === null || options === void 0 ? void 0 : options.schema)) {
86
+ const typeName = fragmentDef.typeCondition.name.value;
87
+ const type = options.schema.getType(typeName);
88
+ if (type && (isObjectType(type) || isInterfaceType(type) || isUnionType(type))) {
89
+ collectFields(fragmentDef.selectionSet.selections, type, depth + 1);
90
+ }
91
+ }
92
+ }
93
+ break;
94
+ }
95
+ }
96
+ };
97
+ // Collect fields from the selection set
98
+ // For union types, only inline fragments will contribute fields (handled in collectFields)
99
+ collectFields(selectionSet.selections, parentType, currentDepth);
100
+ // Reconcile field order using lock manager if available
101
+ let orderedFieldNames = fieldNames;
102
+ if (fieldNumberManager && 'reconcileFieldOrder' in fieldNumberManager) {
103
+ orderedFieldNames = fieldNumberManager.reconcileFieldOrder(messageName, fieldNames);
104
+ }
105
+ // Second pass: process fields in reconciled order
106
+ // Pre-assign field numbers from lock data if available
107
+ assignFieldNumbersFromLockData(messageName, orderedFieldNames, fieldNumberManager);
108
+ for (const protoFieldName of orderedFieldNames) {
109
+ const fieldData = fieldSelections.get(protoFieldName);
110
+ if (fieldData) {
111
+ const fieldOptions = {
112
+ ...options,
113
+ _depth: currentDepth,
114
+ };
115
+ processFieldSelection(fieldData.selection, message, fieldData.type, typeInfo, fieldOptions, fieldNumberManager);
116
+ }
117
+ }
118
+ return message;
119
+ }
120
+ /**
121
+ * Gets or assigns a field number for a proto field
122
+ */
123
+ function getOrAssignFieldNumber(message, protoFieldName, fieldNumberManager) {
124
+ const existingFieldNumber = fieldNumberManager === null || fieldNumberManager === void 0 ? void 0 : fieldNumberManager.getFieldNumber(message.name, protoFieldName);
125
+ if (existingFieldNumber !== undefined) {
126
+ return existingFieldNumber;
127
+ }
128
+ if (fieldNumberManager) {
129
+ const fieldNumber = fieldNumberManager.getNextFieldNumber(message.name);
130
+ fieldNumberManager.assignFieldNumber(message.name, protoFieldName, fieldNumber);
131
+ return fieldNumber;
132
+ }
133
+ return message.fieldsArray.length + 1;
134
+ }
135
+ /**
136
+ * Resolves the final type name and repetition flag, handling nested list wrappers
137
+ */
138
+ function resolveTypeNameAndRepetition(baseTypeName, protoTypeInfo, fieldType, options) {
139
+ let typeName = baseTypeName;
140
+ let isRepeated = protoTypeInfo.isRepeated;
141
+ if (protoTypeInfo.requiresNestedWrapper && (options === null || options === void 0 ? void 0 : options.ensureNestedListWrapper)) {
142
+ typeName = options.ensureNestedListWrapper(fieldType);
143
+ isRepeated = false; // Wrapper handles the repetition
144
+ }
145
+ return { typeName, isRepeated };
146
+ }
147
+ /**
148
+ * Creates and configures a proto field with comments
149
+ */
150
+ function createProtoField(protoFieldName, fieldNumber, typeName, isRepeated, fieldDef, options) {
151
+ const protoField = new protobuf.Field(protoFieldName, fieldNumber, typeName);
152
+ if (isRepeated) {
153
+ protoField.repeated = true;
154
+ }
155
+ if ((options === null || options === void 0 ? void 0 : options.includeComments) && fieldDef.description) {
156
+ protoField.comment = fieldDef.description;
157
+ }
158
+ return protoField;
159
+ }
160
+ /**
161
+ * Ensures an enum type is created and added to the root
162
+ */
163
+ function ensureEnumCreated(namedType, options) {
164
+ if (!options.root) {
165
+ return;
166
+ }
167
+ const enumTypeName = namedType.name;
168
+ // Initialize createdEnums in options if missing to ensure persistence across calls
169
+ if (!options.createdEnums) {
170
+ options.createdEnums = new Set();
171
+ }
172
+ if (!options.createdEnums.has(enumTypeName)) {
173
+ const protoEnum = buildEnumType(namedType, {
174
+ includeComments: options.includeComments,
175
+ });
176
+ options.root.add(protoEnum);
177
+ options.createdEnums.add(enumTypeName);
178
+ }
179
+ }
180
+ /**
181
+ * Processes a field selection and adds it to the message
182
+ */
183
+ function processFieldSelection(field, message, parentType, typeInfo, options, fieldNumberManager) {
184
+ var _a;
185
+ const fieldName = field.name.value;
186
+ // Skip __typename - it's a GraphQL introspection field that doesn't need to be in proto
187
+ if (fieldName === '__typename') {
188
+ return;
189
+ }
190
+ const protoFieldName = graphqlFieldToProtoField(fieldName);
191
+ // Check if field already exists in the message (avoid duplicates)
192
+ if (message.fields[protoFieldName]) {
193
+ return; // Field already added, skip
194
+ }
195
+ // Get the field definition from the parent type
196
+ // Union types don't have fields directly, so skip field validation for them
197
+ if (isUnionType(parentType)) {
198
+ // Union types should only be processed through inline fragments
199
+ // This shouldn't happen in normal GraphQL, but we'll handle it gracefully
200
+ return;
201
+ }
202
+ const fieldDef = parentType.getFields()[fieldName];
203
+ if (!fieldDef) {
204
+ throw new Error(`Field "${fieldName}" does not exist on type "${parentType.name}". ` +
205
+ `GraphQL validation should be performed before proto compilation.`);
206
+ }
207
+ const fieldType = fieldDef.type;
208
+ // Determine the base type name based on whether we have a selection set
209
+ let baseTypeName;
210
+ if (field.selectionSet) {
211
+ // Build nested message for object types
212
+ const namedType = getNamedType(fieldType);
213
+ if (isObjectType(namedType) || isInterfaceType(namedType) || isUnionType(namedType)) {
214
+ const nestedMessageName = upperFirst(camelCase(fieldName));
215
+ const nestedOptions = {
216
+ ...options,
217
+ _depth: ((_a = options === null || options === void 0 ? void 0 : options._depth) !== null && _a !== void 0 ? _a : 0) + 1,
218
+ };
219
+ const nestedMessage = buildMessageFromSelectionSet(nestedMessageName, field.selectionSet, namedType, typeInfo, nestedOptions);
220
+ message.add(nestedMessage);
221
+ baseTypeName = nestedMessageName;
222
+ }
223
+ else {
224
+ return; // Shouldn't happen with valid GraphQL
225
+ }
226
+ }
227
+ else {
228
+ // Handle scalar/enum fields
229
+ const namedType = getNamedType(fieldType);
230
+ if (isEnumType(namedType) && (options === null || options === void 0 ? void 0 : options.root)) {
231
+ ensureEnumCreated(namedType, options);
232
+ }
233
+ const protoTypeInfo = mapGraphQLTypeToProto(fieldType, {
234
+ customScalarMappings: options === null || options === void 0 ? void 0 : options.customScalarMappings,
235
+ });
236
+ baseTypeName = protoTypeInfo.typeName;
237
+ }
238
+ // Common logic for both branches
239
+ const protoTypeInfo = mapGraphQLTypeToProto(fieldType, {
240
+ customScalarMappings: options === null || options === void 0 ? void 0 : options.customScalarMappings,
241
+ });
242
+ const { typeName, isRepeated } = resolveTypeNameAndRepetition(baseTypeName, protoTypeInfo, fieldType, options);
243
+ const fieldNumber = getOrAssignFieldNumber(message, protoFieldName, fieldNumberManager);
244
+ const protoField = createProtoField(protoFieldName, fieldNumber, typeName, isRepeated, fieldDef, options);
245
+ message.add(protoField);
246
+ }
247
+ /**
248
+ * Processes an inline fragment and adds its selections to the message
249
+ * Inline fragments allow type-specific field selections on interfaces/unions
250
+ */
251
+ function processInlineFragment(fragment, message, parentType, typeInfo, options, fieldNumberManager) {
252
+ // Determine the type for this inline fragment
253
+ let fragmentType;
254
+ if (fragment.typeCondition) {
255
+ // Type condition specified: ... on User
256
+ const typeName = fragment.typeCondition.name.value;
257
+ const schema = options === null || options === void 0 ? void 0 : options.schema;
258
+ if (!schema) {
259
+ // Without schema, we can't resolve the type - skip
260
+ return;
261
+ }
262
+ const type = schema.getType(typeName);
263
+ if (!type || !(isObjectType(type) || isInterfaceType(type) || isUnionType(type))) {
264
+ // Type not found or not a supported type - skip
265
+ return;
266
+ }
267
+ fragmentType = type;
268
+ }
269
+ else {
270
+ // No type condition: just process with parent type
271
+ fragmentType = parentType;
272
+ }
273
+ // Process all selections in the inline fragment with the resolved type
274
+ if (fragment.selectionSet) {
275
+ for (const selection of fragment.selectionSet.selections) {
276
+ if (selection.kind === 'Field') {
277
+ processFieldSelection(selection, message, fragmentType, typeInfo, options, fieldNumberManager);
278
+ }
279
+ else if (selection.kind === 'InlineFragment') {
280
+ // Nested inline fragment
281
+ processInlineFragment(selection, message, fragmentType, typeInfo, options, fieldNumberManager);
282
+ }
283
+ else if (selection.kind === 'FragmentSpread') {
284
+ processFragmentSpread(selection, message, fragmentType, typeInfo, options, fieldNumberManager);
285
+ }
286
+ }
287
+ }
288
+ }
289
+ /**
290
+ * Processes a fragment spread and adds its selections to the message
291
+ * Fragment spreads reference named fragment definitions
292
+ */
293
+ function processFragmentSpread(spread, message, parentType, typeInfo, options, fieldNumberManager) {
294
+ const fragmentName = spread.name.value;
295
+ const fragments = options === null || options === void 0 ? void 0 : options.fragments;
296
+ if (!fragments) {
297
+ // No fragments provided - skip
298
+ return;
299
+ }
300
+ const fragmentDef = fragments.get(fragmentName);
301
+ if (!fragmentDef) {
302
+ // Fragment definition not found - skip
303
+ return;
304
+ }
305
+ // Resolve the fragment's type condition
306
+ const typeName = fragmentDef.typeCondition.name.value;
307
+ const schema = options === null || options === void 0 ? void 0 : options.schema;
308
+ if (!schema) {
309
+ // Without schema, we can't resolve the type - skip
310
+ return;
311
+ }
312
+ const type = schema.getType(typeName);
313
+ if (!type || !(isObjectType(type) || isInterfaceType(type) || isUnionType(type))) {
314
+ // Type not found or not a supported type - skip
315
+ return;
316
+ }
317
+ // Process the fragment's selection set with the resolved type
318
+ for (const selection of fragmentDef.selectionSet.selections) {
319
+ if (selection.kind === 'Field') {
320
+ processFieldSelection(selection, message, type, typeInfo, options, fieldNumberManager);
321
+ }
322
+ else if (selection.kind === 'InlineFragment') {
323
+ processInlineFragment(selection, message, type, typeInfo, options, fieldNumberManager);
324
+ }
325
+ else if (selection.kind === 'FragmentSpread') {
326
+ // Nested fragment spread (fragment inside fragment)
327
+ processFragmentSpread(selection, message, type, typeInfo, options, fieldNumberManager);
328
+ }
329
+ }
330
+ }
331
+ /**
332
+ * Builds a field definition for a proto message
333
+ *
334
+ * @param fieldName - The name of the field
335
+ * @param fieldType - The GraphQL type of the field
336
+ * @param fieldNumber - The proto field number
337
+ * @param options - Optional configuration
338
+ * @returns A protobuf Field object
339
+ */
340
+ export function buildFieldDefinition(fieldName, fieldType, fieldNumber, options) {
341
+ const protoFieldName = graphqlFieldToProtoField(fieldName);
342
+ const typeInfo = mapGraphQLTypeToProto(fieldType, {
343
+ customScalarMappings: options === null || options === void 0 ? void 0 : options.customScalarMappings,
344
+ });
345
+ const field = new protobuf.Field(protoFieldName, fieldNumber, typeInfo.typeName);
346
+ if (typeInfo.isRepeated) {
347
+ field.repeated = true;
348
+ }
349
+ return field;
350
+ }
351
+ /**
352
+ * Builds a nested message type
353
+ *
354
+ * @param messageName - The name for the nested message
355
+ * @param fields - Map of field names to their GraphQL types
356
+ * @param options - Optional configuration
357
+ * @returns A protobuf Type object
358
+ */
359
+ export function buildNestedMessage(messageName, fields, options) {
360
+ const message = new protobuf.Type(messageName);
361
+ const fieldNumberManager = options === null || options === void 0 ? void 0 : options.fieldNumberManager;
362
+ let fieldNumber = 1;
363
+ for (const [fieldName, fieldType] of fields.entries()) {
364
+ const protoFieldName = graphqlFieldToProtoField(fieldName);
365
+ if (fieldNumberManager) {
366
+ fieldNumber = fieldNumberManager.getNextFieldNumber(messageName);
367
+ fieldNumberManager.assignFieldNumber(messageName, protoFieldName, fieldNumber);
368
+ }
369
+ const field = buildFieldDefinition(fieldName, fieldType, fieldNumber, options);
370
+ message.add(field);
371
+ if (!fieldNumberManager) {
372
+ fieldNumber++;
373
+ }
374
+ }
375
+ return message;
376
+ }
377
+ //# sourceMappingURL=message-builder.js.map