Skip to content

feat(composition): @openfed__queryCache and @openfed__is directives (4/5)#2997

Draft
SkArchon wants to merge 94 commits into
mainfrom
milinda/queryCache-directive
Draft

feat(composition): @openfed__queryCache and @openfed__is directives (4/5)#2997
SkArchon wants to merge 94 commits into
mainfrom
milinda/queryCache-directive

Conversation

@SkArchon

@SkArchon SkArchon commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

@openfed__queryCache + @openfed__is — query-field caching with argument-to-@key mappings for cache-key construction. Adds QueryCacheConfiguration/EntityKeyMapping/EntityCacheFieldMapping proto (field 5) + bindings, config types, normalization (@openfed__is mapping incl. batch), error messages, a warning, and the query-cache + serializer tests.

More documentation on this: https://app.notion.com/p/wundergraph/Directives-37bb19e0a0ec80b9b505d896beed5897


Note: This PR supersedes #2986 as part of reordering the stack so @openfed__queryCache/@openfed__is lands before @openfed__requestScoped. #2986 was inadvertently marked as merged by GitHub during the reorder (its head branch was contained by the requestScoped branch while its base still pointed at it) — no code was merged to main. This PR carries the identical change, now based on main as 4/5. The requestScoped PR (#2985) is now 5/5 and stacked on top of this one.

Summary by CodeRabbit

  • New Features

    • Added support for query-level caching and argument-to-entity key mapping in router configuration.
    • Introduced support for a new directive that links query arguments to entity keys, including batch and nested input handling.
  • Bug Fixes

    • Improved validation for directive placement, field types, and cache settings to prevent invalid configurations.
    • Added clearer errors for missing cache setup, mismatched key mappings, and unsupported argument shapes.
  • Tests

    • Expanded automated coverage for query caching, directive validation, and entity caching configuration output.

SkArchon added 30 commits June 19, 2026 11:50
…date-directive

# Conflicts:
#	composition/src/router-configuration/types.ts
#	composition/src/v1/normalization/normalization-factory.ts
#	connect-go/gen/proto/wg/cosmo/node/v1/node.pb.go
#	connect/src/wg/cosmo/node/v1/node_pb.ts
#	proto/wg/cosmo/node/v1/node.proto
#	router/gen/proto/wg/cosmo/node/v1/node.pb.go
#	shared/src/router-config/builder.ts
…pulate-directive

# Conflicts:
#	composition/src/v1/normalization/normalization-factory.ts
#	composition/src/v1/normalization/types/types.ts
#	connect-go/gen/proto/wg/cosmo/node/v1/node.pb.go
#	connect/src/wg/cosmo/node/v1/node_pb.ts
#	router/gen/proto/wg/cosmo/node/v1/node.pb.go
#	shared/src/router-config/builder.ts
…oped-directive

# Conflicts:
#	composition/src/v1/normalization/normalization-factory.ts
#	connect-go/gen/proto/wg/cosmo/node/v1/node.pb.go
#	connect/src/wg/cosmo/node/v1/node_pb.ts
#	router/gen/proto/wg/cosmo/node/v1/node.pb.go
…e-directive

# Conflicts:
#	composition/src/v1/normalization/normalization-factory.ts
#	connect-go/gen/proto/wg/cosmo/node/v1/node.pb.go
#	connect/src/wg/cosmo/node/v1/node_pb.ts
#	router/gen/proto/wg/cosmo/node/v1/node.pb.go
#	shared/src/router-config/builder.ts
SkArchon and others added 14 commits June 22, 2026 21:37
…e-directive

# Conflicts:
#	composition/src/router-configuration/types.ts
#	composition/src/v1/normalization/normalization-factory.ts
#	composition/src/v1/warnings/warnings.ts
#	connect-go/gen/proto/wg/cosmo/node/v1/node.pb.go
#	connect/src/wg/cosmo/node/v1/node_pb.ts
#	proto/wg/cosmo/node/v1/node.proto
#	router/gen/proto/wg/cosmo/node/v1/node.pb.go
#	shared/src/router-config/builder.ts
…s 4/5)

Reorders the stack so @openfed__queryCache/@openfed__is sits directly on
main as the 4th PR. The @openfed__requestScoped feature is removed here and
re-applied on top in the requestScoped branch (now 5/5). The requestScoped
assertions in shared/test/entity-caching.test.ts move to that PR.

Proto regenerated (request_scoped_configurations field 4 dropped; query_cache
remains field 5).
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds @openfed__queryCache and @openfed__is directive support, including directive registration, normalization-time validation and key mapping, router configuration and proto schema updates, builder translation, and test coverage.

Changes

OpenFed query cache and @is feature

