-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Problem
When generating GraphQL queries for content types that have properties of type array with items of type content (and no explicit allowedTypes/restrictedTypes), the createSingleContentQuery method produces fragments that reference themselves, either directly or indirectly. This causes GraphQL validation errors:
Example errors:
Cannot spread fragment "MapPage" within itself.
Cannot spread fragment "MapPage" within itself via "ArticlePage".
Cannot spread fragment "ArticlePage" within itself.
Root Cause
The issue is in convertPropertyField in packages/optimizely-cms-sdk/src/graph/createQuery.ts.
When a property is defined as:
MainMap: {
type: 'array',
items: {
type: 'content',
},
}The array handler delegates to convertProperty with the inner items definition, which hits the content case. The content handler calls resolveAllowedTypes() — and since there are no allowedTypes or restrictedTypes, it returns all registered content types, including the type currently being processed.
For each allowed type, two things happen:
createFragment(key, visited, ...)is called — this correctly returns[]when the key is already in thevisitedset, preventing duplicate fragment definitions.subfields.push('...' + key)adds the fragment spread reference unconditionally.
The problem is in step 2: the spread reference ...MapPage is added inside the MapPage fragment body even though MapPage is already in the visited set. This creates the circular reference that GraphQL rejects.
The same issue occurs with indirect cycles. For example, MapPage includes ...ArticlePage, and ArticlePage also has a MainContentArea content array that includes ...MapPage, creating a mutual recursion: MapPage → ArticlePage → MapPage.
Example
Given a MapPage type with multiple content-area properties (MainMap, BodyContent, RelatedDatasets), the generated query includes:
fragment MapPage on MapPage {
__typename
MapPage__MainMap: MainMap {
__typename
...MapPage # <-- circular: MapPage references itself
...ArticlePage
...ContainerPage
# ... all other content types
}
# ...
}Fix
In convertPropertyField, the content type handler should skip types that are already in the visited set before adding the spread reference. This prevents both direct self-references and indirect cycles.
for (const t of allowed) {
let key = getKeyName(t);
// If the key is '_self', we use the root name (since it's self-referential)
if (key === '_self') {
key = rootName;
}
+ // Skip types already being processed to prevent circular fragment references
+ // (e.g. MapPage spreading ...MapPage within itself)
+ if (visited.has(key)) {
+ continue;
+ }
extraFragments.push(
...createFragment(key, visited, '', true, damEnabled),
);
subfields.push(`...${key}`);
}The existing guard inside createFragment (if (visited.has(fragmentName)) return [];) already prevents duplicate fragment definitions, but it doesn't prevent the caller from adding the spread reference. This fix closes that gap by checking visited before both the createFragment call and the subfields.push.
How to reproduce
- Define content types where at least one has a property of type
arraywith items of typecontentand noallowedTypesconstraint (e.g., a content area that accepts all types). - Call
createSingleContentQuery('MapPage')(or any type with such a property). - The generated query will contain circular fragment spreads, causing a
GRAPHQL_VALIDATION_FAILEDerror when sent to the Content Graph endpoint.
Impact
Any content type with an unrestricted content-area property cannot be queried via the generated GraphQL query. This affects page preview and any other functionality that relies on createSingleContentQuery or createMultipleContentQuery.