Skip to content

feat: generate proto from operations and sdl#2302

Merged
asoorm merged 86 commits into
mainfrom
ahmet/eng-8163-operations-to-proto
Nov 21, 2025
Merged

feat: generate proto from operations and sdl#2302
asoorm merged 86 commits into
mainfrom
ahmet/eng-8163-operations-to-proto

Conversation

@asoorm

@asoorm asoorm commented Oct 24, 2025

Copy link
Copy Markdown
Contributor

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.

  • Added compileOperationsToProto function to map GraphQL operations -> protobuf AST -> proto text.
  • Introduced a new CLI flag --with-operations that selects operations-based generation mode.
  • field-number management (via a lock/manager) to avoid collisions and ensure deterministic ordering.
  • fragment-denormalization support: named fragments and inline fragments are resolved
  • Query operations now optionally support an --idempotent-queries flag to mark queries as having no side-effects (mapped to option idempotency_level = NO_SIDE_EFFECTS; in the generated proto).

Backwards compatibility
Existing schema-type-based generation unchanged. New mode opt-in.

Summary by CodeRabbit

  • New Features

    • Alpha: generate a single merged proto from GraphQL operation files (--with-operations) with idempotency control, maxDepth protection, operation-name prefixing, language-specific proto options, and custom scalar mappings.
  • CLI / UX

    • New operation-related CLI options; improved validation, warnings, and results that include generation mode and a dynamic list of generated files.
  • Documentation

    • Added detailed Operations→Proto guide and updated README examples.
  • Tests

    • Extensive tests for operations, field-number stability, fragments, enums, builders, validation, recursion, and proto text output.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Oct 24, 2025

Copy link
Copy Markdown
Contributor

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 75b2e6d and 5e81432.

📒 Files selected for processing (2)
  • cli/src/commands/grpc-service/commands/generate.ts (6 hunks)
  • protographic/OPERATIONS_TO_PROTO.md (1 hunks)

Walkthrough

Adds 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