Layer / File(s) Summary
Directive constants and definition data
composition/src/utils/string-constants.ts, composition/src/v1/constants/*, composition/src/directive-definition-data/directive-definition-data.ts, composition/src/v1/normalization/utils.ts
OPENFED_IS, OPENFED_QUERY_CACHE, and LITERAL_OPEN_BRACE are added, along with directive definition nodes, directive-definition data, and registration in directive lookup and initialization paths.
Validation error helpers
composition/src/errors/*
New query-cache placement errors and expanded @openfed__is mapping error helpers are added with their parameter types.
Router cache config types
composition/src/router-configuration/*
Router-config field typing is narrowed, query-cache configuration types are added, and queryCacheConfigurations is initialized in entity-caching defaults.
NormalizationFactory mapping logic
composition/src/v1/normalization/..., composition/src/types/types.ts, composition/tests/v1/directives/query-cache.test.ts
NormalizationFactory adds query-cache extraction, @openfed__is placement checks, key-field parsing, explicit/composite mapping validation, and directive tests for those paths.
Proto messages and router builder
proto/wg/cosmo/node/v1/node.proto, connect/src/wg/cosmo/node/v1/node_pb.ts, shared/src/router-config/builder.ts, shared/test/entity-caching.test.ts
The proto and generated bindings add query-cache mapping messages, the builder translates router config entries into them, and shared tests cover the resulting EntityCachingConfiguration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • wundergraph/cosmo#2986: Touches the same @openfed__queryCache / @openfed__is composition and normalization code paths.
  • wundergraph/cosmo#2996: Modifies the shared EntityCachingConfiguration proto/generated contract used here.
  • wundergraph/cosmo#2980: Adds the earlier entity-cache directive support that this PR validates and consumes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding the @openfed__queryCache and @openfed__is directives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch milinda/queryCache-directive

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

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown

Router image scan failed

❌ Security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-aafbf6eee7c621f7a52083ee7b82fd6c839df0ce

Please check the security vulnerabilities found in the PR.

If you believe this is a false positive, please add the vulnerability to the .trivyignore file and re-run the scan.

@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: 4

🧹 Nitpick comments (1)
composition/src/v1/normalization/normalization-factory.ts (1)

4285-4291: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tighten the new helper typings.

extractQueryCacheConfig and validateIsDirectivePlacement return void but omit return annotations, and walkSelections uses any[]. Add : void and a concrete GraphQL selection type to keep this new AST traversal type-safe.

As per coding guidelines, **/*.{ts,tsx} should “Use explicit type annotations for function parameters and return types in TypeScript” and “Avoid any type in TypeScript; use specific types or generics.”

