Skip to content

Commit a1ccdaf

Browse files
Merge pull request #3678 from mag123c/feat/custom-type-name-definitions
feat(graphql): add type-name-option-for-custom-type-naming
2 parents 4e7d467 + 9e151cd commit a1ccdaf

File tree

5 files changed

+159
-12
lines changed

5 files changed

+159
-12
lines changed

packages/apollo/tests/e2e/generated-definitions.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,4 +358,23 @@ describe('Generated Definitions', () => {
358358
),
359359
).toBe(await readFile(outputFile, 'utf8'));
360360
});
361+
362+
it('should apply typeName function to transform type names', async () => {
363+
const typeDefs = await readFile(
364+
generatedDefinitions('type-name.graphql'),
365+
'utf8',
366+
);
367+
368+
const outputFile = generatedDefinitions('type-name.test-definitions.ts');
369+
await graphqlFactory.generateDefinitions(typeDefs, {
370+
definitions: {
371+
path: outputFile,
372+
typeName: (name: string) => `${name}Schema`,
373+
},
374+
});
375+
376+
expect(
377+
await readFile(generatedDefinitions('type-name.fixture.ts'), 'utf8'),
378+
).toBe(await readFile(outputFile, 'utf8'));
379+
});
361380
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
/*
3+
* -------------------------------------------------------
4+
* THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
5+
* -------------------------------------------------------
6+
*/
7+
8+
/* tslint:disable */
9+
/* eslint-disable */
10+
11+
export enum UserRoleSchema {
12+
ADMIN = "ADMIN",
13+
USER = "USER",
14+
GUEST = "GUEST"
15+
}
16+
17+
export interface CreateUserInputSchema {
18+
name: string;
19+
email: string;
20+
role: UserRoleSchema;
21+
}
22+
23+
export interface UserSchema {
24+
id: string;
25+
name: string;
26+
email: string;
27+
role: UserRoleSchema;
28+
profile?: Nullable<ProfileSchema>;
29+
}
30+
31+
export interface ProfileSchema {
32+
bio?: Nullable<string>;
33+
avatar?: Nullable<string>;
34+
}
35+
36+
export interface IQuery {
37+
user(id: string): Nullable<UserSchema> | Promise<Nullable<UserSchema>>;
38+
searchUsers(query: string): SearchResultSchema[] | Promise<SearchResultSchema[]>;
39+
}
40+
41+
export interface IMutation {
42+
createUser(input: CreateUserInputSchema): UserSchema | Promise<UserSchema>;
43+
}
44+
45+
export type DateTimeSchema = any;
46+
export type SearchResultSchema = UserSchema | ProfileSchema;
47+
type Nullable<T> = T | null;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
type User {
2+
id: ID!
3+
name: String!
4+
email: String!
5+
role: UserRole!
6+
profile: Profile
7+
}
8+
9+
type Profile {
10+
bio: String
11+
avatar: String
12+
}
13+
14+
enum UserRole {
15+
ADMIN
16+
USER
17+
GUEST
18+
}
19+
20+
union SearchResult = User | Profile
21+
22+
input CreateUserInput {
23+
name: String!
24+
email: String!
25+
role: UserRole!
26+
}
27+
28+
type Query {
29+
user(id: ID!): User
30+
searchUsers(query: String!): [SearchResult!]!
31+
}
32+
33+
type Mutation {
34+
createUser(input: CreateUserInput!): User!
35+
}
36+
37+
scalar DateTime

packages/graphql/lib/graphql-ast.explorer.ts

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ export interface DefinitionsGeneratorOptions {
8484
* @default false
8585
*/
8686
enumsAsTypes?: boolean;
87+
88+
/**
89+
* If provided, specifies a function to transform type names.
90+
* @example (name) => `${name}Schema`
91+
* @default undefined
92+
*/
93+
typeName?: (name: string) => string;
8794
}
8895

8996
@Injectable()
@@ -176,7 +183,7 @@ export class GraphQLAstExplorer {
176183
return this.toEnumDefinitionStructure(item, options);
177184
case 'UnionTypeDefinition':
178185
case 'UnionTypeExtension':
179-
return this.toUnionDefinitionStructure(item);
186+
return this.toUnionDefinitionStructure(item, options);
180187
}
181188
}
182189

