@@ -73,6 +73,7 @@ query Selection {
7373```
7474
7575The composed schema will include:
76+
7677- Only the selected types: ` vehicle ` , ` adas ` , ` abs `
7778- Only the selected fields within each type
7879- Types referenced by field arguments (e.g., enums used in field arguments)
@@ -109,6 +110,376 @@ type InCabinArea2x2 @instanceTag @reference(source: "S2DM Spec") {
109110
110111## Export Commands
111112
113+ ### Protocol Buffers (Protobuf)
114+
115+ This exporter translates the given GraphQL schema to [Protocol Buffers](https://protobuf.dev/) (`.proto`) format.
116+
117+ #### Key Features
118+
119+ - **Complete GraphQL Type Support**: Handles all GraphQL types including scalars, objects, enums, unions, interfaces, and lists
120+ - **Root Type Filtering**: Use the `--root-type` flag to export only a specific type and its dependencies
121+ - **Flatten Naming Mode**: Use the `--flatten-naming` flag to flatten nested structures into a single message with prefixed field names
122+ - **Expanded Instance Tags**: Use the `--expanded-instances` flag to transform instance tag arrays into nested message structures
123+ - **Directive Support**: Converts S2DM directives like `@cardinality `, `@range `, and `@noDuplicates ` to protovalidate constraints
124+ - **Package Name Support**: Use the `--package-name` flag to specify a protobuf package namespace
125+
126+ #### Example Transformation
127+
128+ Consider the following GraphQL schema:
129+
130+ ```graphql
131+ type Cabin {
132+ doors : [Door ]
133+ temperature : Float
134+ }
135+
136+ type Door {
137+ isLocked : Boolean
138+ instanceTag : DoorPosition
139+ }
140+
141+ type DoorPosition @instanceTag {
142+ row : RowEnum
143+ side : SideEnum
144+ }
145+
146+ enum RowEnum {
147+ ROW1
148+ ROW2
149+ }
150+
151+ enum SideEnum {
152+ DRIVERSIDE
153+ PASSENGERSIDE
154+ }
155+ ```
156+
157+ The Protobuf exporter produces:
158+
159+ ``` protobuf
160+ syntax = "proto3";
161+
162+ import "google/protobuf/descriptor.proto";
163+ import "buf/validate/validate.proto";
164+
165+ extend google.protobuf.MessageOptions {
166+ string source = 50001;
167+ }
168+
169+ message RowEnum {
170+ option (source) = "RowEnum";
171+
172+ enum Enum {
173+ ROWENUM_UNSPECIFIED = 0;
174+ ROW1 = 1;
175+ ROW2 = 2;
176+ }
177+ }
178+
179+ message SideEnum {
180+ option (source) = "SideEnum";
181+
182+ enum Enum {
183+ SIDEENUM_UNSPECIFIED = 0;
184+ DRIVERSIDE = 1;
185+ PASSENGERSIDE = 2;
186+ }
187+ }
188+
189+ message DoorPosition {
190+ option (source) = "DoorPosition";
191+
192+ RowEnum.Enum row = 1;
193+ SideEnum.Enum side = 2;
194+ }
195+
196+ message Cabin {
197+ option (source) = "Cabin";
198+
199+ repeated Door doors = 1;
200+ float temperature = 2;
201+ }
202+
203+ message Door {
204+ option (source) = "Door";
205+
206+ bool isLocked = 1;
207+ DoorPosition instanceTag = 2;
208+ }
209+ ```
210+
211+ #### Root Type Filtering
212+
213+ Use the ` --root-type ` flag to export only a specific type and its dependencies:
214+
215+ ``` bash
216+ s2dm export protobuf --schema schema.graphql --output vehicle.proto --root-type Vehicle
217+ ```
218+
219+ This will include only the ` Vehicle ` type and all types transitively referenced by it.
220+
221+ #### Flatten Naming Mode
222+
223+ Use the ` --flatten-naming ` flag to flatten nested object structures into a single message with prefixed field names. This mode requires ` --root-type ` to be set:
224+
225+ ``` bash
226+ s2dm export protobuf --schema schema.graphql --output vehicle.proto --root-type Vehicle --flatten-naming
227+ ```
228+
229+ ** Example transformation:**
230+
231+ Given a GraphQL schema:
232+
233+ ``` graphql
234+ type Vehicle {
235+ adas : ADAS
236+ }
237+
238+ type ADAS {
239+ abs : ABS
240+ }
241+
242+ type ABS {
243+ isEngaged : Boolean
244+ }
245+ ```
246+
247+ Flatten mode produces :
248+
249+ ```protobuf
250+ syntax = "proto3" ;
251+
252+ import "google/protobuf/descriptor.proto" ;
253+ import "buf/validate/validate.proto" ;
254+
255+ extend google .protobuf .MessageOptions {
256+ string source = 50001;
257+ }
258+
259+ message Message {
260+ bool Vehicle_adas_abs_isEngaged = 1;
261+ }
262+
263+ ```
264+
265+ #### Expanded Instance Tags
266+
267+ The ` --expanded-instances ` flag transforms instance tag objects into nested message structures instead of repeated fields. This provides compile-time type safety for accessing specific instances.
268+
269+ ``` bash
270+ s2dm export protobuf --schema schema.graphql --output cabin.proto --expanded-instances
271+ ```
272+
273+ ** Default behavior (without flag):**
274+
275+ Given a GraphQL schema with instance tags:
276+
277+ ``` graphql
278+ type Cabin {
279+ doors : [Door ]
280+ }
281+
282+ type Door {
283+ isLocked : Boolean
284+ instanceTag : DoorPosition
285+ }
286+
287+ type DoorPosition @instanceTag {
288+ row : RowEnum
289+ side : SideEnum
290+ }
291+
292+ enum RowEnum {
293+ ROW1
294+ ROW2
295+ }
296+
297+ enum SideEnum {
298+ DRIVERSIDE
299+ PASSENGERSIDE
300+ }
301+ ```
302+
303+ Default output uses repeated fields and includes the instanceTag field:
304+
305+ ``` protobuf
306+ syntax = "proto3";
307+
308+ import "google/protobuf/descriptor.proto";
309+ import "buf/validate/validate.proto";
310+
311+ extend google.protobuf.MessageOptions {
312+ string source = 50001;
313+ }
314+
315+ message RowEnum {
316+ option (source) = "RowEnum";
317+
318+ enum Enum {
319+ ROWENUM_UNSPECIFIED = 0;
320+ ROW1 = 1;
321+ ROW2 = 2;
322+ }
323+ }
324+
325+ message SideEnum {
326+ option (source) = "SideEnum";
327+
328+ enum Enum {
329+ SIDEENUM_UNSPECIFIED = 0;
330+ DRIVERSIDE = 1;
331+ PASSENGERSIDE = 2;
332+ }
333+ }
334+
335+ message Door {
336+ option (source) = "Door";
337+
338+ bool isLocked = 1;
339+ DoorPosition instanceTag = 2;
340+ }
341+
342+
343+ message Cabin {
344+ option (source) = "Cabin";
345+
346+ repeated Door doors = 1;
347+ }
348+
349+
350+ message DoorPosition {
351+ option (source) = "DoorPosition";
352+
353+ RowEnum.Enum row = 1;
354+ SideEnum.Enum side = 2;
355+ }
356+ ```
357+
358+ ** With ` --expanded-instances ` flag:**
359+
360+ The same schema produces nested messages representing the cartesian product of instance tag values:
361+
362+ ``` protobuf
363+ syntax = "proto3";
364+
365+ import "google/protobuf/descriptor.proto";
366+ import "buf/validate/validate.proto";
367+
368+ extend google.protobuf.MessageOptions {
369+ string source = 50001;
370+ }
371+
372+ message Door {
373+ option (source) = "Door";
374+
375+ bool isLocked = 1;
376+ }
377+
378+
379+ message Cabin {
380+ option (source) = "Cabin";
381+
382+ message Cabin_Door {
383+ message Cabin_Door_ROW1 {
384+ Door DRIVERSIDE = 1;
385+ Door PASSENGERSIDE = 2;
386+ }
387+
388+ message Cabin_Door_ROW2 {
389+ Door DRIVERSIDE = 1;
390+ Door PASSENGERSIDE = 2;
391+ }
392+
393+ Cabin_Door_ROW1 ROW1 = 1;
394+ Cabin_Door_ROW2 ROW2 = 2;
395+ }
396+
397+ Cabin_Door Door = 1;
398+ }
399+ ```
400+
401+ ** Key differences:**
402+
403+ - Instance tag enums (` RowEnum ` , ` SideEnum ` ) are excluded from the output when using expanded instances
404+ - Types with ` @instanceTag ` directive (` DoorPosition ` ) are excluded from the output
405+ - The ` instanceTag ` field is excluded from the Door message
406+ - Nested messages are created inside the parent message
407+ - Field names use the GraphQL type name (` Door ` not ` doors ` )
408+
409+ #### Directive Support
410+
411+ S2DM directives are converted to [ protovalidate] ( https://github.com/bufbuild/protovalidate ) constraints:
412+
413+ - ` @range(min: 0, max: 100) ` → ` [(buf.validate.field).int32 = {gte: 0, lte: 100}] `
414+ - ` @noDuplicates ` → ` [(buf.validate.field).repeated = {unique: true}] `
415+ - ` @cardinality(min: 1, max: 5) ` → ` [(buf.validate.field).repeated = {min_items: 1, max_items: 5}] `
416+
417+ Example:
418+
419+ ``` graphql
420+ type Vehicle {
421+ speed : Int @range (min : 0 , max : 300 )
422+ tags : [String ] @noDuplicates @cardinality (min : 1 , max : 10 )
423+ }
424+ ```
425+
426+ Produces :
427+
428+ ```protobuf
429+ syntax = "proto3" ;
430+
431+ import "google/protobuf/descriptor.proto" ;
432+ import "buf/validate/validate.proto" ;
433+
434+ extend google .protobuf .MessageOptions {
435+ string source = 50001;
436+ }
437+
438+ message Vehicle {
439+ option (source) = "Vehicle" ;
440+
441+ int32 speed = 1 [(buf.validate.field).int32 = {gte : 0, lte : 300}];
442+ repeated string tags = 2 [(buf.validate.field).repeated = {unique : true , min_items : 1, max_items : 10}];
443+ }
444+ ```
445+
446+ #### Type Mappings
447+
448+ GraphQL types are mapped to protobuf types as follows:
449+
450+ | GraphQL Type | Protobuf Type |
451+ | --------------| ---------------|
452+ | ` String ` | ` string ` |
453+ | ` Int ` | ` int32 ` |
454+ | ` Float ` | ` float ` |
455+ | ` Boolean ` | ` bool ` |
456+ | ` ID ` | ` string ` |
457+ | ` Int8 ` | ` int32 ` |
458+ | ` UInt8 ` | ` uint32 ` |
459+ | ` Int16 ` | ` int32 ` |
460+ | ` UInt16 ` | ` uint32 ` |
461+ | ` UInt32 ` | ` uint32 ` |
462+ | ` Int64 ` | ` int64 ` |
463+ | ` UInt64 ` | ` uint64 ` |
464+
465+ ** List types** are converted to ` repeated ` fields:
466+
467+ - ` [String] ` → ` repeated string `
468+ - ` [Int] ` → ` repeated int32 `
469+
470+ ** Enums** are converted to protobuf enums wrapped in a message:
471+
472+ - Each GraphQL enum becomes a protobuf message with the same name
473+ - Inside the message, an ` Enum ` nested enum is created
474+ - An ` UNSPECIFIED ` value is added at position 0
475+ - References use the ` .Enum ` suffix (e.g., ` LockStatus.Enum ` )
476+
477+ You can call the help for usage reference:
478+
479+ ``` bash
480+ s2dm export protobuf --help
481+ ```
482+
112483### Naming Configuration
113484
114485All export commands support a global naming configuration feature that allows you to transform element names during the export process using the ` [--naming-config | -n] ` flag.
0 commit comments