Also applies to: 4375-4375, 4403-4403

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 4285
- 4291, Add missing return type annotations to the new helper functions for type
safety. In extractQueryCacheConfig function, add `: void` return type
annotation. In validateIsDirectivePlacement function (around line 4375), add `:
void` return type annotation. In walkSelections function (around line 4403),
replace the `any[]` type parameter with a concrete GraphQL selection type
instead of using the `any` type. This ensures all three functions have explicit
type annotations as required by the coding guidelines.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@composition/src/v1/normalization/normalization-factory.ts`:
- Around line 4953-4955: The early return when explicitMappings.length === 0
bypasses the extra-argument validation that follows, allowing composite-only
mappings to skip validation of all field arguments. Remove or modify this early
return condition in the normalization logic so that the extra-argument
validation continues to execute even when there are no explicit mappings,
ensuring that composite mappings are validated against all arguments present on
the field to prevent cache-key collisions.
- Around line 5390-5413: The query-cache directive validation logic (including
the check for OPENFED_QUERY_CACHE and the validateIsDirectivePlacement call for
`@openfed__is` misplacement) is currently inside the isObject guard, which skips
validation for interface fields. Move the entire if/else block that checks
fieldData.directivesByName.has(OPENFED_QUERY_CACHE) and calls
extractQueryCacheConfig or validateIsDirectivePlacement outside and after the if
(isObject) block so that query-cache and `@openfed__is` placement validation runs
for all field types, not just object fields.
- Around line 5171-5173: The code currently allows singular composite inputs for
list-return cache fields by setting isBatch=false when isListReturn is true but
argInfo.isList is false. Add validation logic after the isBatch assignment in
the normalization-factory.ts file to reject this invalid configuration. Mirror
the explicit scalar-path validation by checking that when isListReturn is true
and the field is a nested key (indicated by isNestedKey based on
LITERAL_OPEN_BRACE), the argInfo.isList must also be true. If this condition
fails, throw an appropriate error to prevent non-list composite inputs from
being used with list-return query caches.
- Around line 4836-4853: The namedTypesMatch method only compares named types
and ignores list wrappers, allowing mismatched list depths to pass validation.
After unwrapping exactly one batch dimension using this.unwrapListType to get
innerType, replace the namedTypesMatch check with a comparison that validates
both the named types AND the complete type structure including list wrappers
match the keyFieldTypeNode. This ensures that extra nesting in the argument type
is properly rejected before router mappings are emitted, preventing cases like
[[ID!]!]! from incorrectly satisfying a scalar ID batch key requirement.

---

Nitpick comments:
In `@composition/src/v1/normalization/normalization-factory.ts`:
- Around line 4285-4291: Add missing return type annotations to the new helper
functions for type safety. In extractQueryCacheConfig function, add `: void`
return type annotation. In validateIsDirectivePlacement function (around line
4375), add `: void` return type annotation. In walkSelections function (around
line 4403), replace the `any[]` type parameter with a concrete GraphQL selection
type instead of using the `any` type. This ensures all three functions have
explicit type annotations as required by the coding guidelines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9d8011ee-5e39-4430-a489-391027019492

📥 Commits

Reviewing files that changed from the base of the PR and between a8cc2af and efa4cdf.

⛔ Files ignored due to path filters (2)
  • connect-go/gen/proto/wg/cosmo/node/v1/node.pb.go is excluded by !**/*.pb.go, !**/gen/**
  • router/gen/proto/wg/cosmo/node/v1/node.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (19)
  • composition/src/directive-definition-data/directive-definition-data.ts
  • composition/src/errors/errors.ts
  • composition/src/errors/types/params.ts
  • composition/src/router-configuration/types.ts
  • composition/src/utils/string-constants.ts
  • composition/src/v1/constants/constants.ts
  • composition/src/v1/constants/directive-definitions.ts
  • composition/src/v1/normalization/normalization-factory.ts
  • composition/src/v1/normalization/types/types.ts
  • composition/src/v1/normalization/utils.ts
  • composition/src/v1/warnings/params.ts
  • composition/src/v1/warnings/warnings.ts
  • composition/tests/v1/directives/cache-populate.test.ts
  • composition/tests/v1/directives/entity-cache.test.ts
  • composition/tests/v1/directives/query-cache.test.ts
  • connect/src/wg/cosmo/node/v1/node_pb.ts
  • proto/wg/cosmo/node/v1/node.proto
  • shared/src/router-config/builder.ts
  • shared/test/entity-caching.test.ts

Comment on lines +4836 to +4853
const unwrapped = this.unwrapListType(argTypeNode);
if (!isTypeNodeListType(unwrapped)) {
// Single list, not list of lists
this.errors.push(
invalidDirectiveError(OPENFED_IS, `${fieldCoords}(${argInfo.name}: ...)`, FIRST_ORDINAL, [
batchListValuedKeyRequiresNestedListsErrorMessage(
fieldCoords,
isFieldValue,
entityTypeName,
`a single tag list of type "${printTypeNode(argTypeNode)}"`,
),
]),
);
return [];
}
// List of lists - check inner type matches
const innerType = this.unwrapListType(unwrapped);
if (!this.namedTypesMatch(innerType, keyFieldTypeNode)) {

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Compare list shape, not only named types.

namedTypesMatch ignores list wrappers, so extra nesting can satisfy the wrong key shape, e.g. a [[ID!]!]! argument can pass for a scalar ID batch key. After removing exactly one batch dimension, reject remaining list-depth mismatches before emitting router mappings.

Also applies to: 4872-4873, 4931-4932

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 4836
- 4853, The namedTypesMatch method only compares named types and ignores list
wrappers, allowing mismatched list depths to pass validation. After unwrapping
exactly one batch dimension using this.unwrapListType to get innerType, replace
the namedTypesMatch check with a comparison that validates both the named types
AND the complete type structure including list wrappers match the
keyFieldTypeNode. This ensures that extra nesting in the argument type is
properly rejected before router mappings are emitted, preventing cases like
[[ID!]!]! from incorrectly satisfying a scalar ID batch key requirement.

Comment on lines +4953 to +4955
if (explicitMappings.length === 0) {
return compositeMappings;
}

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Run extra-argument checks for composite-only mappings.

When the only mapping is composite, this early return skips the extra-argument validation below. A field like user(input: UserKeyInput @openfed__is(fields: "id sku"), locale: String) would cache by input while ignoring locale, creating cache-key collisions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 4953
- 4955, The early return when explicitMappings.length === 0 bypasses the
extra-argument validation that follows, allowing composite-only mappings to skip
validation of all field arguments. Remove or modify this early return condition
in the normalization logic so that the extra-argument validation continues to
execute even when there are no explicit mappings, ensuring that composite
mappings are validated against all arguments present on the field to prevent
cache-key collisions.

Comment on lines +5171 to +5173
const isBatch = isListReturn && argInfo.isList;
const isNestedKey = normalizedFieldSet.includes(LITERAL_OPEN_BRACE);

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject singular composite inputs for list-return cache fields.

For list returns, a non-list composite input currently produces mappings with isBatch=false. Mirror the explicit scalar-path validation here so list-return query caches require a batch-capable list argument.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 5171
- 5173, The code currently allows singular composite inputs for list-return
cache fields by setting isBatch=false when isListReturn is true but
argInfo.isList is false. Add validation logic after the isBatch assignment in
the normalization-factory.ts file to reject this invalid configuration. Mirror
the explicit scalar-path validation by checking that when isListReturn is true
and the field is a nested key (indicated by isNestedKey based on
LITERAL_OPEN_BRACE), the argInfo.isList must also be true. If this condition
fails, throw an appropriate error to prevent non-list composite inputs from
being used with list-return query caches.

Comment thread composition/src/v1/normalization/normalization-factory.ts
…alue arg

Pass the value directly instead of { directiveName, value }; the error message
no longer includes the directive name. Restores the base signature and updates
all call sites (entityCache/cachePopulate/queryCache handlers + tests).
@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.68914% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.31%. Comparing base (a8cc2af) to head (30c81e0).

Files with missing lines Patch % Lines
...tion/src/v1/normalization/normalization-factory.ts 98.14% 14 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2997      +/-   ##
==========================================
+ Coverage   42.77%   47.31%   +4.54%     
==========================================
  Files         861     1119     +258     
  Lines      124386   153254   +28868     
  Branches     9995    10181     +186     
==========================================
+ Hits        53204    72514   +19310     
- Misses      70837    78923    +8086     
- Partials      345     1817    +1472     
Files with missing lines Coverage Δ
...ctive-definition-data/directive-definition-data.ts 100.00% <100.00%> (ø)
composition/src/errors/errors.ts 84.32% <100.00%> (+1.87%) ⬆️
composition/src/utils/string-constants.ts 100.00% <100.00%> (ø)
composition/src/v1/constants/constants.ts 100.00% <100.00%> (ø)
...position/src/v1/constants/directive-definitions.ts 100.00% <100.00%> (ø)
composition/src/v1/normalization/utils.ts 90.09% <100.00%> (+0.03%) ⬆️
composition/src/v1/warnings/params.ts 100.00% <ø> (ø)
composition/src/v1/warnings/warnings.ts 89.47% <100.00%> (+0.81%) ⬆️
router/gen/proto/wg/cosmo/node/v1/node.pb.go 26.93% <ø> (ø)
...tion/src/v1/normalization/normalization-factory.ts 91.67% <98.14%> (+1.28%) ⬆️

... and 258 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
composition/src/v1/normalization/normalization-factory.ts (4)

4535-4538: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject extra input-object fields in composite mappings.

validateNestedInputObjectMapping verifies that required key fields exist, but it never rejects input fields that are not part of the mapped key. An input like { id, sku, locale } mapped with fields: "id sku" emits mappings for only id and sku, so different locale values can collide in the same cache key.

Also applies to: 4652-4655

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 4535
- 4538, The validateNestedInputObjectMapping function currently validates that
required key fields exist but does not reject input object fields that are not
part of the mapped keys. After building the fieldMappings array from
topLevelGroups in the function, add validation to ensure all input fields from
inputData.inputValueDataByName are accounted for in the mapping. Reject any
extra input fields that are not part of the mapped keys by throwing an error
with a clear message indicating which fields are unmapped. This validation
should be added both at the primary location (around lines 4535-4538 where
fieldMappings is created) and at the secondary location mentioned in the comment
(lines 4652-4655).

4770-4773: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject duplicate key fields when adding composite mappings.

The scalar path checks mappedKeyFieldToArgumentName before adding a mapping, but this composite branch overwrites existing entries. If a scalar @openfed__is(fields: "id") is processed before a composite @openfed__is(fields: "id sku"), argument order decides whether the duplicate is rejected or emitted.

🐛 Proposed fix
         for (const mapping of mappings) {
           for (const fieldMapping of mapping.fieldMappings) {
+            if (mappedKeyFieldToArgumentName.has(fieldMapping.entityKeyField)) {
+              this.errors.push(
+                invalidDirectiveError(OPENFED_IS, `${fieldCoords}(${argInfo.name}: ...)`, FIRST_ORDINAL, [
+                  duplicateKeyFieldMappingErrorMessage(fieldCoords, fieldMapping.entityKeyField),
+                ]),
+              );
+              return [];
+            }
             mappedKeyFieldToArgumentName.set(fieldMapping.entityKeyField, argInfo.name);
           }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 4770
- 4773, The composite mapping loop through fieldMappings is unconditionally
adding entries to mappedKeyFieldToArgumentName without checking for duplicates
first, unlike the scalar path that validates this. Before calling
mappedKeyFieldToArgumentName.set() with fieldMapping.entityKeyField and
argInfo.name, check if fieldMapping.entityKeyField already exists in the
mappedKeyFieldToArgumentName map. If the key field is already mapped, reject the
composite mapping and provide an appropriate error or validation failure instead
of silently overwriting the existing entry.

4864-4886: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate scalar companion types in batch mappings.

For list-return fields, scalar arguments paired with a batched list argument skip type validation entirely in this branch. A composite key like orgId id can accept orgId: Int @openfed__is(fields: "orgId") even when the entity key field is ID, producing an invalid router key mapping.

🐛 Proposed fix
           } else {
-            // Scalar arg in batch mode - could still be valid as a scalar `@openfed__is`, we'll check later
+            if (!this.namedTypesMatch(argTypeNode, keyFieldTypeNode)) {
+              this.errors.push(
+                invalidDirectiveError(OPENFED_IS, `${fieldCoords}(${argInfo.name}: ...)`, FIRST_ORDINAL, [
+                  explicitTypeMismatchErrorMessage(
+                    argInfo.name,
+                    fieldCoords,
+                    printTypeNode(argTypeNode),
+                    isFieldValue,
+                    entityTypeName,
+                    printTypeNode(keyFieldTypeNode),
+                  ),
+                ]),
+              );
+              return [];
+            }
           }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 4864
- 4886, The else branch that handles scalar arguments (when argInfo.isList is
false) in batch mode currently skips validation with a placeholder comment. This
allows type mismatches to pass through. Add type validation in this branch by
comparing the scalar argTypeNode against the keyFieldTypeNode using the
namedTypesMatch method, similar to how list arguments are validated above. If
the types do not match, push an invalidDirectiveError using
explicitTypeMismatchErrorMessage with the same parameters (argInfo.name,
fieldCoords, printTypeNode(argTypeNode), and printTypeNode(keyFieldTypeNode)) to
ensure scalar companion types in batch mappings are properly validated.

4955-4957: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Treat every unannotated argument as extra once mappings are explicit.

The builder states that argument names are never matched to @key fields, but these filters exempt unannotated arguments whose names equal any key field. For example, user(id: ID! @openfed__is(fields: "id"), sku: String) can emit an id mapping while ignoring sku if sku is also an entity key field, causing cache-key collisions.

🐛 Proposed fix
-      const extraArgs = argumentInfos.filter((a) => !a.isFieldValue && !allKeyFieldPaths.has(a.name));
+      const extraArgs = argumentInfos.filter((a) => !a.isFieldValue);
@@
-      const globalExtraArgs = argumentInfos.filter((a) => !a.isFieldValue && !allKeyFieldPaths.has(a.name));
+      const globalExtraArgs = argumentInfos.filter((a) => !a.isFieldValue);

Also applies to: 4995-4997

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 4955
- 4957, The filter in the extraArgs assignment (around line 4955) is currently
excluding unannotated arguments whose names happen to match key fields in
allKeyFieldPaths. Once explicit mappings with `@openfed__is` are used, all
unannotated arguments should be treated as extra regardless of whether their
names match key field names. Remove the condition !allKeyFieldPaths.has(a.name)
from the extraArgs filter so that the filter only checks !a.isFieldValue,
ensuring that any unannotated argument is treated as extra and preventing
name-based matching to key fields that could cause cache-key collisions. Apply
this same logic change to the similar filter at line 4995-4997 that also applies
to this same issue.
🧹 Nitpick comments (1)
composition/src/v1/normalization/normalization-factory.ts (1)

4281-4287: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tighten the new helper signatures.

extractQueryCacheConfig and validateIsDirectivePlacement infer void, and walkSelections uses any[]. Please add explicit : void return types and replace any with a GraphQL selection type or narrow local union. As per coding guidelines, “Use explicit type annotations for function parameters and return types in TypeScript” and “Avoid any type in TypeScript; use specific types or generics.”

♻️ Suggested shape
   extractQueryCacheConfig(
     parentTypeName: string,
     configurationTypeName: string,
     fieldName: string,
     fieldData: FieldData,
     operationType: OperationTypeNode | undefined,
-  ) {
+  ): void {
@@
-  validateIsDirectivePlacement(fieldCoords: string, fieldData: FieldData) {
+  validateIsDirectivePlacement(fieldCoords: string, fieldData: FieldData): void {
@@
-    const walkSelections = (selections: readonly any[], currentTypeName: string, pathPrefix: string) => {
+    const walkSelections = (selections: readonly SelectionNode[], currentTypeName: string, pathPrefix: string): void => {

If SelectionNode is not already imported from graphql, add it to the existing type import.

Also applies to: 4371-4371, 4399-4399

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 4281
- 4287, The methods extractQueryCacheConfig, validateIsDirectivePlacement, and
walkSelections have loose type signatures that rely on type inference and use
the any type. Add explicit `: void` return type annotations to
extractQueryCacheConfig and validateIsDirectivePlacement methods. For the
walkSelections method, replace the `any[]` type with a more specific GraphQL
type such as `SelectionNode[]`. If SelectionNode is not already imported from
the graphql package, add it to the existing type imports at the top of the file.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@composition/src/v1/normalization/normalization-factory.ts`:
- Around line 4535-4538: The validateNestedInputObjectMapping function currently
validates that required key fields exist but does not reject input object fields
that are not part of the mapped keys. After building the fieldMappings array
from topLevelGroups in the function, add validation to ensure all input fields
from inputData.inputValueDataByName are accounted for in the mapping. Reject any
extra input fields that are not part of the mapped keys by throwing an error
with a clear message indicating which fields are unmapped. This validation
should be added both at the primary location (around lines 4535-4538 where
fieldMappings is created) and at the secondary location mentioned in the comment
(lines 4652-4655).
- Around line 4770-4773: The composite mapping loop through fieldMappings is
unconditionally adding entries to mappedKeyFieldToArgumentName without checking
for duplicates first, unlike the scalar path that validates this. Before calling
mappedKeyFieldToArgumentName.set() with fieldMapping.entityKeyField and
argInfo.name, check if fieldMapping.entityKeyField already exists in the
mappedKeyFieldToArgumentName map. If the key field is already mapped, reject the
composite mapping and provide an appropriate error or validation failure instead
of silently overwriting the existing entry.
- Around line 4864-4886: The else branch that handles scalar arguments (when
argInfo.isList is false) in batch mode currently skips validation with a
placeholder comment. This allows type mismatches to pass through. Add type
validation in this branch by comparing the scalar argTypeNode against the
keyFieldTypeNode using the namedTypesMatch method, similar to how list arguments
are validated above. If the types do not match, push an invalidDirectiveError
using explicitTypeMismatchErrorMessage with the same parameters (argInfo.name,
fieldCoords, printTypeNode(argTypeNode), and printTypeNode(keyFieldTypeNode)) to
ensure scalar companion types in batch mappings are properly validated.
- Around line 4955-4957: The filter in the extraArgs assignment (around line
4955) is currently excluding unannotated arguments whose names happen to match
key fields in allKeyFieldPaths. Once explicit mappings with `@openfed__is` are
used, all unannotated arguments should be treated as extra regardless of whether
their names match key field names. Remove the condition
!allKeyFieldPaths.has(a.name) from the extraArgs filter so that the filter only
checks !a.isFieldValue, ensuring that any unannotated argument is treated as
extra and preventing name-based matching to key fields that could cause
cache-key collisions. Apply this same logic change to the similar filter at line
4995-4997 that also applies to this same issue.

---

Nitpick comments:
In `@composition/src/v1/normalization/normalization-factory.ts`:
- Around line 4281-4287: The methods extractQueryCacheConfig,
validateIsDirectivePlacement, and walkSelections have loose type signatures that
rely on type inference and use the any type. Add explicit `: void` return type
annotations to extractQueryCacheConfig and validateIsDirectivePlacement methods.
For the walkSelections method, replace the `any[]` type with a more specific
GraphQL type such as `SelectionNode[]`. If SelectionNode is not already imported
from the graphql package, add it to the existing type imports at the top of the
file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d70e1fef-0d05-4fe8-a9a4-49a3c4c26db1

📥 Commits

Reviewing files that changed from the base of the PR and between efa4cdf and b4effbd.

📒 Files selected for processing (3)
  • composition/src/errors/errors.ts
  • composition/src/v1/normalization/normalization-factory.ts
  • composition/tests/v1/directives/query-cache.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • composition/tests/v1/directives/query-cache.test.ts

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
composition/src/v1/normalization/normalization-factory.ts (2)

4953-4995: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Treat every unmapped argument as cache-key input.

The extra-argument checks ignore any non-@openfed__is argument whose name matches any entity key path. Since Line 4683 says argument names are never implicitly mapped, such arguments are omitted from fieldMappings and can still affect resolver output without being part of the cache key.

