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
19 changes: 19 additions & 0 deletions packages/apollo/tests/e2e/generated-definitions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,23 @@ describe('Generated Definitions', () => {
),
).toBe(await readFile(outputFile, 'utf8'));
});

it('should apply typeName function to transform type names', async () => {
const typeDefs = await readFile(
generatedDefinitions('type-name.graphql'),
'utf8',
);

const outputFile = generatedDefinitions('type-name.test-definitions.ts');
await graphqlFactory.generateDefinitions(typeDefs, {
definitions: {
path: outputFile,
typeName: (name: string) => `${name}Schema`,
},
});

expect(
await readFile(generatedDefinitions('type-name.fixture.ts'), 'utf8'),
).toBe(await readFile(outputFile, 'utf8'));
});
});
47 changes: 47 additions & 0 deletions packages/apollo/tests/generated-definitions/type-name.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@

/*
* -------------------------------------------------------
* THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
* -------------------------------------------------------
*/

/* tslint:disable */
/* eslint-disable */

export enum UserRoleSchema {
ADMIN = "ADMIN",
USER = "USER",
GUEST = "GUEST"
}

export interface CreateUserInputSchema {
name: string;
email: string;
role: UserRoleSchema;
}

export interface UserSchema {
id: string;
name: string;
email: string;
role: UserRoleSchema;
profile?: Nullable<ProfileSchema>;
}

export interface ProfileSchema {
bio?: Nullable<string>;
avatar?: Nullable<string>;
}

export interface IQuery {
user(id: string): Nullable<UserSchema> | Promise<Nullable<UserSchema>>;
searchUsers(query: string): SearchResultSchema[] | Promise<SearchResultSchema[]>;
}

export interface IMutation {
createUser(input: CreateUserInputSchema): UserSchema | Promise<UserSchema>;
}

export type DateTimeSchema = any;
export type SearchResultSchema = UserSchema | ProfileSchema;
type Nullable<T> = T | null;
37 changes: 37 additions & 0 deletions packages/apollo/tests/generated-definitions/type-name.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
type User {
id: ID!
name: String!
email: String!
role: UserRole!
profile: Profile
}

type Profile {
bio: String
avatar: String
}

enum UserRole {
ADMIN
USER
GUEST
}

union SearchResult = User | Profile

input CreateUserInput {
name: String!
email: String!
role: UserRole!
}

type Query {
user(id: ID!): User
searchUsers(query: String!): [SearchResult!]!
}

type Mutation {
createUser(input: CreateUserInput!): User!
}

scalar DateTime
67 changes: 55 additions & 12 deletions packages/graphql/lib/graphql-ast.explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ export interface DefinitionsGeneratorOptions {
* @default false
*/
enumsAsTypes?: boolean;

/**
* If provided, specifies a function to transform type names.
* @example (name) => `${name}Schema`
* @default undefined
*/
typeName?: (name: string) => string;
}

@Injectable()
Expand Down Expand Up @@ -176,7 +183,7 @@ export class GraphQLAstExplorer {
return this.toEnumDefinitionStructure(item, options);
case 'UnionTypeDefinition':
case 'UnionTypeExtension':
return this.toUnionDefinitionStructure(item);
return this.toUnionDefinitionStructure(item, options);
}
}

