Skip to content

Commit

Permalink
feat(): extract configurations
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Nov 9, 2021
1 parent 9eab0e9 commit 7e53ce2
Show file tree
Hide file tree
Showing 45 changed files with 712 additions and 217 deletions.
79 changes: 74 additions & 5 deletions packages/apollo/lib/adapters/apollo-federation-graphql.adapter.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,82 @@
import { SchemaDirectiveVisitor } from '@graphql-tools/utils';
import { Injectable } from '@nestjs/common';
import { GqlModuleOptions } from '@nestjs/graphql-experimental/interfaces';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { ModulesContainer } from '@nestjs/core';
import { GraphQLSchemaBuilder } from '@nestjs/graphql-experimental/graphql-schema.builder';
import { GraphQLSchemaHost } from '@nestjs/graphql-experimental/graphql-schema.host';
import {
ResolversExplorerService,
ScalarsExplorerService,
} from '@nestjs/graphql-experimental/services';
import { extend } from '@nestjs/graphql-experimental/utils/extend.util';
import { ApolloAdapterOptions } from '..';
import { GraphQLFederationFactory } from '../factories/graphql-federation.factory';
import { PluginsExplorerService } from '../services/plugins-explorer.service';
import { ApolloGraphQLBaseAdapter } from './apollo-graphql-base.adapter';

@Injectable()
export class ApolloFederationGraphQLAdapter extends ApolloGraphQLBaseAdapter {
public async runPreOptionsHooks(apolloOptions: GqlModuleOptions) {
private readonly graphqlFederationFactory: GraphQLFederationFactory;

constructor(
resolversExplorerService: ResolversExplorerService,
scalarsExplorerService: ScalarsExplorerService,
gqlSchemaBuilder: GraphQLSchemaBuilder,
gqlSchemaHost: GraphQLSchemaHost,
modulesContainer: ModulesContainer,
) {
super();
const pluginsExplorerService = new PluginsExplorerService(modulesContainer);
this.graphqlFederationFactory = new GraphQLFederationFactory(
resolversExplorerService,
scalarsExplorerService,
pluginsExplorerService,
gqlSchemaBuilder,
gqlSchemaHost,
);
}

public async start(options: ApolloAdapterOptions): Promise<void> {
const { printSubgraphSchema } = loadPackage(
'@apollo/subgraph',
'ApolloFederation',
() => require('@apollo/subgraph'),
);
options = await this.mergeDefaultOptions(options);

const { typePaths } = options;
const typeDefs =
(await this.graphQlTypesLoader.mergeTypesByPaths(typePaths)) || [];

const mergedTypeDefs = extend(typeDefs, options.typeDefs);
const adapterOptions = await this.graphqlFederationFactory.mergeOptions({
...options,
typeDefs: mergedTypeDefs,
});
await this.runPreOptionsHooks(adapterOptions);

if (options.definitions && options.definitions.path) {
await this.graphQlFactory.generateDefinitions(
printSubgraphSchema(adapterOptions.schema),
options,
);
}

await super.start(adapterOptions);

if (options.installSubscriptionHandlers || options.subscriptions) {
// TL;DR <https://github.com/apollographql/apollo-server/issues/2776>
throw new Error(
'No support for subscriptions yet when using Apollo Federation',
);
}
}

public async runPreOptionsHooks(apolloOptions: ApolloAdapterOptions) {
await this.runExecutorFactoryIfPresent(apolloOptions);
}

protected async registerExpress(apolloOptions: GqlModuleOptions) {
protected async registerExpress(apolloOptions: ApolloAdapterOptions) {
return super.registerExpress(apolloOptions, {
preStartHook: () => {
// If custom directives are provided merge them into schema per Apollo
Expand All @@ -24,7 +91,7 @@ export class ApolloFederationGraphQLAdapter extends ApolloGraphQLBaseAdapter {
});
}

protected async registerFastify(apolloOptions: GqlModuleOptions) {
protected async registerFastify(apolloOptions: ApolloAdapterOptions) {
return super.registerFastify(apolloOptions, {
preStartHook: () => {
// If custom directives are provided merge them into schema per Apollo
Expand All @@ -39,7 +106,9 @@ export class ApolloFederationGraphQLAdapter extends ApolloGraphQLBaseAdapter {
});
}

private async runExecutorFactoryIfPresent(apolloOptions: GqlModuleOptions) {
private async runExecutorFactoryIfPresent(
apolloOptions: ApolloAdapterOptions,
) {
if (!apolloOptions.executorFactory) {
return;
}
Expand Down
26 changes: 15 additions & 11 deletions packages/apollo/lib/adapters/apollo-gateway-graphql.adapter.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { Injectable } from '@nestjs/common';
import { GqlModuleOptions } from '@nestjs/graphql-experimental/interfaces';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { ApolloGatewayAdapterOptions } from '..';
import { ApolloGraphQLBaseAdapter } from './apollo-graphql-base.adapter';

@Injectable()
export class ApolloGatewayGraphQLAdapter extends ApolloGraphQLBaseAdapter {
public async runPreOptionsHooks(apolloOptions: GqlModuleOptions) {
await this.runExecutorFactoryIfPresent(apolloOptions);
}
export class ApolloGatewayGraphQLAdapter extends ApolloGraphQLBaseAdapter<ApolloGatewayAdapterOptions> {
public async start(options: ApolloGatewayAdapterOptions): Promise<void> {
const { ApolloGateway } = loadPackage(
'@apollo/gateway',
'ApolloGateway',
() => require('@apollo/gateway'),
);
const { server: serverOpts = {}, gateway: gatewayOpts = {} } = options;
const gateway = new ApolloGateway(gatewayOpts);

private async runExecutorFactoryIfPresent(apolloOptions: GqlModuleOptions) {
if (!apolloOptions.executorFactory) {
return;
}
const executor = await apolloOptions.executorFactory(apolloOptions.schema);
apolloOptions.executor = executor;
await super.start({
...serverOpts,
gateway,
});
}
}
34 changes: 17 additions & 17 deletions packages/apollo/lib/adapters/apollo-graphql-base.adapter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { HttpStatus } from '@nestjs/common';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { GqlModuleOptions } from '@nestjs/graphql-experimental';
import { AbstractGraphQLAdapter } from '@nestjs/graphql-experimental/adapters/abstract-graphql.adapter';
import { normalizeRoutePath } from '@nestjs/graphql-experimental/utils';
import {
Expand All @@ -15,6 +14,7 @@ import {
} from 'apollo-server-core';
import { GraphQLError, GraphQLFormattedError } from 'graphql';
import { omit } from 'lodash';
import { ApolloAdapterOptions } from '../interfaces';

const apolloPredefinedExceptions: Partial<
Record<HttpStatus, typeof ApolloError | typeof UserInputError>
Expand All @@ -24,17 +24,16 @@ const apolloPredefinedExceptions: Partial<
[HttpStatus.FORBIDDEN]: ForbiddenError,
};

export abstract class ApolloGraphQLBaseAdapter extends AbstractGraphQLAdapter<
ApolloServerBase,
GqlModuleOptions
> {
export abstract class ApolloGraphQLBaseAdapter<
T extends Record<string, any> = ApolloAdapterOptions,
> extends AbstractGraphQLAdapter<ApolloServerBase, T> {
protected _apolloServer: ApolloServerBase;

get instance(): ApolloServerBase {
return this._apolloServer;
}

public async start(apolloOptions: GqlModuleOptions) {
public async start(apolloOptions: T) {
const httpAdapter = this.httpAdapterHost.httpAdapter;
const platformName = httpAdapter.getType();

Expand All @@ -51,10 +50,8 @@ export abstract class ApolloGraphQLBaseAdapter extends AbstractGraphQLAdapter<
return this._apolloServer?.stop();
}

public async mergeDefaultOptions(
options: GqlModuleOptions,
): Promise<GqlModuleOptions> {
let defaults: GqlModuleOptions = {
public async mergeDefaultOptions(options: T): Promise<T> {
let defaults: ApolloAdapterOptions = {
path: '/graphql',
fieldResolverEnhancers: [],
stopOnTerminationSignals: false,
Expand Down Expand Up @@ -91,14 +88,16 @@ export abstract class ApolloGraphQLBaseAdapter extends AbstractGraphQLAdapter<
omit(defaults, 'plugins'),
);

options.plugins = (options.plugins || []).concat(defaults.plugins || []);
(options as ApolloAdapterOptions).plugins = (options.plugins || []).concat(
defaults.plugins || [],
);

this.wrapFormatErrorFn(options);
return options;
}

protected async registerExpress(
apolloOptions: GqlModuleOptions,
apolloOptions: T,
{ preStartHook }: { preStartHook?: () => void } = {},
) {
const { ApolloServer } = loadPackage(
Expand Down Expand Up @@ -131,7 +130,7 @@ export abstract class ApolloGraphQLBaseAdapter extends AbstractGraphQLAdapter<
}

protected async registerFastify(
apolloOptions: GqlModuleOptions,
apolloOptions: T,
{ preStartHook }: { preStartHook?: () => void } = {},
) {
const { ApolloServer } = loadPackage(
Expand Down Expand Up @@ -164,7 +163,7 @@ export abstract class ApolloGraphQLBaseAdapter extends AbstractGraphQLAdapter<
this._apolloServer = apolloServer;
}

private getNormalizedPath(apolloOptions: GqlModuleOptions): string {
private getNormalizedPath(apolloOptions: T): string {
const prefix = this.applicationConfig.getGlobalPrefix();
const useGlobalPrefix = prefix && apolloOptions.useGlobalPrefix;
const gqlOptionsPath = normalizeRoutePath(apolloOptions.path);
Expand All @@ -173,19 +172,20 @@ export abstract class ApolloGraphQLBaseAdapter extends AbstractGraphQLAdapter<
: gqlOptionsPath;
}

private wrapFormatErrorFn(options: GqlModuleOptions) {
private wrapFormatErrorFn(options: T) {
if (options.autoTransformHttpErrors === false) {
return;
}
if (options.formatError) {
const origFormatError = options.formatError;
const transformHttpErrorFn = this.createTransformHttpErrorFn();
options.formatError = (err) => {
(options as ApolloAdapterOptions).formatError = (err) => {
err = transformHttpErrorFn(err) as GraphQLError;
return origFormatError(err);
};
} else {
options.formatError = this.createTransformHttpErrorFn();
(options as ApolloAdapterOptions).formatError =
this.createTransformHttpErrorFn();
}
}

Expand Down
69 changes: 67 additions & 2 deletions packages/apollo/lib/adapters/apollo-graphql.adapter.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,70 @@
import { Injectable } from '@nestjs/common';
import { GqlModuleOptions } from '@nestjs/graphql-experimental';
import { ModulesContainer } from '@nestjs/core';
import {
GqlSubscriptionService,
SubscriptionConfig,
} from '@nestjs/graphql-experimental';
import { extend } from '@nestjs/graphql-experimental/utils';
import { printSchema } from 'graphql';
import { ApolloAdapterOptions } from '../interfaces';
import { PluginsExplorerService } from '../services/plugins-explorer.service';
import { ApolloGraphQLBaseAdapter } from './apollo-graphql-base.adapter';

@Injectable()
export class ApolloGraphQLAdapter extends ApolloGraphQLBaseAdapter {
public async start(apolloOptions: GqlModuleOptions) {
private _subscriptionService?: GqlSubscriptionService;
private readonly pluginsExplorerService: PluginsExplorerService;

constructor(modulesContainer: ModulesContainer) {
super();
this.pluginsExplorerService = new PluginsExplorerService(modulesContainer);
}

public async start(apolloOptions: ApolloAdapterOptions) {
const options = await this.mergeDefaultOptions(apolloOptions);
const typeDefs =
(await this.graphQlTypesLoader.mergeTypesByPaths(options.typePaths)) ||
[];

const mergedTypeDefs = extend(typeDefs, options.typeDefs);

options.plugins = extend(
options.plugins || [],
this.pluginsExplorerService.explore(options),
);

const adapterOptions =
await this.graphQlFactory.mergeOptions<ApolloAdapterOptions>({
...options,
typeDefs: mergedTypeDefs,
});
await this.runPreOptionsHooks(adapterOptions);

if (options.definitions && options.definitions.path) {
await this.graphQlFactory.generateDefinitions(
printSchema(adapterOptions.schema),
options,
);
}

await this.registerServer(adapterOptions);

if (options.installSubscriptionHandlers || options.subscriptions) {
const subscriptionsOptions: SubscriptionConfig =
options.subscriptions || { 'subscriptions-transport-ws': {} };
this._subscriptionService = new GqlSubscriptionService(
{
schema: adapterOptions.schema,
path: options.path,
context: options.context,
...subscriptionsOptions,
},
this.httpAdapterHost.httpAdapter?.getHttpServer(),
);
}
}

public async registerServer(apolloOptions: ApolloAdapterOptions) {
const httpAdapter = this.httpAdapterHost.httpAdapter;
const platformName = httpAdapter.getType();

Expand All @@ -16,4 +76,9 @@ export class ApolloGraphQLAdapter extends ApolloGraphQLBaseAdapter {
throw new Error(`No support for current HttpAdapter: ${platformName}`);
}
}

public async stop() {
await this._subscriptionService?.stop();
await super.stop();
}
}
1 change: 1 addition & 0 deletions packages/apollo/lib/apollo.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const PLUGIN_METADATA = 'graphql:plugin';
1 change: 1 addition & 0 deletions packages/apollo/lib/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './plugin.decorator';
11 changes: 11 additions & 0 deletions packages/apollo/lib/decorators/plugin.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SetMetadata } from '@nestjs/common';
import { PLUGIN_METADATA } from '../apollo.constants';

/**
* Decorator that marks a class as an Apollo plugin.
*/
export function Plugin(): ClassDecorator {
return (target: Function) => {
SetMetadata(PLUGIN_METADATA, true)(target);
};
}
Loading

0 comments on commit 7e53ce2

Please sign in to comment.