🐛 Proposed fix
-      const extraArgs = argumentInfos.filter((a) => !a.isFieldValue && !allKeyFieldPaths.has(a.name));
+      const extraArgs = argumentInfos.filter((a) => !a.isFieldValue);
@@
-      const globalExtraArgs = argumentInfos.filter((a) => !a.isFieldValue && !allKeyFieldPaths.has(a.name));
+      const globalExtraArgs = argumentInfos.filter((a) => !a.isFieldValue);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 4953
- 4995, The extra-argument validation in normalization-factory should treat
every unmapped non-@openfed__is argument as cache-key input, not exclude ones
whose names happen to match any entity key path. Update the batch and
singular/composite checks around the explicitMappings/argumentInfos filtering so
only actually mapped field values are exempt, and ensure the cache-key
accounting uses the same mapping source as fieldMappings rather than
allKeyFieldPaths alone.

5369-5385: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the root-type helper so default Query fields are accepted.

operationTypeNodeByTypeName.get(parentTypeName) only covers explicitly registered operation types; default type Query can reach extractQueryCacheConfig with undefined and be rejected as non-Query. Use the existing helper that also recognizes default root names.

🐛 Proposed fix
-          const operationTypeNode = this.operationTypeNodeByTypeName.get(parentTypeName);
+          const operationTypeNode = this.getOperationTypeNodeForRootTypeName(parentTypeName);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 5369
- 5385, The root-type lookup in normalization is too narrow because
`operationTypeNodeByTypeName.get(parentTypeName)` misses default root names like
`Query`, which lets `extractQueryCacheConfig` see `undefined` and reject valid
fields. Update the logic in `normalization-factory.ts` to use the existing
helper that resolves root operation types by both registered nodes and default
names, then pass that result through the `operationTypeNode` flow before calling
`extractQueryCacheConfig` and related cache handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@composition/src/v1/normalization/normalization-factory.ts`:
- Around line 4706-4710: `buildExplicitMappings` in `normalization-factory` is
using undeclared inputs and an out-of-date helper call, so restore the mapping
helper contract by reintroducing the missing local values (`fieldCoords`,
`isListReturn`, `entityTypeName`) before they are used and update the
`buildCompositeIsMapping(...)` invocation to match its current 3-argument
signature. Check the surrounding `buildExplicitMappings` flow and the helper
definitions it calls so the explicit mapping path compiles again.

In `@composition/src/v1/normalization/types/types.ts`:
- Around line 204-209: `IsDirectiveNode.arguments` is typed incorrectly and
should model a GraphQL directive argument AST instead of a string tuple. Update
the `IsDirectiveNode` definition in `types.ts` so it matches the `@openfed__is`
shape with a `Kind.ARGUMENT` node containing a `StringValueNode` for the
`fields` argument, consistent with the other directive node types and the
normalizer’s AST-based reads. Use the `IsDirectiveNode` and `OPENFED_IS` symbols
to locate and align the type with the existing directive node patterns.

---

Outside diff comments:
In `@composition/src/v1/normalization/normalization-factory.ts`:
- Around line 4953-4995: The extra-argument validation in normalization-factory
should treat every unmapped non-@openfed__is argument as cache-key input, not
exclude ones whose names happen to match any entity key path. Update the batch
and singular/composite checks around the explicitMappings/argumentInfos
filtering so only actually mapped field values are exempt, and ensure the
cache-key accounting uses the same mapping source as fieldMappings rather than
allKeyFieldPaths alone.
- Around line 5369-5385: The root-type lookup in normalization is too narrow
because `operationTypeNodeByTypeName.get(parentTypeName)` misses default root
names like `Query`, which lets `extractQueryCacheConfig` see `undefined` and
reject valid fields. Update the logic in `normalization-factory.ts` to use the
existing helper that resolves root operation types by both registered nodes and
default names, then pass that result through the `operationTypeNode` flow before
calling `extractQueryCacheConfig` and related cache handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: caf443e7-1d73-4b67-b648-77d7d249feb0

📥 Commits

Reviewing files that changed from the base of the PR and between 30c81e0 and 1fbcf23.

📒 Files selected for processing (10)
  • composition/src/directive-definition-data/directive-definition-data.ts
  • composition/src/errors/errors.ts
  • composition/src/errors/types/params.ts
  • composition/src/router-configuration/types.ts
  • composition/src/router-configuration/utils.ts
  • composition/src/types/types.ts
  • composition/src/v1/constants/directive-definitions.ts
  • composition/src/v1/normalization/normalization-factory.ts
  • composition/src/v1/normalization/types/types.ts
  • shared/src/router-config/builder.ts
✅ Files skipped from review due to trivial changes (1)
  • composition/src/errors/types/params.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • composition/src/v1/constants/directive-definitions.ts
  • shared/src/router-config/builder.ts
  • composition/src/directive-definition-data/directive-definition-data.ts

Comment on lines +4706 to +4710
buildExplicitMappings(
fieldData: FieldData,
keyFieldSets: Map<string, KeyFieldSetData>,
argumentInfos: Array<ArgumentInfo>,
): EntityKeyMappingConfig[] {

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.

🎯 Functional Correctness | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '4706,5193p' composition/src/v1/normalization/normalization-factory.ts
rg -nP '\bbuildCompositeIsMapping\s*\(' composition/src/v1/normalization/normalization-factory.ts

Repository: wundergraph/cosmo

Length of output: 19048


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the start of buildExplicitMappings and the full buildCompositeIsMapping signature/body.
sed -n '4706,4795p' composition/src/v1/normalization/normalization-factory.ts
printf '\n---\n'
sed -n '5118,5198p' composition/src/v1/normalization/normalization-factory.ts

Repository: wundergraph/cosmo

Length of output: 6824


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the start of buildExplicitMappings and the full buildCompositeIsMapping signature/body.
sed -n '4706,4795p' composition/src/v1/normalization/normalization-factory.ts
printf '\n---\n'
sed -n '5118,5198p' composition/src/v1/normalization/normalization-factory.ts

Repository: wundergraph/cosmo

Length of output: 6824


Restore the mapping helper contract. buildExplicitMappings still references fieldCoords, isListReturn, and entityTypeName before declaring them, and the buildCompositeIsMapping(...) call no longer matches its 3-argument signature. This leaves the normalization path uncompilable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/normalization-factory.ts` around lines 4706
- 4710, `buildExplicitMappings` in `normalization-factory` is using undeclared
inputs and an out-of-date helper call, so restore the mapping helper contract by
reintroducing the missing local values (`fieldCoords`, `isListReturn`,
`entityTypeName`) before they are used and update the
`buildCompositeIsMapping(...)` invocation to match its current 3-argument
signature. Check the surrounding `buildExplicitMappings` flow and the helper
definitions it calls so the explicit mapping path compiles again.