Cohort / File(s) Change Summary
CLI: operations mode
cli/src/commands/grpc-service/commands/generate.ts
Add operations-mode generation: read/validate operation files, parse custom scalar mappings, validate CLI flags (withOperations, queryIdempotency, maxDepth, prefixOperationType), branch SDL vs operations flows, propagate languageOptions/lock data, conditional output of mapping.json/lock, and report generated files. Extend CLIOptions / GenerationOptions / GenerationResult.
Operations-to-Proto core & public API
protographic/src/operation-to-proto.ts, protographic/src/index.ts
New compiler compileOperationsToProto and OperationsToProtoVisitor; public options/results types; re-export operation utilities and protobuf default.
Proto text generation
protographic/src/operations/proto-text-generator.ts
New renderer rootToProtoText and helpers (serviceToProtoText, messageToProtoText, enumToProtoText, formatField, formatMethod); deterministic ordering, header/imports, language proto options, reserved formatting, and method idempotency support.
Field-numbering manager
protographic/src/operations/field-numbering.ts
New FieldNumberManager and createFieldNumberManager to assign/reconcile per-message field numbers, integrate optional lock manager, support resets/lookups, and helper to assign numbers from lock data.
Message & request builders
protographic/src/operations/message-builder.ts, protographic/src/operations/request-builder.ts
New builders: buildMessageFromSelectionSet, buildFieldDefinition, buildNestedMessage, buildRequestMessage, buildInputObjectMessage, buildEnumType, buildVariableField; two-pass building, fragment handling, nested-list wrapper generation, maxDepth enforcement, and FieldNumberManager integration.
Type mapping & list utilities
protographic/src/operations/type-mapper.ts, protographic/src/operations/list-type-utils.ts
New GraphQL→Proto type mapper (mapGraphQLTypeToProto, getProtoTypeName, etc.) with ProtoTypeInfo/TypeMapperOptions, custom scalar mapping support, wrapper rules, nested-list detection and nesting-level helpers, and import calculations.
SDL visitor updates
protographic/src/sdl-to-proto-visitor.ts
Replace internal list helpers with list-type-utils, integrate buildProtoOptions for language-specific proto options, adjust nested-wrapper generation, and extend visitor options to include ProtoOptions.
Proto options helper
protographic/src/proto-options.ts
New ProtoOptions interface and buildProtoOptions that emit language-specific option statements and compute a default go_package when applicable.
Public types
protographic/src/types.ts
Add IdempotencyLevel and MethodWithIdempotency typings used by proto text generator and operation visitor.
Docs & README
protographic/OPERATIONS_TO_PROTO.md, protographic/README.md
Add ALPHA documentation and README updates describing Operations→Proto feature, CLI/API examples, tutorials, and link to detailed docs.
Tests: operations & SDL
protographic/tests/operations/*, protographic/tests/sdl-to-proto/10-options.test.ts
Add extensive unit and integration tests covering enums, field-numbering, ordering stability, fragments, builders, proto-text generation, recursion protection, validation, type-mapper behavior, and SDL option emission.
Test utilities
protographic/tests/util.ts
Strengthen protobufjs.Root typing, rename messageNamemessagePath, improve error messages, add nested-type enumeration helper, expand numeric reserved-range handling, and more robust lookups.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Areas needing extra attention:

  • Field-numbering reconciliation and lockData propagation (protographic/src/operations/field-numbering.ts, protographic/src/operations/message-builder.ts, protographic/src/operation-to-proto.ts)
  • Type-mapper, nested-list wrapper logic and custom scalar mapping handling (protographic/src/operations/type-mapper.ts, protographic/src/operations/list-type-utils.ts)
  • Merging/deduplication of multiple protobuf.Root instances and deterministic proto ordering (mergeProtoRoots + protographic/src/operations/proto-text-generator.ts)
  • Proto header/options emission and idempotency metadata formatting (protographic/src/operations/proto-text-generator.ts, protographic/src/sdl-to-proto-visitor.ts)
  • CLI generate flow: validations, file I/O, conditional writes, and reported generated-files (cli/src/commands/grpc-service/commands/generate.ts)

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: generate proto from operations and sdl' clearly summarizes the main change: adding capability to generate Protocol Buffer definitions from GraphQL operations in addition to existing SDL-based generation.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the cli label Oct 24, 2025
@asoorm asoorm marked this pull request as ready for review October 25, 2025 05:09
@asoorm asoorm force-pushed the ahmet/eng-8163-operations-to-proto branch from cb28579 to e284194 Compare October 25, 2025 05:14
Comment thread protographic/tests/operations/enum-support.test.ts Outdated
Comment thread protographic/src/operations/field-numbering.ts
Comment thread protographic/src/operations/field-numbering.ts
Comment thread protographic/src/operations/message-builder.ts Outdated
Comment thread protographic/src/operations/message-builder.ts Outdated
Comment thread protographic/tests/operations/message-builder.test.ts
Comment thread protographic/tests/operations/message-builder.test.ts Outdated
Comment thread protographic/tests/operations/fragments.test.ts
Comment thread protographic/tests/operations/fragments.test.ts Outdated
Comment thread cli/src/commands/grpc-service/commands/generate.ts Outdated
@asoorm asoorm marked this pull request as draft October 25, 2025 12:25
@github-actions

github-actions Bot commented Oct 25, 2025

Copy link
Copy Markdown

❌ Internal Query Planner CI checks failed

The Internal Query Planner CI checks failed in the celestial repository, and this is going to stop the merge of this PR.
If you are part of the WunderGraph organization, you can see the PR with more details.

@asoorm asoorm force-pushed the ahmet/eng-8163-operations-to-proto branch 2 times, most recently from efd48b7 to 9ff4661 Compare October 25, 2025 15:08
@github-actions github-actions Bot removed the router label Oct 25, 2025
@asoorm asoorm force-pushed the ahmet/eng-8163-operations-to-proto branch 5 times, most recently from c1fa72f to 51d2da7 Compare October 28, 2025 22:08
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.
@asoorm asoorm force-pushed the ahmet/eng-8163-operations-to-proto branch from b817019 to e47cb2f Compare November 20, 2025 12:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 sync

The docstrings for handleListType / createNestedListWrapper describe a level‑1 wrapper like:

message ListOfString {
  repeated string items = 1;
}

but buildWrapperMessage always generates an inner List message:

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 === 1 in buildWrapperMessage to match the documented “simple wrapper” shape; or
  • Update the examples in the comments to show the actual List nested 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 behavior

You’re manually reconstructing the ProtoOptions bag before calling buildProtoOptions. Since GraphQLToProtoTextVisitorOptions already extends ProtoOptions, you can avoid drift by passing options directly:

-    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 buildProtoOptions currently only emits go_package when options.goPackage is truthy, so the “Generate default go_package if not provided” behavior isn’t actually triggered when goPackage is omitted. If you intended to derive a default from packageName, consider relaxing that guard in buildProtoOptions in a follow‑up.

protographic/tests/operations/field-numbering.test.ts (1)

1-224: FieldNumberManager behavior is well covered

These 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 a ProtoLockManager into this flow, consider an additional test that passes a stub lock manager and asserts reconcileFieldOrder / getLockManager interactions, but that’s optional for now.

protographic/src/types.ts (1)

1-14: Use a type‑only namespace import for protobuf types

Since protobuf is 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 using protobufjs.

protographic/tests/operations/request-builder.test.ts (1)

241-299: Prefer GraphQL type guards over constructor name checks

In 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 import isInputObjectType, 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 expectations

Two small nits:

  • The test should handle fragments that reference non-existent fragments actually 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 __typename is dropped, but nothing explicitly checks for its absence. You could make the intent clearer and guard against regressions by also asserting that proto does 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 drift

You’re redefining a local MethodWithIdempotency interface that matches the production type:

interface MethodWithIdempotency extends protobuf.Method {
  idempotencyLevel?: 'NO_SIDE_EFFECTS' | 'DEFAULT';
}

Now that MethodWithIdempotency (and IdempotencyLevel) are exported from src/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 assertion

Two minor suggestions:

  • should handle empty fragments gracefully and should handle fragments that reference non-existent fragments don’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, asserting endTime - startTime < 1000 is 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

fieldNumberManager is declared without a type annotation and only assigned in the constructor via createFieldNumberManager(this.lockManager). This effectively makes it any under 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

processOperation currently derives the method name via upperFirst(camelCase(operationName)), even though a dedicated createOperationMethodName helper 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

getRootType uses non-null assertions on schema.getQueryType(), getMutationType(), and getSubscriptionType(). If a caller passes a schema without a matching root type for the operation (e.g., a mutation operation against a schema without mutation root), 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 null and 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 formatMethod

Right now serviceToProtoText hand-rolls RPC formatting (including idempotency handling), while formatMethod independently formats a single rpc line without idempotency support. This creates two slightly different code paths for essentially the same concern.

Consider either:

  • Making serviceToProtoText delegate to an enhanced formatMethod that also understands MethodWithIdempotency, or
  • Updating formatMethod to mirror the idempotency/streaming logic so callers using it directly get behaviour consistent with serviceToProtoText.

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

📥 Commits

Reviewing files that changed from the base of the PR and between d35b0d2 and e47cb2f.

📒 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.ts
  • protographic/tests/operations/field-ordering-stability.test.ts
  • protographic/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.ts
  • protographic/src/sdl-to-proto-visitor.ts
  • protographic/src/operations/proto-text-generator.ts
  • protographic/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 good

Extending GraphQLToProtoTextVisitorOptions from ProtoOptions cleanly 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 coverage

This suite does a good job exercising the public compileOperationsToProto API 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 behaviour

This suite exercises the critical permutations (reordering, add/remove/re-add, nested inputs, fragments, inline fragments, multiple operations, mutations, deep nesting, edge cases) against compileOperationsToProto with and without lockData. Assertions consistently use proto-level names (in_stock, nested message paths like GetUserResponse.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 consistent

The additional exports (operations-to-proto entrypoint, type-mapper/request/message builders, proto-text generation helpers, idempotency types, and protobufjs itself) are wired through index.ts coherently 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 semantics

The updated header generation and reserved rendering look solid:

  • Header now conditionally imports google/protobuf/wrappers.proto based on actual google.protobuf.* usage in the Root, and merges user-specified imports/options on top of language-specific options from buildProtoOptions.
  • messageToProtoText / enumToProtoText emit reserved clauses before fields/values, using formatReserved/formatReservedNumbers to 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

@asoorm asoorm requested a review from Noroth November 20, 2025 12:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 adding reconcileFieldOrder support similar to lines 172-174 in buildMessageFromSelectionSet.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e47cb2f and 9b0ee08.

📒 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 _depth field 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 depth parameter in collectFields and _depth in 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.createdEnums when missing (lines 271-274), ensuring the Set is persisted across calls. This addresses the previous review concern about enum duplication when createdEnums is 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 __typename skip (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.

Comment thread cli/src/commands/grpc-service/commands/generate.ts
Comment thread cli/src/commands/grpc-service/commands/generate.ts Outdated
Comment thread cli/src/commands/grpc-service/commands/generate.ts Outdated
Comment thread cli/src/commands/grpc-service/commands/generate.ts Outdated
@asoorm asoorm requested a review from StarpTech November 20, 2025 22:15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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**
 ```graphql

Also 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 merge

Merging via protobuf.Root and a single synthetic Service is a good direction, but the current logic silently dedupes messages/enums by name (first one wins) and unconditionally adds all methods to mergedService:

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 validation

The parseCustomScalarMapping helper correctly supports both:

  • inline JSON strings, and
  • @path/to/file.json syntax 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9b0ee08 and 3cc0b5f.

📒 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 coherent

The wiring for --with-operations, --query-idempotency, --custom-scalar-mapping, --max-depth, and --prefix-operation-type, plus the associated validations/warnings and the enriched resultInfo (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 correct

Compiling each operation document separately, threading currentLockData through compileOperationsToProto for field-number stability, collecting the root ASTs, and then generating proto text once from the merged Root is 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 consistent

The introduction of languageOptions: ProtoOptions in GenerationOptions and the way you translate it into ProtoOption[] inside generateFromSDL (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. Returning isOperationsMode: false here 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

generateProtoAndMapping nicely centralizes common concerns (reading the schema, deriving serviceName, schema validation, lock-file defaulting) and then cleanly branches into generateFromOperations vs generateFromSDL based on operationsDir. This keeps generateCommandAction simpler and makes it easier to reuse the generation logic from other entry points later if needed. Looks good.

Comment thread cli/src/commands/grpc-service/commands/generate.ts
@asoorm asoorm force-pushed the ahmet/eng-8163-operations-to-proto branch from 6d6a9f7 to 37ea1b1 Compare November 21, 2025 09:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 protoOptions array from languageOptions (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

📥 Commits

Reviewing files that changed from the base of the PR and between 3cc0b5f and 37ea1b1.

📒 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 queryIdempotency parameter to generateFromOperations at line 510 once the CLI option is added.


527-544: LGTM: Helper functions are correctly implemented.

Both fetchLockData and exists are well-implemented. The use of fs.access instead of the deprecated fs.exists is the correct approach, as noted in the inline comment.

Comment thread cli/src/commands/grpc-service/commands/generate.ts
Comment thread cli/src/commands/grpc-service/commands/generate.ts
Comment thread cli/src/commands/grpc-service/commands/generate.ts
@asoorm asoorm force-pushed the ahmet/eng-8163-operations-to-proto branch from 37ea1b1 to 75b2e6d Compare November 21, 2025 09:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 queryIdempotency is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 37ea1b1 and 75b2e6d.

📒 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 CLIOptions type properly captures all the new operation-related configuration options and cleanly extends ProtoOptions for 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 GenerationResult type properly accommodates both generation modes, with mapping correctly typed as nullable for operations mode and the new isOperationsMode flag for mode differentiation.


236-248: LGTM!

The GenerationOptions type 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 queryIdempotency is 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 exists wrapper properly uses the recommended access() approach instead of the deprecated fs.exists().

@asoorm asoorm force-pushed the ahmet/eng-8163-operations-to-proto branch from 75b2e6d to 5e81432 Compare November 21, 2025 09:29

@StarpTech StarpTech left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@asoorm asoorm merged commit 4cb22f5 into main Nov 21, 2025
26 checks passed
@asoorm asoorm deleted the ahmet/eng-8163-operations-to-proto branch November 21, 2025 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants