Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ type FragmentSpreadUsage = {
selectionNodes: Array<SelectionNode>;
};

type TypenameField = {
name: string;
type: string;
};

function isMetadataFieldName(name: string) {
return ['__schema', '__type'].includes(name);
}
Expand Down Expand Up @@ -434,7 +439,7 @@ export class SelectionSetToObject<Config extends ParsedDocumentsConfig = ParsedD
let requireTypename = false;

// usages via fragment typescript type
const fragmentsSpreadUsages: string[] = [];
const fragmentsSpreadUsages: FragmentSpreadUsage[] = [];

// ensure we mutate no function params
selectionNodes = [...selectionNodes];
Expand Down Expand Up @@ -494,7 +499,7 @@ export class SelectionSetToObject<Config extends ParsedDocumentsConfig = ParsedD
}

if (this._config.inlineFragmentTypes === 'combine' || this._config.inlineFragmentTypes === 'mask') {
fragmentsSpreadUsages.push(selectionNode.typeName);
fragmentsSpreadUsages.push(selectionNode);
continue;
}

Expand Down Expand Up @@ -547,10 +552,7 @@ export class SelectionSetToObject<Config extends ParsedDocumentsConfig = ParsedD
this._config.skipTypeNameForRoot
);
const transformed: ProcessResult = [
// Only add the typename field if we're not merging fragment
// types. If we are merging, we need to wait until we know all
// the involved typenames.
...(typeInfoField && (!this._config.mergeFragmentTypes || this._config.inlineFragmentTypes === 'mask')
...(typeInfoField && this.needsTypenameField(fragmentsSpreadUsages)
? this._processor.transformTypenameField(typeInfoField.type, typeInfoField.name)
: []),
...this._processor.transformPrimitiveFields(
Expand Down Expand Up @@ -584,22 +586,41 @@ export class SelectionSetToObject<Config extends ParsedDocumentsConfig = ParsedD

if (fragmentsSpreadUsages.length) {
if (this._config.inlineFragmentTypes === 'combine') {
fields.push(...fragmentsSpreadUsages);
fields.push(...fragmentsSpreadUsages.map(({ typeName }) => typeName));
} else if (this._config.inlineFragmentTypes === 'mask') {
fields.push(`{ ' $fragmentRefs'?: { ${fragmentsSpreadUsages.map(name => `'${name}': ${name}`).join(`;`)} } }`);
fields.push(
`{ ' $fragmentRefs'?: { ${fragmentsSpreadUsages
.map(({ typeName }) => `'${typeName}': ${typeName}`)
.join(`;`)} } }`
);
}
}

return { typeInfo: typeInfoField, fields };
}

/**
* When masking the fragments, typename will always be added.
* When combining the fragments, typename will be added only if there
* are no fragments.
* When merging fragments, we need to wait until we know all the involved
* typenames, so we don't add the typename field.
*/
private needsTypenameField(fragmentsSpreadUsages: FragmentSpreadUsage[]) {
if (this._config.inlineFragmentTypes === 'combine' && fragmentsSpreadUsages.length > 0) {
return false;
}

return !this._config.mergeFragmentTypes || this._config.inlineFragmentTypes === 'mask';
}

protected buildTypeNameField(
type: GraphQLObjectType,
nonOptionalTypename: boolean = this._config.nonOptionalTypename,
addTypename: boolean = this._config.addTypename,
queriedForTypename: boolean = this._queriedForTypename,
skipTypeNameForRoot: boolean = this._config.skipTypeNameForRoot
): { name: string; type: string } {
): TypenameField | null {
const rootTypes = getRootTypes(this._schema);
if (rootTypes.has(type) && skipTypeNameForRoot && !queriedForTypename) {
return null;
Expand Down
34 changes: 30 additions & 4 deletions packages/plugins/typescript/operations/tests/ts-documents.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6364,6 +6364,23 @@ function test(q: GetEntityBrandDataQuery): void {
fragment UserFragment on User {
id
}

query {
me {
id
}
}

query {
search(term: "test") {
...UserFragment
...NotifFragment
}
}
fragment NotifFragment on TextNotification {
id
text
}
`);
const result = await plugin(
schema,
Expand All @@ -6375,12 +6392,21 @@ function test(q: GetEntityBrandDataQuery): void {
export type Unnamed_1_QueryVariables = Exact<{ [key: string]: never; }>;


export type Unnamed_1_Query = { __typename?: 'Query', me?: (
{ __typename?: 'User' }
& UserFragmentFragment
) | null };
export type Unnamed_1_Query = { __typename?: 'Query', me?: UserFragmentFragment | null };

export type UserFragmentFragment = { __typename?: 'User', id: string };

export type Unnamed_2_QueryVariables = Exact<{ [key: string]: never; }>;


export type Unnamed_2_Query = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null };

export type Unnamed_3_QueryVariables = Exact<{ [key: string]: never; }>;


export type Unnamed_3_Query = { __typename?: 'Query', search: Array<NotifFragmentFragment | { __typename?: 'ImageNotification' } | UserFragmentFragment> };

export type NotifFragmentFragment = { __typename?: 'TextNotification', id: string, text: string };
`);
});

Expand Down