Comment on lines +204 to +209
export type IsDirectiveNode = {
readonly arguments: readonly [string];
readonly kind: Kind.DIRECTIVE;
readonly name: NameNode & { readonly value: typeof OPENFED_IS };
readonly loc?: Location;
};

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.

🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP --type=ts '\bIsDirectiveNode\b' -C3
# Inspect a representative FieldsArgumentNode / argument node type for the intended shape
rg -nP --type=ts '(FieldsArgumentNode|MaxAgeArgumentNode)\b' -C3

Repository: wundergraph/cosmo

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the target file and related type definitions.
git ls-files 'composition/src/v1/normalization/types/*.ts' | sed -n '1,20p'
echo '---'
sed -n '1,280p' composition/src/v1/normalization/types/types.ts
echo '---'
rg -n "type (IsDirectiveNode|QueryCacheDirectiveNode|CachePopulateDirectiveNode|FieldsArgumentNode|MaxAgeArgumentNode|ArgumentInfo)" composition/src/v1/normalization/types -C 3

Repository: wundergraph/cosmo

Length of output: 10034


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the target file and related type definitions.
git ls-files 'composition/src/v1/normalization/types/*.ts' | sed -n '1,20p'
echo '---'
sed -n '1,280p' composition/src/v1/normalization/types/types.ts
echo '---'
rg -n "type (IsDirectiveNode|QueryCacheDirectiveNode|CachePopulateDirectiveNode|FieldsArgumentNode|MaxAgeArgumentNode|ArgumentInfo)" composition/src/v1/normalization/types -C 3