@@ -230,10 +237,14 @@ export class GraphQLAstExplorer {
230237
? tsMorphLib.StructureKind.Class
231238
: tsMorphLib.StructureKind.Interface;
232239
const isRoot = this.root.indexOf(parentName) >= 0;
240+
// Don't transform root type names (Query, Mutation, Subscription)
241+
const transformedName = isRoot
242+
? parentName
243+
: this.getTransformedTypeName(parentName, options);
233244
const parentStructure:
234245
| ClassDeclarationStructure
235246
| InterfaceDeclarationStructure = {
236-
name: this.addSymbolIfRoot(upperFirst(parentName)),
247+
name: this.addSymbolIfRoot(upperFirst(transformedName)),
237248
isExported: true,
238249
isAbstract: isRoot && mode === 'class',
239250
kind: structureKind,
@@ -245,11 +256,21 @@ export class GraphQLAstExplorer {
245256
if (interfaces) {
246257
if (mode === 'class') {
247258
(parentStructure as ClassDeclarationStructure).implements = interfaces
248-
.map((element) => get(element, 'name.value'))
259+
.map((element) => {
260+
const interfaceName = get(element, 'name.value');
261+
return interfaceName
262+
? this.getTransformedTypeName(interfaceName, options)
263+
: null;
264+
})
249265
.filter(Boolean);
250266
} else {
251267
parentStructure.extends = interfaces
252-
.map((element) => get(element, 'name.value'))
268+
.map((element) => {
269+
const interfaceName = get(element, 'name.value');
270+
return interfaceName
271+
? this.getTransformedTypeName(interfaceName, options)
272+
: null;
273+
})
253274
.filter(Boolean);
254275
}
255276
}
@@ -395,7 +416,11 @@ export class GraphQLAstExplorer {
395416
getType(typeName: string, options: DefinitionsGeneratorOptions): string {
396417
const defaults = this.getDefaultTypes(options);
397418
const isDefault = defaults[typeName];
398-
return isDefault ? defaults[typeName] : upperFirst(typeName);
419+
if (isDefault) {
420+
return defaults[typeName];
421+
}
422+
const transformedName = this.getTransformedTypeName(typeName, options);
423+
return upperFirst(transformedName);
399424
}
400425

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

472+
const transformedName = this.getTransformedTypeName(name, options);
473+
447474
return {
448475
kind: tsMorphLib.StructureKind.TypeAlias,
449-
name,
476+
name: transformedName,
450477
type: mappedTypeName ?? options.defaultScalarType ?? 'any',
451478
isExported: true,
452479
};
@@ -460,13 +487,15 @@ export class GraphQLAstExplorer {
460487
if (!name) {
461488
return undefined;
462489
}
490+
const transformedName = this.getTransformedTypeName(name, options);
491+
463492
if (options.enumsAsTypes) {
464493
const values = item.values.map(
465494
(value) => `"${get(value, 'name.value')}"`,
466495
);
467496
return {
468497
kind: tsMorphLib.StructureKind.TypeAlias,
469-
name,
498+
name: transformedName,
470499
type: values.join(' | '),
471500
isExported: true,
472501
};
@@ -477,26 +506,30 @@ export class GraphQLAstExplorer {
477506
}));
478507
return {
479508
kind: tsMorphLib.StructureKind.Enum,
480-
name,
509+
name: transformedName,
481510
members,
482511
isExported: true,
483512
};
484513
}
485514

486515
toUnionDefinitionStructure(
487516
item: UnionTypeDefinitionNode | UnionTypeExtensionNode,
517+
options: DefinitionsGeneratorOptions,
488518
): TypeAliasDeclarationStructure {
489519
const name = get(item, 'name.value');
490520
if (!name) {
491521
return undefined;
492522
}
493-
const types: string[] = map(item.types, (value) =>
494-
get(value, 'name.value'),
495-
);
523+
const transformedName = this.getTransformedTypeName(name, options);
524+
525+
const types: string[] = map(item.types, (value) => {
526+
const typeName = get(value, 'name.value');
527+
return typeName ? this.getTransformedTypeName(typeName, options) : null;
528+
}).filter(Boolean);
496529

497530
return {
498531
kind: tsMorphLib.StructureKind.TypeAlias,
499-
name,
532+
name: transformedName,
500533
type: types.join(' | '),
501534
isExported: true,
502535
};
@@ -509,4 +542,14 @@ export class GraphQLAstExplorer {
509542
isRoot(name: string): boolean {
510543
return ['IQuery', 'IMutation', 'ISubscription'].indexOf(name) >= 0;
511544
}
545+
546+
private getTransformedTypeName(
547+
name: string,
548+
options: DefinitionsGeneratorOptions,
549+
): string {
550+
if (!options.typeName) {
551+
return name;
552+
}
553+
return options.typeName(name);
554+
}
512555
}

packages/graphql/lib/graphql.factory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ export class GraphQLFactory {
208208
additionalHeader: options.definitions.additionalHeader,
209209
defaultTypeMapping: options.definitions.defaultTypeMapping,
210210
enumsAsTypes: options.definitions.enumsAsTypes,
211+
typeName: options.definitions.typeName,
211212
};
212213
const tsFile = await this.graphqlAstExplorer.explore(
213214
gql`

0 commit comments

Comments
 (0)