Skip to content

Commit

Permalink
feat(federation): gateway module
Browse files Browse the repository at this point in the history
  • Loading branch information
Davide-Gheri committed Feb 17, 2021
1 parent 5df7974 commit 5c835f0
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 109 deletions.
86 changes: 86 additions & 0 deletions lib/base-mercurius.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { MercuriusModuleOptions } from './interfaces';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { FastifyInstance } from 'fastify';
import { MercuriusOptions } from 'mercurius';
import { normalizeRoutePath } from '@nestjs/graphql/dist/utils';
import { ApplicationConfig, HttpAdapterHost } from '@nestjs/core';
import { BaseMercuriusModuleOptions } from './interfaces/base-mercurius-module-options.interface';

export abstract class BaseMercuriusModule<
Opts extends BaseMercuriusModuleOptions
> {
constructor(
protected readonly httpAdapterHost: HttpAdapterHost,
protected readonly applicationConfig: ApplicationConfig,
protected readonly options: Opts,
) {}

protected async registerGqlServer(mercuriusOptions: Opts) {
const httpAdapter = this.httpAdapterHost.httpAdapter;
const platformName = httpAdapter.getType();

if (platformName === 'fastify') {
await this.registerFastify(mercuriusOptions);
} else {
throw new Error(`No support for current HttpAdapter: ${platformName}`);
}
}

protected async registerFastify(mercuriusOptions: Opts) {
const mercurius = loadPackage('mercurius', 'MercuriusModule', () =>
require('mercurius'),
);

const httpAdapter = this.httpAdapterHost.httpAdapter;
const app: FastifyInstance = httpAdapter.getInstance();

const options = {
...mercuriusOptions,
path: this.getNormalizedPath(mercuriusOptions),
};

if (mercuriusOptions.uploads) {
const mercuriusUpload = loadPackage(
'mercurius-upload',
'MercuriusModule',
() => require('mercurius-upload'),
);
await app.register(
mercuriusUpload,
typeof mercuriusOptions.uploads !== 'boolean'
? mercuriusOptions.uploads
: undefined,
);
}

if (mercuriusOptions.altair) {
const altairPlugin = loadPackage(
'altair-fastify-plugin',
'MercuriusModule',
() => require('altair-fastify-plugin'),
);

options.graphiql = false;
options.ide = false;

await app.register(altairPlugin, {
baseURL: '/altair/',
path: '/altair',
...(typeof mercuriusOptions.altair !== 'boolean' &&
mercuriusOptions.altair),
endpointURL: options.path,
});
}

await app.register(mercurius, options);
}

protected getNormalizedPath(mercuriusOptions: Opts): string {
const prefix = this.applicationConfig.getGlobalPrefix();
const useGlobalPrefix = prefix && this.options.useGlobalPrefix;
const gqlOptionsPath = normalizeRoutePath(mercuriusOptions.path);
return useGlobalPrefix
? normalizeRoutePath(prefix) + gqlOptionsPath
: gqlOptionsPath;
}
}
3 changes: 2 additions & 1 deletion lib/factories/graphql.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
ScalarsExplorerService,
} from '@nestjs/graphql/dist/services';
import { GraphQLSchemaBuilder } from '@nestjs/graphql/dist/graphql-schema.builder';
import { MercuriusModuleOptions, ValidationRules } from '../interfaces';
import { MercuriusModuleOptions } from '../interfaces';
import { ValidationRules } from '../interfaces/base-mercurius-module-options.interface';
import {
LoadersExplorerService,
ValidationRuleExplorerService,
Expand Down
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './mercurius.module';
export * from './mercurius-gateway.module';
export * from './constants';
export * from './interfaces';
export * from './decorators';
Expand Down
25 changes: 25 additions & 0 deletions lib/interfaces/base-mercurius-module-options.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MercuriusCommonOptions } from 'mercurius';
import { ValidationRule } from 'graphql';

export interface BaseMercuriusModuleOptions extends MercuriusCommonOptions {
path?: string;
useGlobalPrefix?: boolean;
uploads?: boolean | FileUploadOptions;
validationRules?: ValidationRules;
altair?: boolean | import('altair-fastify-plugin').AltairFastifyPluginOptions;
}

export interface FileUploadOptions {
//Max allowed non-file multipart form field size in bytes; enough for your queries (default: 1 MB).
maxFieldSize?: number;
//Max allowed file size in bytes (default: Infinity).
maxFileSize?: number;
//Max allowed number of files (default: Infinity).
maxFiles?: number;
}

export type ValidationRules = (params: {
source: string;
variables?: Record<string, any>;
operationName?: string;
}) => ValidationRule[];
1 change: 1 addition & 0 deletions lib/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './mercurius-module-options.interface';
export * from './mercurius-gateway-module-options.interface';
export * from './loader.interface';
export * from './validation-rule-host';
export * from './reference.interface';
23 changes: 23 additions & 0 deletions lib/interfaces/mercurius-gateway-module-options.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { BaseMercuriusModuleOptions } from './base-mercurius-module-options.interface';
import { MercuriusGatewayOptions } from 'mercurius';
import { ModuleMetadata, Type } from '@nestjs/common';

export interface MercuriusGatewayModuleOptions
extends BaseMercuriusModuleOptions,
MercuriusGatewayOptions {}

export interface MercuriusGatewayOptionsFactory {
createMercuriusGatewayOptions():
| Promise<MercuriusGatewayModuleOptions>
| MercuriusGatewayModuleOptions;
}

export interface MercuriusGatewayModuleAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
useExisting?: Type<MercuriusGatewayOptionsFactory>;
useClass?: Type<MercuriusGatewayOptionsFactory>;
useFactory?: (
...args: any[]
) => Promise<MercuriusGatewayModuleOptions> | MercuriusGatewayModuleOptions;
inject?: any[];
}
28 changes: 4 additions & 24 deletions lib/interfaces/mercurius-module-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import {
Enhancer,
} from '@nestjs/graphql';
import { ModuleMetadata, Type } from '@nestjs/common';
import { GraphQLSchema, ValidationRule } from 'graphql';
import { GraphQLSchema } from 'graphql';
import { IResolverValidationOptions } from '@nestjs/graphql/dist/interfaces/gql-module-options.interface';
import { MercuriusCommonOptions, MercuriusSchemaOptions } from 'mercurius';
import { MercuriusSchemaOptions } from 'mercurius';
import { BaseMercuriusModuleOptions } from './base-mercurius-module-options.interface';

export interface MercuriusModuleOptions
extends Omit<MercuriusSchemaOptions, 'schema'>,
MercuriusCommonOptions {
BaseMercuriusModuleOptions {
schema?: GraphQLSchema | string;
path?: string;
typeDefs?: string | string[];
Expand All @@ -28,15 +29,9 @@ export interface MercuriusModuleOptions
} & DefinitionsGeneratorOptions;
autoSchemaFile?: boolean | string;
buildSchemaOptions?: BuildSchemaOptions;
useGlobalPrefix?: boolean;
transformAutoSchemaFile?: boolean;
sortSchema?: boolean;
fieldResolverEnhancers?: Enhancer[];

validationRules?: ValidationRules;

uploads?: boolean | FileUploadOptions;
altair?: boolean | import('altair-fastify-plugin').AltairFastifyPluginOptions;
}

export interface MercuriusOptionsFactory {
Expand All @@ -54,18 +49,3 @@ export interface MercuriusModuleAsyncOptions
) => Promise<MercuriusModuleOptions> | MercuriusModuleOptions;
inject?: any[];
}

export interface FileUploadOptions {
//Max allowed non-file multipart form field size in bytes; enough for your queries (default: 1 MB).
maxFieldSize?: number;
//Max allowed file size in bytes (default: Infinity).
maxFileSize?: number;
//Max allowed number of files (default: Infinity).
maxFiles?: number;
}

export type ValidationRules = (params: {
source: string;
variables?: Record<string, any>;
operationName?: string;
}) => ValidationRule[];
102 changes: 102 additions & 0 deletions lib/mercurius-gateway.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
DynamicModule,
Inject,
Module,
OnModuleInit,
Provider,
} from '@nestjs/common';
import { GRAPHQL_GATEWAY_MODULE_OPTIONS } from '@nestjs/graphql/dist/federation/federation.constants';
import { GRAPHQL_MODULE_ID } from '@nestjs/graphql/dist/graphql.constants';
import { generateString } from '@nestjs/graphql/dist/utils';
import { HttpAdapterHost, ApplicationConfig } from '@nestjs/core';
import { BaseMercuriusModule } from './base-mercurius.module';
import {
MercuriusGatewayModuleAsyncOptions,
MercuriusGatewayModuleOptions,
} from './interfaces';

@Module({})
export class MercuriusGatewayModule
extends BaseMercuriusModule<MercuriusGatewayModuleOptions>
implements OnModuleInit {
constructor(
protected readonly httpAdapterHost: HttpAdapterHost,
protected readonly applicationConfig: ApplicationConfig,
@Inject(GRAPHQL_GATEWAY_MODULE_OPTIONS)
protected readonly options: MercuriusGatewayModuleOptions,
) {
super(httpAdapterHost, applicationConfig, options);
}

static forRoot(options: MercuriusGatewayModuleOptions): DynamicModule {
return {
module: MercuriusGatewayModule,
providers: [
{
provide: GRAPHQL_GATEWAY_MODULE_OPTIONS,
useValue: options,
},
],
};
}

static forRootAsync(
options: MercuriusGatewayModuleAsyncOptions,
): DynamicModule {
return {
module: MercuriusGatewayModule,
imports: options.imports,
providers: [
...this.createAsyncProviders(options),
{
provide: GRAPHQL_MODULE_ID,
useValue: generateString(),
},
],
};
}

private static createAsyncProviders(
options: MercuriusGatewayModuleAsyncOptions,
): Provider[] {
if (options.useExisting || options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
}

return [
this.createAsyncOptionsProvider(options),
{
provide: options.useClass,
useClass: options.useClass,
},
];
}

private static createAsyncOptionsProvider(
options: MercuriusGatewayModuleAsyncOptions,
): Provider {
if (options.useFactory) {
return {
provide: GRAPHQL_GATEWAY_MODULE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
}

return {
provide: GRAPHQL_GATEWAY_MODULE_OPTIONS,
useFactory: (optionsFactory: any) =>
optionsFactory.createGatewayOptions(),
inject: [options.useExisting || options.useClass],
};
}

async onModuleInit() {
const { httpAdapter } = this.httpAdapterHost || {};
if (!httpAdapter) {
return;
}

await this.registerGqlServer(this.options);
}
}
Loading

0 comments on commit 5c835f0

Please sign in to comment.