Repository: wundergraph/cosmo

Length of output: 10034


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "OPENFED_IS|IsDirectiveNode|OPENFED_QUERY_CACHE|OPENFED_CACHE_POPULATE|EntityCacheDirectiveNode|QueryCacheDirectiveNode|CachePopulateDirectiveNode" composition/src -C 3

echo '---'
rg -n "arguments: readonly \[string\]|readonly \[string\]" composition/src -C 2

Repository: wundergraph/cosmo

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focused evidence for the `@openfed__is` directive shape and any consumer/cast of IsDirectiveNode.
rg -n "IsDirectiveNode|as IsDirectiveNode|OPENFED_IS_DEFINITION|`@openfed__is`|fields: String!" composition/src -C 3

echo '---'
# Look for any dedicated argument-node type for `@openfed__is` or similar patterns.
rg -n "FieldsArgumentNode|fieldsArgument|OPENFED_IS.*ArgumentNode|ArgumentNode" composition/src/v1/normalization/types composition/src/v1/constants/directive-definitions.ts -C 2

Repository: wundergraph/cosmo

Length of output: 29498


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focused evidence for the `@openfed__is` directive shape and any consumer/cast of IsDirectiveNode.
rg -n "IsDirectiveNode|as IsDirectiveNode|OPENFED_IS_DEFINITION|`@openfed__is`|fields: String!" composition/src -C 3

