feat: generate proto from operations and sdl#2302
Conversation
|
Warning Rate limit exceeded@asoorm has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 45 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (2)
WalkthroughAdds an operations-based GraphQL→Protobuf compilation pipeline (compileOperationsToProto) with AST-to-proto merging, field-number lock management, type/message/request builders, proto-text rendering with language options, CLI options and file handling for operations mode, expanded protographic exports, extensive tests, and documentation; SDL-based flow remains supported. Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Areas needing extra attention:
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
cb28579 to
e284194
Compare
❌ Internal Query Planner CI checks failedThe Internal Query Planner CI checks failed in the celestial repository, and this is going to stop the merge of this PR. |
efd48b7 to
9ff4661
Compare
c1fa72f to
51d2da7
Compare
Maps GraphQL types to Protocol Buffer types with full type system support: - Scalars: nullable → wrapper types, non-null → direct types - Lists: mapped to repeated fields - Complex types: objects, enums, input objects mapped by name - Custom scalar mappings and configuration options
Manages field number assignment with collision avoidance. Tracks numbers per-message, supports manual assignment and reset.
Builds request messages from operation variables with input object and enum support. Handles variable type conversion and field numbering.
Converts protobuf AST to formatted proto text with proper syntax, package declarations, imports, and indentation.
Wires together all modules to provide complete operation-to-proto conversion. Adds input object processing and updates exports.
Import Kind from graphql and use Kind.SELECTION_SET instead of 'SelectionSet' string to fix type error in test.
…eration-to-proto Add support for named fragments (fragment spreads) and inline fragments. Fragments are expanded inline to produce self-contained proto messages with deterministic field ordering, ensuring wire compatibility across router restarts. - Collect fragment definitions from GraphQL documents - Recursively resolve fragment spreads and inline fragments - Handle interfaces, unions, and nested fragment compositions - Prevent duplicate field additions
Adds a new queryNoSideEffects option to compileOperationsToProto that marks Query operations with 'option idempotency_level = NO_SIDE_EFFECTS;' in the generated proto definition. Mutations are not affected by this option.
b817019 to
e47cb2f
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
protographic/src/sdl-to-proto-visitor.ts (1)
1989-2088: List wrapper implementation and documentation are out of syncThe docstrings for
handleListType/createNestedListWrapperdescribe a level‑1 wrapper like:message ListOfString { repeated string items = 1; }but
buildWrapperMessagealways generates an innerListmessage:message ListOfString { message List { repeated string items = 1; } List list = 1; }This is functionally fine, but the comments no longer match what’s emitted, which is confusing when reasoning about nullable vs. nested list shapes. Either:
- Special‑case
level === 1inbuildWrapperMessageto match the documented “simple wrapper” shape; or- Update the examples in the comments to show the actual
Listnested message structure for both single‑ and multi‑level wrappers.I’d also briefly confirm that the operations path’s nested list wrappers (
ensureNestedListWrapper/list-type-utils) produce the same message names and shapes so SDL‑based and operations‑based generation stay consistent.
🧹 Nitpick comments (11)
protographic/src/sdl-to-proto-visitor.ts (1)
224-250: Simplify proto option wiring and double‑check go_package default behaviorYou’re manually reconstructing the
ProtoOptionsbag before callingbuildProtoOptions. SinceGraphQLToProtoTextVisitorOptionsalready extendsProtoOptions, you can avoid drift by passingoptionsdirectly:- const protoOptionsFromLanguageProps = buildProtoOptions( - { - goPackage: options.goPackage, - javaPackage: options.javaPackage, - javaOuterClassname: options.javaOuterClassname, - javaMultipleFiles: options.javaMultipleFiles, - csharpNamespace: options.csharpNamespace, - rubyPackage: options.rubyPackage, - phpNamespace: options.phpNamespace, - phpMetadataNamespace: options.phpMetadataNamespace, - objcClassPrefix: options.objcClassPrefix, - swiftPrefix: options.swiftPrefix, - }, - packageName, - ); + const protoOptionsFromLanguageProps = buildProtoOptions(options, packageName);Also note that
buildProtoOptionscurrently only emitsgo_packagewhenoptions.goPackageis truthy, so the “Generate default go_package if not provided” behavior isn’t actually triggered whengoPackageis omitted. If you intended to derive a default frompackageName, consider relaxing that guard inbuildProtoOptionsin a follow‑up.protographic/tests/operations/field-numbering.test.ts (1)
1-224: FieldNumberManager behavior is well coveredThese tests exercise all the important behaviors of
createFieldNumberManager(per‑message sequencing, manual assignment, resets, and mixed usage), which should make regressions obvious. If you ever wire aProtoLockManagerinto this flow, consider an additional test that passes a stub lock manager and assertsreconcileFieldOrder/getLockManagerinteractions, but that’s optional for now.protographic/src/types.ts (1)
1-14: Use a type‑only namespace import for protobuf typesSince
protobufis only used for typing and the rest of the codebase uses a namespace import, it’s cleaner and safer to switch to a type‑only namespace import:-import protobuf from 'protobufjs'; +import type * as protobuf from 'protobufjs';This avoids relying on a synthetic default export, prevents an unnecessary runtime import when compiling with
importsNotUsedAsValues = 'remove', and keeps the style consistent with other modules usingprotobufjs.protographic/tests/operations/request-builder.test.ts (1)
241-299: Prefer GraphQL type guards over constructor name checksIn multiple places you validate the looked‑up type with:
if (!inputType || inputType.constructor.name !== 'GraphQLInputObjectType') { throw new Error('Invalid input type'); }Since you already import
GraphQLInputObjectType, or could importisInputObjectType, this can be made more robust and intention‑revealing:import { /* ..., */ isInputObjectType } from 'graphql'; const inputType = schema.getType('UserInput'); if (!inputType || !isInputObjectType(inputType)) { throw new Error('Invalid input type'); }This avoids relying on
constructor.name(which can be brittle under minification or across multiple GraphQL instances) and keeps the tests aligned with GraphQL’s own helpers.protographic/tests/operations/fragments.test.ts (1)
324-353: Tighten fragment test naming and expectationsTwo small nits:
- The test
should handle fragments that reference non-existent fragmentsactually uses a single defined fragment (UserFields) and doesn’t exercise missing/undefined fragments. If the intent is simply “basic fragment usage without cycles”, consider renaming to avoid confusion for future readers.- In
should handle __typename field in fragments, the snapshot implicitly asserts that__typenameis dropped, but nothing explicitly checks for its absence. You could make the intent clearer and guard against regressions by also asserting thatprotodoes not contain__typename(or the corresponding field name) in addition to the snapshot.These are purely clarity/coverage tweaks; the existing assertions already exercise the core behavior.
Also applies to: 1849-1905
protographic/tests/operations/proto-text-generator.test.ts (1)
6-11: Reuse the exported MethodWithIdempotency type to avoid driftYou’re redefining a local
MethodWithIdempotencyinterface that matches the production type:interface MethodWithIdempotency extends protobuf.Method { idempotencyLevel?: 'NO_SIDE_EFFECTS' | 'DEFAULT'; }Now that
MethodWithIdempotency(andIdempotencyLevel) are exported fromsrc/types.ts/src/index.ts, you could import and use that instead:import type { MethodWithIdempotency } from '../../src';This keeps the tests in sync if you ever extend the allowed idempotency levels or adjust the interface.
protographic/tests/operations/recursion-protection.test.ts (1)
294-392: Clarify edge‑case test names and consider softening the timing assertionTwo minor suggestions:
should handle empty fragments gracefullyandshould handle fragments that reference non-existent fragmentsdon’t match their implementations: the former’s fragment isn’t empty, and the latter doesn’t reference any missing fragment. Renaming them to reflect what they actually test (e.g., “simple fragment spread without cycles”) would make the suite easier to read.- In
should handle reasonable depth efficiently, assertingendTime - startTime < 1000is probably fine, but it does introduce a small risk of flakiness on slow or contended CI runners. If this ever becomes noisy, you could either relax the threshold further or drop the timing assertion and rely on the fact that the test will naturally fail if compilation becomes pathologically slow.Functionally everything here looks correct; these are just resilience/clarity tweaks.
Also applies to: 395-431
protographic/src/operation-to-proto.ts (3)
181-212: Type fieldNumberManager explicitly to align with FieldNumberManager interface
fieldNumberManageris declared without a type annotation and only assigned in the constructor viacreateFieldNumberManager(this.lockManager). This effectively makes itanyunder typical TypeScript settings, which loses the structural contract (e.g.,reconcileFieldOrder,getLockManager, etc.) and weakens IDE support.Consider importing the type and annotating the field:
import type { FieldNumberManager } from './operations/field-numbering.js'; class OperationsToProtoVisitor { // ... private readonly fieldNumberManager: FieldNumberManager; // ... }This keeps the implementation unchanged while tightening type-safety around the field-number integration.
281-291: Reuse createOperationMethodName to keep RPC naming conventions centralized
processOperationcurrently derives the method name viaupperFirst(camelCase(operationName)), even though a dedicatedcreateOperationMethodNamehelper exists and is already imported. Duplicating the naming logic here can lead to drift if naming rules evolve (e.g., handling of punctuation, acronyms, or legacy conventions used elsewhere in protographic).You can delegate to the shared helper and then apply the optional operation-type prefix on top, e.g.:
let methodName = createOperationMethodName(operationName); if (this.prefixOperationType) { const operationTypePrefix = upperFirst(node.operation.toLowerCase()); methodName = `${operationTypePrefix}${methodName}`; }This keeps operations-based generation aligned with the rest of the codebase’s RPC naming semantics.
439-448: Consider a clearer error when the schema lacks the root operation type
getRootTypeuses non-null assertions onschema.getQueryType(),getMutationType(), andgetSubscriptionType(). If a caller passes a schema without a matching root type for the operation (e.g., amutationoperation against a schema withoutmutationroot), this will blow up with a generic runtime error rather than a targeted message.Since you already validate the operations, this is likely rare, but you could improve diagnostics by explicitly checking for
nulland throwing a more descriptive error:private getRootType(operationType: OperationTypeNode): GraphQLObjectType { switch (operationType) { case OperationTypeNode.QUERY: { const type = this.schema.getQueryType(); if (!type) throw new Error('Schema does not define a query root type.'); return type; } // similar for MUTATION/SUBSCRIPTION } }This is non-blocking but makes failures easier to debug when the schema and operations get out of sync.
protographic/src/operations/proto-text-generator.ts (1)
148-188: Avoid duplicating method formatting logic between serviceToProtoText and formatMethodRight now
serviceToProtoTexthand-rolls RPC formatting (including idempotency handling), whileformatMethodindependently formats a singlerpcline without idempotency support. This creates two slightly different code paths for essentially the same concern.Consider either:
- Making
serviceToProtoTextdelegate to an enhancedformatMethodthat also understandsMethodWithIdempotency, or- Updating
formatMethodto mirror the idempotency/streaming logic so callers using it directly get behaviour consistent withserviceToProtoText.This would reduce drift if you later tweak RPC formatting (e.g., adding more per-method options).
Also applies to: 425-440
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
protographic/src/index.ts(3 hunks)protographic/src/operation-to-proto.ts(1 hunks)protographic/src/operations/proto-text-generator.ts(1 hunks)protographic/src/sdl-to-proto-visitor.ts(4 hunks)protographic/src/types.ts(1 hunks)protographic/tests/operations/enum-support.test.ts(1 hunks)protographic/tests/operations/field-numbering.test.ts(1 hunks)protographic/tests/operations/field-ordering-stability.test.ts(1 hunks)protographic/tests/operations/fragment-spreads-interface-union.test.ts(1 hunks)protographic/tests/operations/fragments.test.ts(1 hunks)protographic/tests/operations/message-builder.test.ts(1 hunks)protographic/tests/operations/operation-to-proto.test.ts(1 hunks)protographic/tests/operations/operation-validation.test.ts(1 hunks)protographic/tests/operations/proto-text-generator.test.ts(1 hunks)protographic/tests/operations/recursion-protection.test.ts(1 hunks)protographic/tests/operations/request-builder.test.ts(1 hunks)protographic/tests/operations/type-mapper.test.ts(1 hunks)protographic/tests/operations/validation.test.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- protographic/tests/operations/validation.test.ts
- protographic/tests/operations/operation-validation.test.ts
- protographic/tests/operations/fragment-spreads-interface-union.test.ts
- protographic/tests/operations/message-builder.test.ts
- protographic/tests/operations/enum-support.test.ts
- protographic/tests/operations/type-mapper.test.ts
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-08-20T10:38:49.191Z
Learnt from: Noroth
Repo: wundergraph/cosmo PR: 2151
File: protographic/src/sdl-to-proto-visitor.ts:0-0
Timestamp: 2025-08-20T10:38:49.191Z
Learning: Inline `extend google.protobuf.FieldOptions` declarations in proto3 files work correctly with protoc, despite theoretical concerns about proto3 compatibility with extension declarations.
Applied to files:
protographic/tests/operations/fragments.test.tsprotographic/tests/operations/field-ordering-stability.test.tsprotographic/src/sdl-to-proto-visitor.ts
📚 Learning: 2025-10-27T09:45:41.622Z
Learnt from: Noroth
Repo: wundergraph/cosmo PR: 2290
File: protographic/src/sdl-to-proto-visitor.ts:1350-1390
Timestamp: 2025-10-27T09:45:41.622Z
Learning: In protographic/src/sdl-to-proto-visitor.ts, resolver-related messages (created by `createResolverRequestMessage` and `createResolverResponseMessage`) are special messages that should not be tracked in the proto lock file, unlike other message types.
Applied to files:
protographic/src/operation-to-proto.tsprotographic/src/sdl-to-proto-visitor.tsprotographic/src/operations/proto-text-generator.tsprotographic/src/index.ts
📚 Learning: 2025-10-01T20:39:16.113Z
Learnt from: SkArchon
Repo: wundergraph/cosmo PR: 2252
File: router-tests/telemetry/telemetry_test.go:9684-9693
Timestamp: 2025-10-01T20:39:16.113Z
Learning: Repo preference: In router-tests/telemetry/telemetry_test.go, keep strict > 0 assertions for request.operation.*Time (parsingTime, normalizationTime, validationTime, planningTime) in telemetry-related tests; do not relax to >= 0 unless CI flakiness is observed.
Applied to files:
protographic/tests/operations/operation-to-proto.test.ts
📚 Learning: 2025-09-10T09:53:42.914Z
Learnt from: JivusAyrus
Repo: wundergraph/cosmo PR: 2156
File: studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/checks/index.tsx:189-197
Timestamp: 2025-09-10T09:53:42.914Z
Learning: In protobuf-generated TypeScript code, repeated fields (arrays) are always initialized with default empty arrays and cannot be undefined, making defensive programming checks like `|| []` or optional chaining unnecessary for these fields.
Applied to files:
protographic/src/sdl-to-proto-visitor.ts
🧬 Code graph analysis (11)
protographic/tests/operations/fragments.test.ts (2)
protographic/src/operation-to-proto.ts (1)
compileOperationsToProto(87-141)protographic/tests/util.ts (1)
expectValidProto(29-31)
protographic/src/types.ts (1)
protographic/src/index.ts (2)
IdempotencyLevel(135-135)MethodWithIdempotency(135-135)
protographic/tests/operations/field-numbering.test.ts (2)
protographic/src/index.ts (1)
createFieldNumberManager(113-113)protographic/src/operations/field-numbering.ts (1)
createFieldNumberManager(76-204)
protographic/tests/operations/request-builder.test.ts (2)
protographic/src/operations/request-builder.ts (3)
buildRequestMessage(48-104)buildInputObjectMessage(180-262)buildEnumType(271-293)protographic/src/operations/field-numbering.ts (1)
createFieldNumberManager(76-204)
protographic/src/operation-to-proto.ts (7)
protographic/src/types.ts (2)
IdempotencyLevel(7-7)MethodWithIdempotency(12-14)protographic/src/operations/field-numbering.ts (1)
createFieldNumberManager(76-204)protographic/src/naming-conventions.ts (2)
createRequestMessageName(43-45)createResponseMessageName(50-52)protographic/src/operations/request-builder.ts (3)
buildRequestMessage(48-104)buildInputObjectMessage(180-262)buildEnumType(271-293)protographic/src/operations/message-builder.ts (1)
buildMessageFromSelectionSet(75-204)protographic/src/operations/proto-text-generator.ts (1)
rootToProtoText(53-78)protographic/src/operations/type-mapper.ts (1)
mapGraphQLTypeToProto(74-191)
protographic/tests/operations/field-ordering-stability.test.ts (2)
protographic/src/operation-to-proto.ts (1)
compileOperationsToProto(87-141)protographic/tests/util.ts (3)
expectValidProto(29-31)loadProtoFromText(39-43)getFieldNumbersFromMessage(52-67)
protographic/tests/operations/operation-to-proto.test.ts (2)
protographic/src/operation-to-proto.ts (1)
compileOperationsToProto(87-141)protographic/tests/util.ts (1)
expectValidProto(29-31)
protographic/tests/operations/proto-text-generator.test.ts (3)
protographic/src/types.ts (1)
MethodWithIdempotency(12-14)protographic/src/operations/proto-text-generator.ts (5)
rootToProtoText(53-78)serviceToProtoText(148-194)messageToProtoText(199-239)enumToProtoText(244-273)formatField(278-291)protographic/tests/util.ts (1)
expectValidProto(29-31)
protographic/tests/operations/recursion-protection.test.ts (2)
protographic/src/operation-to-proto.ts (1)
compileOperationsToProto(87-141)protographic/tests/util.ts (1)
expectValidProto(29-31)
protographic/src/sdl-to-proto-visitor.ts (2)
protographic/src/proto-options.ts (2)
ProtoOptions(4-15)buildProtoOptions(24-71)protographic/src/operations/list-type-utils.ts (3)
unwrapNonNullType(9-11)isNestedListType(20-24)calculateNestingLevel(37-53)
protographic/src/operations/proto-text-generator.ts (4)
protographic/src/sdl-to-proto-visitor.ts (2)
formatIndent(2217-2219)formatReservedNumbers(2119-2160)protographic/src/index.ts (7)
ProtoTextOptions(133-133)rootToProtoText(127-127)serviceToProtoText(128-128)messageToProtoText(129-129)enumToProtoText(130-130)MethodWithIdempotency(135-135)formatField(131-131)protographic/src/proto-options.ts (1)
buildProtoOptions(24-71)protographic/src/types.ts (1)
MethodWithIdempotency(12-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (go)
🔇 Additional comments (5)
protographic/src/sdl-to-proto-visitor.ts (1)
91-99: Exposing language proto options via visitor options looks goodExtending
GraphQLToProtoTextVisitorOptionsfromProtoOptionscleanly surfaces the language-specific options to SDL-based generation and keeps the public surface aligned with the operations-based flow. No issues from a typing or backwards-compatibility perspective.protographic/tests/operations/operation-to-proto.test.ts (1)
1-1822: Comprehensive operation‑to‑proto integration coverageThis suite does a good job exercising the public
compileOperationsToProtoAPI across operation types, language options, idempotency behavior, error conditions, and determinism (field numbering, repeated compilation). Assertions are scoped to stable substrings or snapshots, so future internal refactors should be reasonably safe as long as the surface behavior stays the same.protographic/tests/operations/field-ordering-stability.test.ts (1)
5-1999: Thorough coverage of field/variable/input ordering and lockData behaviourThis suite exercises the critical permutations (reordering, add/remove/re-add, nested inputs, fragments, inline fragments, multiple operations, mutations, deep nesting, edge cases) against
compileOperationsToProtowith and withoutlockData. Assertions consistently use proto-level names (in_stock, nested message paths likeGetUserResponse.User, etc.), and the expectations around re-used vs. reserved tag numbers line up with the field-number manager semantics. I don’t see gaps or logical issues here; this is a solid safety net for the new operations-to-proto flow.protographic/src/index.ts (1)
8-154: Public API surface for operations-based generation looks consistentThe additional exports (operations-to-proto entrypoint, type-mapper/request/message builders, proto-text generation helpers, idempotency types, and protobufjs itself) are wired through
index.tscoherently and match the intended usage described in the PR. I don’t see any correctness or compatibility issues in how these are surfaced.protographic/src/operations/proto-text-generator.ts (1)
83-143: Header and reserved-handling logic align with protobufjs/lock semanticsThe updated header generation and reserved rendering look solid:
- Header now conditionally imports
google/protobuf/wrappers.protobased on actualgoogle.protobuf.*usage in theRoot, and merges user-specified imports/options on top of language-specific options frombuildProtoOptions.messageToProtoText/enumToProtoTextemitreservedclauses before fields/values, usingformatReserved/formatReservedNumbersto normalize(number[]|string)[]from protobufjs into compact ranges plus quoted names.This matches the intended behaviour for preserving removed field numbers/names while keeping the emitted proto clean and deterministic.
Also applies to: 199-239, 244-273, 304-383
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
protographic/src/operations/message-builder.ts (1)
526-552: LGTM!The function correctly builds nested messages from a field map. The field numbering logic appropriately uses the manager when provided (lines 538-541) and falls back to simple incrementing otherwise (lines 546-548).
Note: Unlike
buildMessageFromSelectionSet, this function doesn't reconcile field order with lock data. This appears intentional for this simpler utility, but if lock-stable ordering is needed, consider addingreconcileFieldOrdersupport similar to lines 172-174 inbuildMessageFromSelectionSet.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
protographic/src/operations/field-numbering.ts(1 hunks)protographic/src/operations/message-builder.ts(1 hunks)protographic/src/operations/request-builder.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- protographic/src/operations/field-numbering.ts
- protographic/src/operations/request-builder.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-27T09:45:41.622Z
Learnt from: Noroth
Repo: wundergraph/cosmo PR: 2290
File: protographic/src/sdl-to-proto-visitor.ts:1350-1390
Timestamp: 2025-10-27T09:45:41.622Z
Learning: In protographic/src/sdl-to-proto-visitor.ts, resolver-related messages (created by `createResolverRequestMessage` and `createResolverResponseMessage`) are special messages that should not be tracked in the proto lock file, unlike other message types.
Applied to files:
protographic/src/operations/message-builder.ts
📚 Learning: 2025-09-10T09:53:42.914Z
Learnt from: JivusAyrus
Repo: wundergraph/cosmo PR: 2156
File: studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/checks/index.tsx:189-197
Timestamp: 2025-09-10T09:53:42.914Z
Learning: In protobuf-generated TypeScript code, repeated fields (arrays) are always initialized with default empty arrays and cannot be undefined, making defensive programming checks like `|| []` or optional chaining unnecessary for these fields.
Applied to files:
protographic/src/operations/message-builder.ts
🧬 Code graph analysis (1)
protographic/src/operations/message-builder.ts (4)
protographic/src/operations/field-numbering.ts (2)
FieldNumberManager(14-68)assignFieldNumbersFromLockData(212-229)protographic/src/naming-conventions.ts (1)
graphqlFieldToProtoField(18-20)protographic/src/operations/type-mapper.ts (2)
ProtoTypeInfo(42-55)mapGraphQLTypeToProto(74-191)protographic/src/operations/request-builder.ts (1)
buildEnumType(247-269)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Analyze (go)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
protographic/src/operations/message-builder.ts (8)
1-37: LGTM!The imports are well-organized, and the depth constants are appropriately documented, addressing the previous feedback about magic numbers.
39-63: LGTM!The interface is well-documented with clear JSDoc comments for all fields. The internal
_depthfield appropriately tracks recursion state.
75-192: LGTM!The function implements robust depth protection with clear error messages and properly separates field collection from field processing. The recursion tracking through
depthparameter incollectFieldsand_depthin options ensures stack safety.The field reconciliation logic (lines 172-174) appropriately checks for the method's existence before calling it, maintaining compatibility.
264-283: LGTM!The enum deduplication logic properly initializes
options.createdEnumswhen missing (lines 271-274), ensuring the Set is persisted across calls. This addresses the previous review concern about enum duplication whencreatedEnumsis not provided.
288-381: LGTM!The function correctly handles field processing with appropriate validation and error messages. The union type check (lines 312-316) properly delegates to inline fragments, and the
__typenameskip (lines 299-301) is appropriate for GraphQL introspection.The depth increment (line 339) ensures proper recursion tracking for nested messages.
387-433: LGTM!The function correctly resolves fragment types and handles inline fragments without type conditions by falling back to the parent type (lines 415-418). The early returns for missing schema or invalid types (lines 404-411) provide appropriate safety.
439-487: LGTM!The function correctly resolves fragment spreads and supports nested fragment composition (lines 482-485). The assumption that circular fragment references are caught by GraphQL validation (documented in lines 105-106) is appropriate.
498-516: LGTM!This utility function provides a straightforward field definition builder. The lack of comment support is acceptable since it's a lower-level helper, while the main flow uses
createProtoField(lines 240-259) which does include comment handling.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
protographic/OPERATIONS_TO_PROTO.md (1)
59-59: Fix remaining MD036 linting violations: bold emphasis at line start.Lines 59 and 68 still use bold emphasis markers at the start of the line, triggering the MD036 linting rule. This was flagged in the previous review.
Apply this diff to move the emoji outside the bold emphasis:
-**✅ Correct: Named operation** +✅ **Correct: Named operation** ```graphql-**❌ Incorrect: Anonymous operation** +❌ **Incorrect: Anonymous operation** ```graphqlAlso applies to: 68-68
🧹 Nitpick comments (2)
cli/src/commands/grpc-service/commands/generate.ts (2)
278-328: Handle or explicitly guard against duplicate proto symbols during AST mergeMerging via
protobuf.Rootand a single syntheticServiceis a good direction, but the current logic silently dedupes messages/enums by name (first one wins) and unconditionally adds all methods tomergedService:if (!seenMessages.has(message.name)) { ... } ... if (!seenEnums.has(enumType.name)) { ... } ... for (const method of Object.values(service.methods)) { mergedService.add(method); }If two operation documents accidentally produce:
- different message/enum definitions with the same name, or
- RPC methods with the same name,
you may end up with either confusing behavior (wrong type chosen) or a less-than-obvious error from
protobuf.Service.add. To make failures clearer and avoid silent conflicts, consider explicitly detecting duplicates and failing fast with a targeted error, for example:- const mergedService = new protobuf.Service(serviceName); + const mergedService = new protobuf.Service(serviceName); + const seenMethods = new Set<string>(); ... } else if (nested instanceof protobuf.Service) { const service = nested as protobuf.Service; for (const method of Object.values(service.methods)) { - mergedService.add(method); + if (seenMethods.has(method.name)) { + throw new Error( + `Duplicate RPC method "${method.name}" detected while merging operations. ` + + 'Ensure operation names (and thus RPC names) are unique across files.', + ); + } + seenMethods.add(method.name); + mergedService.add(method); } }Similarly, if compile-time guarantees do not ensure that same-named messages/enums are structurally identical, it may be worth checking and throwing on incompatible duplicates rather than silently picking the first.
541-557: Custom scalar mapping parsing covers both inline JSON and @file; consider light validationThe
parseCustomScalarMappinghelper correctly supports both:
- inline JSON strings, and
@path/to/file.jsonsyntax with a clear error when the file is missing,which matches the CLI help text. One small optional improvement would be to validate the parsed value before returning, to fail fast on unexpected shapes:
- if (input.startsWith('@')) { - ... - const fileContent = await readFile(filePath, 'utf8'); - return JSON.parse(fileContent); - } - // Otherwise, treat as inline JSON - return JSON.parse(input); + const raw = input.startsWith('@') + ? JSON.parse(await readFile(filePath, 'utf8')) + : JSON.parse(input); + + if (!raw || typeof raw !== 'object' || Array.isArray(raw)) { + throw new Error('Custom scalar mapping must be a JSON object of { "GraphQLScalar": "ProtoType" } pairs.'); + } + return raw as Record<string, string>;Not critical, but it would give users a clearer error if they accidentally pass an array or some other invalid JSON shape.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
cli/src/commands/grpc-service/commands/generate.ts(7 hunks)protographic/OPERATIONS_TO_PROTO.md(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-20T10:38:49.191Z
Learnt from: Noroth
Repo: wundergraph/cosmo PR: 2151
File: protographic/src/sdl-to-proto-visitor.ts:0-0
Timestamp: 2025-08-20T10:38:49.191Z
Learning: Inline `extend google.protobuf.FieldOptions` declarations in proto3 files work correctly with protoc, despite theoretical concerns about proto3 compatibility with extension declarations.
Applied to files:
cli/src/commands/grpc-service/commands/generate.ts
📚 Learning: 2025-09-10T09:53:42.914Z
Learnt from: JivusAyrus
Repo: wundergraph/cosmo PR: 2156
File: studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/checks/index.tsx:189-197
Timestamp: 2025-09-10T09:53:42.914Z
Learning: In protobuf-generated TypeScript code, repeated fields (arrays) are always initialized with default empty arrays and cannot be undefined, making defensive programming checks like `|| []` or optional chaining unnecessary for these fields.
Applied to files:
cli/src/commands/grpc-service/commands/generate.ts
🧬 Code graph analysis (1)
cli/src/commands/grpc-service/commands/generate.ts (5)
protographic/src/index.ts (7)
ProtoOptions(138-138)ProtoLock(139-139)compileOperationsToProto(100-100)rootToProtoText(127-127)compileGraphQLToMapping(17-33)ProtoOption(137-137)validateGraphQLSDL(89-92)cli/src/commands/router/commands/plugin/toolchain.ts (1)
getGoModulePathProtoOption(677-682)protographic/src/operation-to-proto.ts (1)
compileOperationsToProto(87-141)protographic/src/operations/proto-text-generator.ts (1)
rootToProtoText(53-78)protographic/src/sdl-to-proto-visitor.ts (1)
ProtoOption(104-107)
🪛 markdownlint-cli2 (0.18.1)
protographic/OPERATIONS_TO_PROTO.md
59-59: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
68-68: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Analyze (go)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
protographic/OPERATIONS_TO_PROTO.md (1)
1-662: Comprehensive alpha documentation with strong examples and clear structure.The documentation effectively covers the new operations-to-proto feature with well-organized sections (overview, concepts, CLI/API references, tutorials, advanced topics, troubleshooting) and practical examples across GraphQL, TypeScript, bash, and protobuf. The API reference includes proper type definitions, the tutorial progresses logically through common workflows, and troubleshooting addresses realistic pain points. Field number stability and idempotency concepts are explained clearly. The alpha warning is appropriately placed, and all ToC anchors align with section headings.
cli/src/commands/grpc-service/commands/generate.ts (4)
56-79: Operations-mode flags, validations, and summary output look coherentThe wiring for
--with-operations,--query-idempotency,--custom-scalar-mapping,--max-depth, and--prefix-operation-type, plus the associated validations/warnings and the enrichedresultInfo(generation mode + generated files + optional query idempotency) is consistent and user-friendly. I don’t see any functional gaps here.Also applies to: 116-169, 183-197, 199-221
345-411: Operations-based generation flow and lock handling look correctCompiling each operation document separately, threading
currentLockDatathroughcompileOperationsToProtofor field-number stability, collecting therootASTs, and then generating proto text once from the mergedRootis a clean design and addresses the earlier issues with string-based merging. The error wrapping with the originating filename is also helpful for diagnosing bad operations. I don’t see functional problems in this flow.
423-481: Language options plumbing into SDL-based generation is consistentThe introduction of
languageOptions: ProtoOptionsinGenerationOptionsand the way you translate it intoProtoOption[]insidegenerateFromSDL(go_package, java_package, java_outer_classname, java_multiple_files, csharp_namespace, ruby_package, php_namespace, php_metadata_namespace, objc_class_prefix, swift_prefix) matches the CLI flags and keeps the SDL path aligned with the operations path. ReturningisOperationsMode: falsehere also makes downstream handling straightforward. No changes needed from my side.Also applies to: 236-249, 170-181
489-530: Dispatcher between SDL and operations modes is straightforward and reusable
generateProtoAndMappingnicely centralizes common concerns (reading the schema, derivingserviceName, schema validation, lock-file defaulting) and then cleanly branches intogenerateFromOperationsvsgenerateFromSDLbased onoperationsDir. This keepsgenerateCommandActionsimpler and makes it easier to reuse the generation logic from other entry points later if needed. Looks good.
6d6a9f7 to
37ea1b1
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
protographic/OPERATIONS_TO_PROTO.md (1)
59-68: Resolve remaining MD036 linting violations on example labels.Lines 59 and 68 still begin with bold emphasis (
**), which markdownlint flags as MD036 violations despite prior review comments indicating these were addressed. Reposition the emoji to the line start and apply bold to the label text only.-**✅ Correct: Named operation** +✅ **Correct: Named operation**-**❌ Incorrect: Anonymous operation** +❌ **Incorrect: Anonymous operation**cli/src/commands/grpc-service/commands/generate.ts (1)
258-261: Sort operation files for deterministic output.
fs.promises.readdir()does not guarantee entry ordering across platforms. To ensure deterministic proto generation and consistent RPC/method ordering, the filtered operation files should be sorted before processing.Apply this diff:
- const operationFiles = files.filter((file) => { - const ext = extname(file).toLowerCase(); - return validExtensions.includes(ext); - }); + const operationFiles = files + .filter((file) => validExtensions.includes(extname(file).toLowerCase())) + .sort();
🧹 Nitpick comments (1)
cli/src/commands/grpc-service/commands/generate.ts (1)
434-464: Consider consolidating language options handling.The manual construction of the
protoOptionsarray fromlanguageOptions(lines 434-464) and the similar pattern in lines 171-182 creates duplication. While the current implementation is correct and functional, consolidating this logic into a helper function or using the protographic library's built-in options handling would improve maintainability.Based on learnings
Example refactor:
function buildProtoOptions(languageOptions: ProtoOptions): ProtoOption[] { const options: ProtoOption[] = []; if (languageOptions.goPackage) { options.push(getGoModulePathProtoOption(languageOptions.goPackage)); } if (languageOptions.javaPackage) { options.push({ name: 'java_package', constant: `"${languageOptions.javaPackage}"` }); } // ... etc return options; }Then use:
const protoOptions = buildProtoOptions(languageOptions);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
cli/src/commands/grpc-service/commands/generate.ts(6 hunks)protographic/OPERATIONS_TO_PROTO.md(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Noroth
Repo: wundergraph/cosmo PR: 2151
File: protographic/src/sdl-to-proto-visitor.ts:0-0
Timestamp: 2025-08-20T10:38:49.191Z
Learning: Inline `extend google.protobuf.FieldOptions` declarations in proto3 files work correctly with protoc, despite theoretical concerns about proto3 compatibility with extension declarations.
📚 Learning: 2025-08-20T10:38:49.191Z
Learnt from: Noroth
Repo: wundergraph/cosmo PR: 2151
File: protographic/src/sdl-to-proto-visitor.ts:0-0
Timestamp: 2025-08-20T10:38:49.191Z
Learning: Inline `extend google.protobuf.FieldOptions` declarations in proto3 files work correctly with protoc, despite theoretical concerns about proto3 compatibility with extension declarations.
Applied to files:
cli/src/commands/grpc-service/commands/generate.ts
📚 Learning: 2025-09-10T09:53:42.914Z
Learnt from: JivusAyrus
Repo: wundergraph/cosmo PR: 2156
File: studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/checks/index.tsx:189-197
Timestamp: 2025-09-10T09:53:42.914Z
Learning: In protobuf-generated TypeScript code, repeated fields (arrays) are always initialized with default empty arrays and cannot be undefined, making defensive programming checks like `|| []` or optional chaining unnecessary for these fields.
Applied to files:
cli/src/commands/grpc-service/commands/generate.ts
🧬 Code graph analysis (1)
cli/src/commands/grpc-service/commands/generate.ts (6)
protographic/src/index.ts (7)
ProtoOptions(138-138)ProtoLock(139-139)compileOperationsToProto(100-100)rootToProtoText(127-127)compileGraphQLToMapping(17-33)ProtoOption(137-137)validateGraphQLSDL(89-92)cli/src/commands/router/commands/plugin/toolchain.ts (1)
getGoModulePathProtoOption(677-682)cli/src/commands/router/commands/plugin/helper.ts (2)
renderResultTree(13-67)renderValidationResults(75-134)protographic/src/operation-to-proto.ts (1)
compileOperationsToProto(87-141)protographic/src/operations/proto-text-generator.ts (1)
rootToProtoText(53-78)protographic/src/sdl-to-proto-visitor.ts (1)
ProtoOption(104-107)
🪛 markdownlint-cli2 (0.18.1)
protographic/OPERATIONS_TO_PROTO.md
59-59: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
68-68: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (go)
🔇 Additional comments (3)
cli/src/commands/grpc-service/commands/generate.ts (3)
281-327: LGTM: Well-structured AST merging logic.The function correctly merges multiple protobuf ASTs by deduplicating messages and enums by name while combining all RPC methods. The early return optimization for single-root case is efficient, and the use of Sets for deduplication is appropriate.
486-525: Clean orchestration logic with clear mode branching.The function effectively coordinates between operations-based and SDL-based generation modes. The validation step (lines 502-505) and clear branching logic (lines 508-524) make the flow easy to follow.
Note: Remember to pass the
queryIdempotencyparameter togenerateFromOperationsat line 510 once the CLI option is added.
527-544: LGTM: Helper functions are correctly implemented.Both
fetchLockDataandexistsare well-implemented. The use offs.accessinstead of the deprecatedfs.existsis the correct approach, as noted in the inline comment.
37ea1b1 to
75b2e6d
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
protographic/OPERATIONS_TO_PROTO.md (1)
59-59: MD036 violations persist: emphasis still used instead of headings.The format
**✅ Correct: Named operation**and**❌ Incorrect: Anonymous operation**starts with bold emphasis, which still violates markdownlint rule MD036. Previous review comments flagged this issue with suggested fixes, but the violations remain in the current code.Apply this diff to move the emoji outside the emphasis:
-**✅ Correct: Named operation** +✅ **Correct: Named operation**-**❌ Incorrect: Anonymous operation** +❌ **Incorrect: Anonymous operation**Also applies to: 68-68
cli/src/commands/grpc-service/commands/generate.ts (2)
223-225: Remove or conditionalize the hardcoded query idempotency display.Since
queryIdempotencyis intentionally hardcoded to'NO_SIDE_EFFECTS'(per line 376's comment) and not exposed as a CLI option, unconditionally displaying it in the results may confuse users who expect to configure it. Either omit this line or add a comment explaining it's a fixed default for operations mode.if (result.isOperationsMode) { - resultInfo['query idempotency'] = 'NO_SIDE_EFFECTS'; + // queryIdempotency is hardcoded to NO_SIDE_EFFECTS for all Query operations in operations mode + // and is not user-configurable in this release + resultInfo['query idempotency'] = 'NO_SIDE_EFFECTS (default)'; }
255-275: Sort operation files for deterministic output.The filtered operation files are not sorted before processing, which can lead to non-deterministic RPC method ordering in the merged proto across different platforms or filesystem types.
const operationFiles = files.filter((file) => { const ext = extname(file).toLowerCase(); return validExtensions.includes(ext); - }); + }).sort();
🧹 Nitpick comments (2)
cli/src/commands/grpc-service/commands/generate.ts (2)
277-327: Consider validating structural equality of duplicate messages/enums.The merge logic silently skips duplicate messages and enums by name (lines 302-304, 309-311) without verifying structural equality. While duplicates should be identical when all operations reference the same schema, adding a structural comparison or warning would make silent conflicts more visible during development.
Example validation at line 303:
if (!seenMessages.has(message.name)) { mergedRoot.add(message); seenMessages.add(message.name); + } else { + // Optional: validate that duplicate messages are structurally identical + const existing = mergedRoot.lookup(message.name); + // Compare field names, types, and numbers - implementation omitted for brevity }
436-466: Consider refactoring verbose protoOptions construction.The protoOptions array is built with repetitive if-statements checking each language option. While functional, this could be simplified with a mapping structure or helper function to reduce duplication and improve maintainability.
Example approach:
const optionMappings: Array<{ key: keyof ProtoOptions; name: string; needsQuotes?: boolean }> = [ { key: 'javaPackage', name: 'java_package', needsQuotes: true }, { key: 'javaOuterClassname', name: 'java_outer_classname', needsQuotes: true }, // ... etc ]; const protoOptions: ProtoOption[] = []; for (const { key, name, needsQuotes } of optionMappings) { const value = languageOptions[key]; if (value !== undefined) { const constant = needsQuotes ? `"${value}"` : String(value); protoOptions.push({ name, constant }); } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
cli/src/commands/grpc-service/commands/generate.ts(6 hunks)protographic/OPERATIONS_TO_PROTO.md(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-20T10:38:49.191Z
Learnt from: Noroth
Repo: wundergraph/cosmo PR: 2151
File: protographic/src/sdl-to-proto-visitor.ts:0-0
Timestamp: 2025-08-20T10:38:49.191Z
Learning: Inline `extend google.protobuf.FieldOptions` declarations in proto3 files work correctly with protoc, despite theoretical concerns about proto3 compatibility with extension declarations.
Applied to files:
cli/src/commands/grpc-service/commands/generate.ts
📚 Learning: 2025-09-10T09:53:42.914Z
Learnt from: JivusAyrus
Repo: wundergraph/cosmo PR: 2156
File: studio/src/pages/[organizationSlug]/[namespace]/graph/[slug]/checks/index.tsx:189-197
Timestamp: 2025-09-10T09:53:42.914Z
Learning: In protobuf-generated TypeScript code, repeated fields (arrays) are always initialized with default empty arrays and cannot be undefined, making defensive programming checks like `|| []` or optional chaining unnecessary for these fields.
Applied to files:
cli/src/commands/grpc-service/commands/generate.ts
🧬 Code graph analysis (1)
cli/src/commands/grpc-service/commands/generate.ts (5)
protographic/src/index.ts (6)
ProtoOptions(138-138)ProtoLock(139-139)compileOperationsToProto(100-100)rootToProtoText(127-127)compileGraphQLToMapping(17-33)ProtoOption(137-137)cli/src/commands/router/commands/plugin/toolchain.ts (2)
generateProtoAndMapping(497-542)getGoModulePathProtoOption(677-682)cli/src/commands/router/commands/plugin/helper.ts (2)
renderResultTree(13-67)renderValidationResults(75-134)protographic/src/operation-to-proto.ts (1)
compileOperationsToProto(87-141)protographic/src/operations/proto-text-generator.ts (1)
rootToProtoText(53-78)
🪛 markdownlint-cli2 (0.18.1)
protographic/OPERATIONS_TO_PROTO.md
59-59: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
68-68: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Analyze (go)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (9)
protographic/OPERATIONS_TO_PROTO.md (1)
5-27: Excellent documentation structure and comprehensiveness.The Table of Contents anchors all correctly match their corresponding headings throughout the document. Code examples are properly formatted with language markers (fixed from previous review). The documentation clearly explains the alpha feature, provides actionable CLI examples, comprehensive API reference, and helpful troubleshooting guidance.
Also applies to: 651-651
cli/src/commands/grpc-service/commands/generate.ts (8)
1-19: LGTM!The new imports for operations-based generation (
compileOperationsToProto,rootToProtoText,protobuf) and path handling (extname) are appropriate for the expanded functionality.
21-31: LGTM!The extended
CLIOptionstype properly captures all the new operation-related configuration options and cleanly extendsProtoOptionsfor language-specific settings.
33-83: LGTM!The command definition properly introduces all necessary CLI options for operations-based generation. The commented-out language options are appropriately marked as not part of the alpha release.
85-90: LGTM!The
GenerationResulttype properly accommodates both generation modes, withmappingcorrectly typed as nullable for operations mode and the newisOperationsModeflag for mode differentiation.
236-248: LGTM!The
GenerationOptionstype comprehensively captures all configuration needed for both SDL-based and operations-based generation modes.
343-410: LGTM!The operations-based generation flow correctly processes multiple operation files, maintains field number stability via lock data, and properly merges protobuf ASTs before text generation. The hardcoded
queryIdempotencyis clearly documented as intentional.
488-527: LGTM!The generation routing logic cleanly separates operations-based and SDL-based paths with proper schema validation before branching. All parameters are correctly threaded through to the appropriate generation function.
529-546: LGTM!The helper functions are correctly implemented. The
existswrapper properly uses the recommendedaccess()approach instead of the deprecatedfs.exists().
75b2e6d to
5e81432
Compare
This adds the ability to collect GraphQL operations (queries/mutations) and emit .proto files so we can auto-generate servers and clients (via gRPC/Buf Connect) to consume the graph.
--with-operationsthat selects operations-based generation mode.Backwards compatibility
Existing schema-type-based generation unchanged. New mode opt-in.
Summary by CodeRabbit
New Features
CLI / UX
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.