Skip to content

Commit 2449d24

Browse files
Merge branch 'tuxmachine-feat/federation'
2 parents 0d7363a + fd4502f commit 2449d24

File tree

73 files changed

+2966
-360
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+2966
-360
lines changed

.prettierrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"trailingComma": "all",
33
"singleQuote": true
4-
}
4+
}

lib/decorators/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * from './mutation.decorator';
66
export * from './parent.decorator';
77
export * from './query.decorator';
88
export * from './resolve-property.decorator';
9+
export * from './resolve-reference.decorator';
910
export * from './resolver.decorator';
1011
export * from './root.decorator';
1112
export * from './scalar.decorator';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { RESOLVER_REFERENCE_METADATA } from '../graphql.constants';
2+
import { SetMetadata } from '@nestjs/common';
3+
4+
export function ResolveReference(): MethodDecorator {
5+
return (
6+
target: Function | Object,
7+
key?: string | symbol,
8+
descriptor?: any,
9+
) => {
10+
SetMetadata(RESOLVER_REFERENCE_METADATA, true)(target, key, descriptor);
11+
};
12+
}

lib/external/type-graphql.types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Type } from '@nestjs/common';
2-
import { GraphQLScalarType } from 'graphql';
2+
import { GraphQLDirective, GraphQLScalarType } from 'graphql';
33

44
/**
55
* Some external types have to be included in order to provide types safety
@@ -41,9 +41,13 @@ export type BasicOptions = DecoratorTypeOptions & DescriptionOptions;
4141
export type AdvancedOptions = BasicOptions &
4242
DepreciationOptions &
4343
SchemaNameOptions;
44+
4445
export interface BuildSchemaOptions {
4546
dateScalarMode?: DateScalarMode;
4647
scalarsMap?: ScalarsTypeMap[];
48+
/** Any types that are not directly referenced or returned by resolvers */
49+
orphanedTypes?: Function[];
50+
directives?: GraphQLDirective[];
4751
}
4852
export type DateScalarMode = 'isoDate' | 'timestamp';
4953
export interface ScalarsTypeMap {

lib/graphql-definitions.factory.ts

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { loadPackage } from '@nestjs/common/utils/load-package.util';
12
import { isEmpty } from '@nestjs/common/utils/shared.utils';
23
import { gql } from 'apollo-server-core';
3-
import { makeExecutableSchema } from 'graphql-tools';
44
import * as chokidar from 'chokidar';
55
import { printSchema } from 'graphql';
6+
import { makeExecutableSchema } from 'graphql-tools';
67
import { GraphQLAstExplorer } from './graphql-ast.explorer';
78
import { GraphQLTypesLoader } from './graphql-types.loader';
8-
import { removeTempField } from './utils/remove-temp.util';
9+
import { removeTempField } from './utils';
910

1011
export class GraphQLDefinitionsFactory {
1112
private readonly gqlAstExplorer = new GraphQLAstExplorer();
@@ -17,9 +18,11 @@ export class GraphQLDefinitionsFactory {
1718
outputAs?: 'class' | 'interface';
1819
watch?: boolean;
1920
debug?: boolean;
21+
federation?: boolean;
2022
}) {
2123
const isDebugEnabled = !(options && options.debug === false);
2224
const typePathsExists = options.typePaths && !isEmpty(options.typePaths);
25+
const isFederation = options && options.federation;
2326
if (!typePathsExists) {
2427
throw new Error(`"typePaths" property cannot be empty.`);
2528
}
@@ -38,6 +41,7 @@ export class GraphQLDefinitionsFactory {
3841
options.typePaths,
3942
options.path,
4043
options.outputAs,
44+
isFederation,
4145
isDebugEnabled,
4246
);
4347
});
@@ -46,11 +50,72 @@ export class GraphQLDefinitionsFactory {
4650
options.typePaths,
4751
options.path,
4852
options.outputAs,
53+
isFederation,
4954
isDebugEnabled,
5055
);
5156
}
5257

5358
private async exploreAndEmit(
59+
typePaths: string[],
60+
path: string,
61+
outputAs: 'class' | 'interface',
62+
isFederation: boolean,
63+
isDebugEnabled: boolean,
64+
) {
65+
if (isFederation) {
66+
return this.exploreAndEmitFederation(
67+
typePaths,
68+
path,
69+
outputAs,
70+
isDebugEnabled,
71+
);
72+
}
73+
return this.exploreAndEmitRegular(
74+
typePaths,
75+
path,
76+
outputAs,
77+
isDebugEnabled,
78+
);
79+
}
80+
81+
private async exploreAndEmitFederation(
82+
typePaths: string[],
83+
path: string,
84+
outputAs: 'class' | 'interface',
85+
isDebugEnabled: boolean,
86+
) {
87+
const typeDefs = await this.gqlTypesLoader.mergeTypesByPaths(typePaths);
88+
89+
const {
90+
buildFederatedSchema,
91+
printSchema,
92+
} = loadPackage('@apollo/federation', 'ApolloFederation', () =>
93+
require('@apollo/federation'),
94+
);
95+
96+
const schema = buildFederatedSchema([
97+
{
98+
typeDefs: gql`
99+
${typeDefs}
100+
`,
101+
resolvers: {},
102+
},
103+
]);
104+
const tsFile = await this.gqlAstExplorer.explore(
105+
gql`
106+
${printSchema(schema)}
107+
`,
108+
path,
109+
outputAs,
110+
);
111+
await tsFile.save();
112+
this.printMessage(
113+
`[${new Date().toLocaleTimeString()}] The definitions have been updated.`,
114+
isDebugEnabled,
115+
);
116+
}
117+
118+
private async exploreAndEmitRegular(
54119
typePaths: string[],
55120
path: string,
56121
outputAs: 'class' | 'interface',

lib/graphql-federation.factory.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { loadPackage } from '@nestjs/common/utils/load-package.util';
3+
import { gql } from 'apollo-server-core';
4+
import { GraphQLSchema } from 'graphql';
5+
import { mergeSchemas } from 'graphql-tools';
6+
import { isEmpty } from 'lodash';
7+
import { GraphQLSchemaBuilder } from './graphql-schema-builder';
8+
import { GqlModuleOptions } from './interfaces';
9+
import {
10+
DelegatesExplorerService,
11+
ResolversExplorerService,
12+
ScalarsExplorerService,
13+
} from './services';
14+
import { extend } from './utils';
15+
16+
@Injectable()
17+
export class GraphQLFederationFactory {
18+
constructor(
19+
private readonly resolversExplorerService: ResolversExplorerService,
20+
private readonly delegatesExplorerService: DelegatesExplorerService,
21+
private readonly scalarsExplorerService: ScalarsExplorerService,
22+
private readonly gqlSchemaBuilder: GraphQLSchemaBuilder,
23+
) {}
24+
25+
async mergeOptions(
26+
options: GqlModuleOptions = {},
27+
): Promise<GqlModuleOptions> {
28+
const transformSchema = async schema =>
29+
options.transformSchema ? options.transformSchema(schema) : schema;
30+
31+
let schema: GraphQLSchema;
32+
if (options.autoSchemaFile) {
33+
// Enable support when Directive support in type-graphql goes stable
34+
throw new Error('Code-first approach is not supported yet');
35+
// schema = await this.generateSchema(options);
36+
} else if (isEmpty(options.typeDefs)) {
37+
schema = options.schema;
38+
} else {
39+
schema = this.buildSchemaFromTypeDefs(options);
40+
}
41+
42+
return {
43+
...options,
44+
schema: await transformSchema(schema),
45+
typeDefs: undefined,
46+
};
47+
}
48+
49+
private buildSchemaFromTypeDefs(options: GqlModuleOptions) {
50+
const { buildFederatedSchema } = loadPackage(
51+
'@apollo/federation',
52+
'ApolloFederation',
53+
() => require('@apollo/federation'),
54+
);
55+
56+
return buildFederatedSchema([
57+
{
58+
typeDefs: gql`
59+
${options.typeDefs}
60+
`,
61+
resolvers: this.getResolvers(options.resolvers),
62+
},
63+
]);
64+
}
65+
66+
private async generateSchema(
67+
options: GqlModuleOptions,
68+
): Promise<GraphQLSchema> {
69+
const {
70+
buildFederatedSchema,
71+
printSchema,
72+
} = loadPackage('@apollo/federation', 'ApolloFederation', () =>
73+
require('@apollo/federation'),
74+
);
75+
76+
const autoGeneratedSchema: GraphQLSchema = await this.gqlSchemaBuilder.buildFederatedSchema(
77+
options.autoSchemaFile,
78+
options.buildSchemaOptions,
79+
this.resolversExplorerService.getAllCtors(),
80+
);
81+
const executableSchema = buildFederatedSchema({
82+
typeDefs: gql(printSchema(autoGeneratedSchema)),
83+
resolvers: this.getResolvers(options.resolvers),
84+
});
85+
86+
const schema = options.schema
87+
? mergeSchemas({
88+
schemas: [options.schema, executableSchema],
89+
})
90+
: executableSchema;
91+
92+
return schema;
93+
}
94+
95+
private getResolvers(optionResolvers) {
96+
optionResolvers = Array.isArray(optionResolvers)
97+
? optionResolvers
98+
: [optionResolvers];
99+
return this.extendResolvers([
100+
this.resolversExplorerService.explore(),
101+
this.delegatesExplorerService.explore(),
102+
...this.scalarsExplorerService.explore(),
103+
...optionResolvers,
104+
]);
105+
}
106+
107+
private extendResolvers(resolvers: any[]) {
108+
return resolvers.reduce((prev, curr) => extend(prev, curr), {});
109+
}
110+
}

0 commit comments

Comments
 (0)