Expand Down Expand Up @@ -230,10 +237,14 @@ export class GraphQLAstExplorer {
? tsMorphLib.StructureKind.Class
: tsMorphLib.StructureKind.Interface;
const isRoot = this.root.indexOf(parentName) >= 0;
// Don't transform root type names (Query, Mutation, Subscription)
const transformedName = isRoot
? parentName
: this.getTransformedTypeName(parentName, options);
const parentStructure:
| ClassDeclarationStructure
| InterfaceDeclarationStructure = {
name: this.addSymbolIfRoot(upperFirst(parentName)),
name: this.addSymbolIfRoot(upperFirst(transformedName)),
isExported: true,
isAbstract: isRoot && mode === 'class',
kind: structureKind,
Expand All @@ -245,11 +256,21 @@ export class GraphQLAstExplorer {
if (interfaces) {
if (mode === 'class') {
(parentStructure as ClassDeclarationStructure).implements = interfaces
.map((element) => get(element, 'name.value'))
.map((element) => {
const interfaceName = get(element, 'name.value');
return interfaceName
? this.getTransformedTypeName(interfaceName, options)
: null;
})
.filter(Boolean);
} else {
parentStructure.extends = interfaces
.map((element) => get(element, 'name.value'))
.map((element) => {
const interfaceName = get(element, 'name.value');
return interfaceName
? this.getTransformedTypeName(interfaceName, options)
: null;
})
.filter(Boolean);
}
}
Expand Down Expand Up @@ -395,7 +416,11 @@ export class GraphQLAstExplorer {
getType(typeName: string, options: DefinitionsGeneratorOptions): string {
const defaults = this.getDefaultTypes(options);
const isDefault = defaults[typeName];
return isDefault ? defaults[typeName] : upperFirst(typeName);
if (isDefault) {
return defaults[typeName];
}
const transformedName = this.getTransformedTypeName(typeName, options);
return upperFirst(transformedName);
}

getDefaultTypes(options: DefinitionsGeneratorOptions): {
Expand Down Expand Up @@ -444,9 +469,11 @@ export class GraphQLAstExplorer {
const mappedTypeName =
typeof typeMapping === 'string' ? typeMapping : typeMapping?.name;

const transformedName = this.getTransformedTypeName(name, options);

return {
kind: tsMorphLib.StructureKind.TypeAlias,
name,
name: transformedName,
type: mappedTypeName ?? options.defaultScalarType ?? 'any',
isExported: true,
};
Expand All @@ -460,13 +487,15 @@ export class GraphQLAstExplorer {
if (!name) {
return undefined;
}
const transformedName = this.getTransformedTypeName(name, options);

if (options.enumsAsTypes) {
const values = item.values.map(
(value) => `"${get(value, 'name.value')}"`,
);
return {
kind: tsMorphLib.StructureKind.TypeAlias,
name,
name: transformedName,
type: values.join(' | '),
isExported: true,
};
Expand All @@ -477,26 +506,30 @@ export class GraphQLAstExplorer {
}));
return {
kind: tsMorphLib.StructureKind.Enum,
name,
name: transformedName,
members,
isExported: true,
};
}

toUnionDefinitionStructure(
item: UnionTypeDefinitionNode | UnionTypeExtensionNode,
options: DefinitionsGeneratorOptions,
): TypeAliasDeclarationStructure {
const name = get(item, 'name.value');
if (!name) {
return undefined;
}
const types: string[] = map(item.types, (value) =>
get(value, 'name.value'),
);
const transformedName = this.getTransformedTypeName(name, options);

const types: string[] = map(item.types, (value) => {
const typeName = get(value, 'name.value');
return typeName ? this.getTransformedTypeName(typeName, options) : null;
}).filter(Boolean);

return {
kind: tsMorphLib.StructureKind.TypeAlias,
name,
name: transformedName,
type: types.join(' | '),
isExported: true,
};
Expand All @@ -509,4 +542,14 @@ export class GraphQLAstExplorer {
isRoot(name: string): boolean {
return ['IQuery', 'IMutation', 'ISubscription'].indexOf(name) >= 0;
}

private getTransformedTypeName(
name: string,
options: DefinitionsGeneratorOptions,
): string {
if (!options.typeName) {
return name;
}
return options.typeName(name);
}
}
1 change: 1 addition & 0 deletions packages/graphql/lib/graphql.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export class GraphQLFactory {
additionalHeader: options.definitions.additionalHeader,
defaultTypeMapping: options.definitions.defaultTypeMapping,
enumsAsTypes: options.definitions.enumsAsTypes,
typeName: options.definitions.typeName,
};
const tsFile = await this.graphqlAstExplorer.explore(
gql`
Expand Down