echo '---'
# Look for any dedicated argument-node type for `@openfed__is` or similar patterns.
rg -n "FieldsArgumentNode|fieldsArgument|OPENFED_IS.*ArgumentNode|ArgumentNode" composition/src/v1/normalization/types composition/src/v1/constants/directive-definitions.ts -C 2

Repository: wundergraph/cosmo

Length of output: 29498


IsDirectiveNode.arguments should be a directive argument node, not string.
@openfed__is is defined as fields: String!, and the normalizer reads directive arguments as GraphQL AST nodes. This should model the fields argument shape (Kind.ARGUMENT + StringValueNode) to match the other directive node types.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition/src/v1/normalization/types/types.ts` around lines 204 - 209,
`IsDirectiveNode.arguments` is typed incorrectly and should model a GraphQL
directive argument AST instead of a string tuple. Update the `IsDirectiveNode`
definition in `types.ts` so it matches the `@openfed__is` shape with a
`Kind.ARGUMENT` node containing a `StringValueNode` for the `fields` argument,
consistent with the other directive node types and the normalizer’s AST-based
reads. Use the `IsDirectiveNode` and `OPENFED_IS` symbols to locate and align
the type with the existing directive node patterns.

@Aenimus Aenimus marked this pull request as draft June 25, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants