Skip to content

Commit

Permalink
feat(): add support for plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Mar 5, 2020
1 parent ced8f43 commit beae27a
Show file tree
Hide file tree
Showing 15 changed files with 125 additions and 25 deletions.
1 change: 1 addition & 0 deletions lib/decorators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './interface-type.decorator';
export * from './mutation.decorator';
export * from './object-type.decorator';
export * from './parent.decorator';
export * from './plugin.decorator';
export * from './query.decorator';
export * from './resolve-field.decorator';
export * from './resolve-property.decorator';
Expand Down
11 changes: 11 additions & 0 deletions 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 '../graphql.constants';

/**
* Decorator that marks a class as a Apollo plugin.
*/
export function Plugin(): ClassDecorator {
return (target: Function) => {
SetMetadata(PLUGIN_METADATA, true)(target);
};
}
14 changes: 12 additions & 2 deletions lib/federation/graphql-federation.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import { mergeSchemas } from 'graphql-tools';
import { isEmpty } from 'lodash';
import { GraphQLSchemaBuilder } from '../graphql-schema-builder';
import { GqlModuleOptions } from '../interfaces';
import { ResolversExplorerService, ScalarsExplorerService } from '../services';
import {
PluginsExplorerService,
ResolversExplorerService,
ScalarsExplorerService,
} from '../services';
import { extend } from '../utils';

@Injectable()
export class GraphQLFederationFactory {
constructor(
private readonly resolversExplorerService: ResolversExplorerService,
private readonly scalarsExplorerService: ScalarsExplorerService,
private readonly pluginsExplorerService: PluginsExplorerService,
private readonly gqlSchemaBuilder: GraphQLSchemaBuilder,
) {}

Expand All @@ -23,6 +28,11 @@ export class GraphQLFederationFactory {
const transformSchema = async schema =>
options.transformSchema ? options.transformSchema(schema) : schema;

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

let schema: GraphQLSchema;
if (options.autoSchemaFile) {
schema = await this.generateSchema(options);
Expand Down Expand Up @@ -68,7 +78,7 @@ export class GraphQLFederationFactory {

const autoGeneratedSchema: GraphQLSchema = await this.gqlSchemaBuilder.buildFederatedSchema(
options.autoSchemaFile,
options.buildSchemaOptions,
options,
this.resolversExplorerService.getAllCtors(),
);
const executableSchema = buildFederatedSchema({
Expand Down
7 changes: 6 additions & 1 deletion lib/federation/graphql-federation.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import {
GqlOptionsFactory,
} from '../interfaces';
import { GraphQLSchemaBuilderModule } from '../schema-builder/schema-builder.module';
import { ResolversExplorerService, ScalarsExplorerService } from '../services';
import {
PluginsExplorerService,
ResolversExplorerService,
ScalarsExplorerService,
} from '../services';
import { generateString, mergeDefaults, normalizeRoutePath } from '../utils';
import { GraphQLFederationFactory } from './graphql-federation.factory';

Expand All @@ -35,6 +39,7 @@ import { GraphQLFederationFactory } from './graphql-federation.factory';
GraphQLFactory,
MetadataScanner,
ResolversExplorerService,
PluginsExplorerService,
ScalarsExplorerService,
GraphQLAstExplorer,
GraphQLTypesLoader,
Expand Down
15 changes: 10 additions & 5 deletions lib/graphql-schema-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { isString } from '@nestjs/common/utils/shared.utils';
import { GraphQLSchema, printSchema, specifiedDirectives } from 'graphql';
import { resolve } from 'path';
import { GRAPHQL_SDL_FILE_HEADER } from './graphql.constants';
import { GqlModuleOptions } from './interfaces';
import { BuildSchemaOptions } from './interfaces/build-schema-options.interface';
import { GraphQLSchemaFactory } from './schema-builder/graphql-schema.factory';
import { FileSystemHelper } from './schema-builder/helpers/file-system.helper';
Expand All @@ -19,14 +20,16 @@ export class GraphQLSchemaBuilder {

async build(
autoSchemaFile: string | boolean,
options: BuildSchemaOptions = {},
options: GqlModuleOptions,
resolvers: Function[],
): Promise<any> {
const scalarsMap = this.scalarsExplorerService.getScalarsMap();
try {
const buildSchemaOptions = options.buildSchemaOptions || {};
return await this.buildSchema(resolvers, autoSchemaFile, {
...options,
...buildSchemaOptions,
scalarsMap,
schemaDirectives: options.schemaDirectives,
});
} catch (err) {
if (err && err.details) {
Expand All @@ -38,19 +41,21 @@ export class GraphQLSchemaBuilder {

async buildFederatedSchema(
autoSchemaFile: string | boolean,
options: BuildSchemaOptions = {},
options: GqlModuleOptions,
resolvers: Function[],
) {
const scalarsMap = this.scalarsExplorerService.getScalarsMap();
try {
const buildSchemaOptions = options.buildSchemaOptions || {};
return await this.buildSchema(resolvers, autoSchemaFile, {
...options,
...buildSchemaOptions,
directives: [
...specifiedDirectives,
...this.loadFederationDirectives(),
...((options && options.directives) || []),
...((buildSchemaOptions && buildSchemaOptions.directives) || []),
],
scalarsMap,
schemaDirectives: options.schemaDirectives,
skipCheck: true,
});
} catch (err) {
Expand Down
1 change: 1 addition & 0 deletions lib/graphql.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const RESOLVER_PROPERTY_METADATA = 'graphql:resolve_property';
export const RESOLVER_DELEGATE_METADATA = 'graphql:delegate_property';
export const SCALAR_NAME_METADATA = 'graphql:scalar_name';
export const SCALAR_TYPE_METADATA = 'graphql:scalar_type';
export const PLUGIN_METADATA = 'graphql:plugin';
export const PARAM_ARGS_METADATA = '__routeArguments__';
export const SUBSCRIPTION_OPTIONS_METADATA = 'graphql:subscription_options;';

Expand Down
14 changes: 12 additions & 2 deletions lib/graphql.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ import { forEach, isEmpty } from 'lodash';
import { GraphQLAstExplorer } from './graphql-ast.explorer';
import { GraphQLSchemaBuilder } from './graphql-schema-builder';
import { GqlModuleOptions } from './interfaces';
import { ResolversExplorerService, ScalarsExplorerService } from './services';
import {
PluginsExplorerService,
ResolversExplorerService,
ScalarsExplorerService,
} from './services';
import { extend, removeTempField } from './utils';

@Injectable()
export class GraphQLFactory {
constructor(
private readonly resolversExplorerService: ResolversExplorerService,
private readonly scalarsExplorerService: ScalarsExplorerService,
private readonly pluginsExplorerService: PluginsExplorerService,
private readonly graphqlAstExplorer: GraphQLAstExplorer,
private readonly gqlSchemaBuilder: GraphQLSchemaBuilder,
) {}
Expand All @@ -32,13 +37,18 @@ export class GraphQLFactory {
this.scalarsExplorerService.explore(),
resolvers,
);
options.plugins = extend(
options.plugins || [],
this.pluginsExplorerService.explore(),
);

const transformSchema = async (schema: GraphQLSchema) =>
options.transformSchema ? await options.transformSchema(schema) : schema;

if (options.autoSchemaFile) {
const autoGeneratedSchema: GraphQLSchema = await this.gqlSchemaBuilder.build(
options.autoSchemaFile,
options.buildSchemaOptions,
options,
this.resolversExplorerService.getAllCtors(),
);
const executableSchema = makeExecutableSchema({
Expand Down
7 changes: 6 additions & 1 deletion lib/graphql.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import {
GqlOptionsFactory,
} from './interfaces/gql-module-options.interface';
import { GraphQLSchemaBuilderModule } from './schema-builder/schema-builder.module';
import { ResolversExplorerService, ScalarsExplorerService } from './services';
import {
PluginsExplorerService,
ResolversExplorerService,
ScalarsExplorerService,
} from './services';
import {
extend,
generateString,
Expand All @@ -35,6 +39,7 @@ import {
MetadataScanner,
ResolversExplorerService,
ScalarsExplorerService,
PluginsExplorerService,
GraphQLAstExplorer,
GraphQLTypesLoader,
GraphQLSchemaBuilder,
Expand Down
5 changes: 5 additions & 0 deletions lib/interfaces/build-schema-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,9 @@ export interface BuildSchemaOptions {
* GraphQL directives
*/
directives?: GraphQLDirective[];

/**
* GraphQL schema directives mapping
*/
schemaDirectives?: Record<string, any>;
}
8 changes: 8 additions & 0 deletions lib/schema-builder/graphql-schema.factory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { getIntrospectionQuery, graphql, GraphQLSchema } from 'graphql';
import { SchemaDirectiveVisitor } from 'graphql-tools';
import { BuildSchemaOptions } from '../interfaces';
import { SchemaGenerationError } from './errors/schema-generation.error';
import { MutationTypeFactory } from './factories/mutation-type.factory';
Expand Down Expand Up @@ -45,6 +46,13 @@ export class GraphQLSchemaFactory {
throw new SchemaGenerationError(errors);
}
}
if (options.schemaDirectives) {
SchemaDirectiveVisitor.visitSchemaDirectives(
schema,
options.schemaDirectives,
);
}

return schema;
}
}
1 change: 1 addition & 0 deletions lib/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './base-explorer.service';
export * from './gql-arguments-host';
export * from './gql-execution-context';
export * from './plugins-explorer.service';
export * from './resolvers-explorer.service';
export * from './scalars-explorer.service';
34 changes: 34 additions & 0 deletions lib/services/plugins-explorer.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Inject, Injectable } from '@nestjs/common';
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
import { ModulesContainer } from '@nestjs/core/injector/modules-container';
import { GRAPHQL_MODULE_OPTIONS, PLUGIN_METADATA } from '../graphql.constants';
import { GqlModuleOptions } from '../interfaces/gql-module-options.interface';
import { BaseExplorerService } from './base-explorer.service';

@Injectable()
export class PluginsExplorerService extends BaseExplorerService {
constructor(
private readonly modulesContainer: ModulesContainer,
@Inject(GRAPHQL_MODULE_OPTIONS)
private readonly gqlOptions: GqlModuleOptions,
) {
super();
}

explore() {
const modules = this.getModules(
this.modulesContainer,
this.gqlOptions.include || [],
);
return this.flatMap<any>(modules, instance => this.filterPlugins(instance));
}

filterPlugins<T = any>(wrapper: InstanceWrapper<T>) {
const { instance } = wrapper;
if (!instance) {
return undefined;
}
const metadata = Reflect.getMetadata(PLUGIN_METADATA, instance.constructor);
return metadata ? instance : undefined;
}
}
24 changes: 14 additions & 10 deletions lib/services/scalars-explorer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export class ScalarsExplorerService extends BaseExplorerService {
);
}

filterImplicitScalar<T extends any = any>(wrapper: InstanceWrapper<T>) {
filterImplicitScalar<T extends Record<string, Function | string> = any>(
wrapper: InstanceWrapper<T>,
) {
const { instance } = wrapper;
if (!instance) {
return undefined;
Expand All @@ -47,10 +49,10 @@ export class ScalarsExplorerService extends BaseExplorerService {
? {
[(metadata as any) as string]: new GraphQLScalarType({
name: (metadata as any) as string,
description: instance['description'],
parseValue: bindContext(instance.parseValue),
serialize: bindContext(instance.serialize),
parseLiteral: bindContext(instance.parseLiteral),
description: instance['description'] as string,
parseValue: bindContext(instance.parseValue as Function),
serialize: bindContext(instance.serialize as Function),
parseLiteral: bindContext(instance.parseLiteral as Function),
}),
}
: undefined;
Expand All @@ -66,7 +68,9 @@ export class ScalarsExplorerService extends BaseExplorerService {
);
}

filterExplicitScalar<T extends any = any>(wrapper: InstanceWrapper<T>) {
filterExplicitScalar<T extends Record<string, Function | string> = any>(
wrapper: InstanceWrapper<T>,
) {
const { instance } = wrapper;
if (!instance) {
return undefined;
Expand All @@ -89,10 +93,10 @@ export class ScalarsExplorerService extends BaseExplorerService {
instance.constructor,
scalar: new GraphQLScalarType({
name: scalarNameMetadata,
description: instance['description'],
parseValue: bindContext(instance.parseValue),
serialize: bindContext(instance.serialize),
parseLiteral: bindContext(instance.parseLiteral),
description: instance['description'] as string,
parseValue: bindContext(instance.parseValue as Function),
serialize: bindContext(instance.serialize as Function),
parseLiteral: bindContext(instance.parseLiteral as Function),
}),
}
: undefined;
Expand Down
6 changes: 3 additions & 3 deletions lib/utils/extend.util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defaultTo, isArray, isString } from 'lodash';

export function extend(obj1: any, obj2: any) {
export function extend(obj1: unknown, obj2: unknown) {
if (isString(obj1)) {
return isString(obj2)
? [defaultTo(obj1, ''), defaultTo(obj2, '')]
Expand All @@ -10,7 +10,7 @@ export function extend(obj1: any, obj2: any) {
return defaultTo(obj1, []).concat(defaultTo(obj2, []));
}
return {
...(obj1 || {}),
...(obj2 || {}),
...((obj1 as object) || {}),
...((obj2 as object) || {}),
};
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestjs/graphql",
"version": "7.0.0-next.2",
"version": "7.0.0-next.6",
"description": "Nest - modern, fast, powerful node.js web framework (@graphql)",
"author": "Kamil Mysliwiec",
"license": "MIT",
Expand Down

0 comments on commit beae27a

Please sign in to comment.