Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/plugins/other/visitor-plugin-common/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# @graphql-codegen/visitor-plugin-common

## 7.1.2

### Patch Changes

- [#10839](https://github.com/dotansimha/graphql-code-generator/pull/10839)
[`8a65a1b`](https://github.com/dotansimha/graphql-code-generator/commit/8a65a1b403e92eedf73c9b1bce8262a4dbc7df8f)
Thanks [@vkbansal-rubrik](https://github.com/vkbansal-rubrik)! - handles conditional spread of a
fragment whose top-level selections are fragment spreads or contain inline fragments

## 7.1.1

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/other/visitor-plugin-common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@graphql-codegen/visitor-plugin-common",
"version": "7.1.1",
"version": "7.1.2",
"type": "module",
"repository": {
"type": "git",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,36 @@ export class SelectionSetToObject<
[],
);

collectGrouped(flattenedSelectionNodes);
// Top-level INLINE_FRAGMENT / FRAGMENT_SPREAD nodes among the
// inlined selections cannot be consumed directly by
// `buildSelectionSet`, which only handles FIELD and DIRECTIVE
// kinds for AST nodes and throws "Unexpected type." otherwise.
// Route them through `flattenSelectionSet` — which already
// expands inline fragments and fragment spreads per type — and
// merge the FIELD-only result with the already-FIELD selections
// before handing off.
const directNodes: GroupedTypeNameNode[] = [];
const nestedSelections: SelectionNode[] = [];
for (const n of flattenedSelectionNodes) {
if (
'kind' in n &&
(n.kind === Kind.INLINE_FRAGMENT || n.kind === Kind.FRAGMENT_SPREAD)
) {
nestedSelections.push(n);
} else {
directNodes.push(n);
}
}
if (nestedSelections.length) {
const { selectionNodesByTypeName: nestedByType } = this.flattenSelectionSet(
nestedSelections,
schemaType,
);
const nestedForThisType = nestedByType.get(typeName) ?? [];
directNodes.push(...nestedForThisType);
}

collectGrouped(directNodes);
}

if (incrementalDirectivesFound) {
Expand Down
12 changes: 12 additions & 0 deletions packages/plugins/typescript/operations/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# @graphql-codegen/typescript-operations

## 6.0.5

### Patch Changes

- [#10839](https://github.com/dotansimha/graphql-code-generator/pull/10839)
[`8a65a1b`](https://github.com/dotansimha/graphql-code-generator/commit/8a65a1b403e92eedf73c9b1bce8262a4dbc7df8f)
Thanks [@vkbansal-rubrik](https://github.com/vkbansal-rubrik)! - handles conditional spread of a
fragment whose top-level selections are fragment spreads or contain inline fragments
- Updated dependencies
[[`8a65a1b`](https://github.com/dotansimha/graphql-code-generator/commit/8a65a1b403e92eedf73c9b1bce8262a4dbc7df8f)]:
- @graphql-codegen/visitor-plugin-common@7.1.2

## 6.0.4

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/typescript/operations/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@graphql-codegen/typescript-operations",
"version": "6.0.4",
"version": "6.0.5",
"type": "module",
"description": "GraphQL Code Generator plugin for generating TypeScript types for GraphQL queries, mutations, subscriptions and fragments",
"repository": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,173 @@ describe('TypeScript Operations Plugin - @include directives', () => {
"
`);
});

it('handles conditional spread of a fragment whose top-level selections contain inline fragments', async () => {
// Regression: when the spread fragment's top-level selection set contains
// an INLINE_FRAGMENT (or nested FRAGMENT_SPREAD), the conditional path used
// to push those raw AST nodes into `buildSelectionSet`, which only accepts
// FIELD/DIRECTIVE for raw nodes and threw `TypeError: Unexpected type.`.
const testSchema = buildSchema(/* GraphQL */ `
type Book {
id: ID!
title: String!
}
type Magazine {
id: ID!
issue: Int!
}
union Publication = Book | Magazine
type Library {
id: ID!
name: String!
featured: Publication
}
type Query {
library(id: ID!): Library
}
`);

const document = parse(/* GraphQL */ `
fragment PublicationFragment on Publication {
... on Book {
id
title
}
... on Magazine {
id
issue
}
}

query Library($includeFeatured: Boolean!) {
library(id: "x") {
id
name
featured {
...PublicationFragment @include(if: $includeFeatured)
}
}
}
`);

const { content } = await plugin(
testSchema,
[{ location: '', document }],
{},
{ outputFile: 'graphql.ts' },
);

// Should not throw, and should produce a usable type where the Publication
// fields appear when includeFeatured=true and an empty-object variant
// covers includeFeatured=false.
expect(content).toContain('LibraryQuery');
expect(content).toContain('featured');
expect(content).toMatchInlineSnapshot(`
"type PublicationFragment_Book_Fragment = { id: string, title: string };

type PublicationFragment_Magazine_Fragment = { id: string, issue: number };

export type PublicationFragmentFragment =
| PublicationFragment_Book_Fragment
| PublicationFragment_Magazine_Fragment
;

export type LibraryQueryVariables = Exact<{
includeFeatured: boolean;
}>;


export type LibraryQuery = { library: { id: string, name: string, featured:
| { id: string, title: string }
| { id: string, issue: number }
| Record<PropertyKey, never>
| null } | null };
"
`);
});

it('handles conditional spread of a fragment whose top-level selections are fragment spreads', async () => {
// Same regression, but the inline fragments inside the spread fragment have
// been refactored into named fragment spreads — also failed before the fix.
const testSchema = buildSchema(/* GraphQL */ `
type Book {
id: ID!
title: String!
}
type Magazine {
id: ID!
issue: Int!
}
union Publication = Book | Magazine
type Library {
id: ID!
featured: Publication
}
type Query {
library(id: ID!): Library
}
`);

const document = parse(/* GraphQL */ `
fragment BookFragment on Book {
id
title
}
fragment MagazineFragment on Magazine {
id
issue
}
fragment PublicationFragment on Publication {
...BookFragment
...MagazineFragment
}

query Library($includeFeatured: Boolean!) {
library(id: "x") {
id
featured {
...PublicationFragment @include(if: $includeFeatured)
}
}
}
`);

const { content } = await plugin(
testSchema,
[{ location: '', document }],
{},
{ outputFile: 'graphql.ts' },
);

expect(content).toContain('LibraryQuery');
expect(content).toContain('featured');
expect(content).toMatchInlineSnapshot(`
"export type BookFragmentFragment = { id: string, title: string };

export type MagazineFragmentFragment = { id: string, issue: number };

type PublicationFragment_Book_Fragment = { id: string, title: string };

type PublicationFragment_Magazine_Fragment = { id: string, issue: number };

export type PublicationFragmentFragment =
| PublicationFragment_Book_Fragment
| PublicationFragment_Magazine_Fragment
;

export type LibraryQueryVariables = Exact<{
includeFeatured: boolean;
}>;


export type LibraryQuery = { library: { id: string, featured:
| { id: string, title: string }
| { id: string, issue: number }
| Record<PropertyKey, never>
| null } | null };
"
`);
});
});

describe('TypeScript Operations Plugin - @skip directive', () => {
Expand Down
Loading