diff --git a/src/extension.ts b/src/extension.ts index 699197fb7..32dbbf992 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -10,7 +10,7 @@ export type GraphQLExtensionDeclaration = ( export interface ExtensionAPI { logger: any; loaders: { - schema: Pick; + schema: Pick; documents: Pick; }; } diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index 67dbe8375..74dfdb5df 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -30,3 +30,15 @@ export function isLegacyProjectConfig( typeof (config as IGraphQLProjectLegacy).excludes !== 'undefined' ); } + +export type MiddlewareFn = (input: T) => T; + +export function useMiddleware(fns: Array>) { + return (input: T) => { + if (fns.length) { + return fns.reduce((obj, cb) => cb(obj), input); + } + + return input; + }; +} diff --git a/src/loaders.ts b/src/loaders.ts index 3d7b80f1e..6bc82f0af 100644 --- a/src/loaders.ts +++ b/src/loaders.ts @@ -4,18 +4,20 @@ import { LoadTypedefsOptions, loadDocuments, loadDocumentsSync, - loadSchema, - loadSchemaSync, + OPERATION_KINDS, loadTypedefs, loadTypedefsSync, } from '@graphql-toolkit/core'; -import {GraphQLSchema} from 'graphql'; +import {mergeTypeDefs} from '@graphql-toolkit/schema-merging'; +import {GraphQLSchema, DocumentNode, buildASTSchema} from 'graphql'; +import {MiddlewareFn, useMiddleware} from './helpers/utils'; type Pointer = UnnormalizedTypeDefPointer | UnnormalizedTypeDefPointer[]; type Options = Partial; export class LoadersRegistry { private _loaders: Loader[] = []; + private _middlewares: MiddlewareFn[] = []; private readonly cwd: string; constructor({cwd}: {cwd: string}) { @@ -28,6 +30,10 @@ export class LoadersRegistry { } } + use(middleware: MiddlewareFn): void { + this._middlewares.push(middleware); + } + async loadTypeDefs(pointer: Pointer, options?: Options): Promise { return loadTypedefs(pointer, { loaders: this._loaders, @@ -52,11 +58,21 @@ export class LoadersRegistry { pointer: Pointer, options?: Options, ): Promise { - return loadSchema(pointer, this.createOptions(options)); + const sources = await loadTypedefs(pointer, { + filterKinds: OPERATION_KINDS, + ...this.createOptions(options), + }); + + return this.buildSchema(sources); } loadSchemaSync(pointer: Pointer, options?: Options): GraphQLSchema { - return loadSchemaSync(pointer, this.createOptions(options)); + const sources = loadTypedefsSync(pointer, { + filterKinds: OPERATION_KINDS, + ...this.createOptions(options), + }); + + return this.buildSchema(sources); } private createOptions(options?: T) { @@ -66,4 +82,11 @@ export class LoadersRegistry { ...options, }; } + + private buildSchema(sources: Source[]) { + const documents: DocumentNode[] = sources.map(source => source.document); + const document = mergeTypeDefs(documents); + + return buildASTSchema(useMiddleware(this._middlewares)(document)); + } } diff --git a/test/loaders.spec.ts b/test/loaders.spec.ts new file mode 100644 index 000000000..b3ed90604 --- /dev/null +++ b/test/loaders.spec.ts @@ -0,0 +1,53 @@ +import {parse, DirectiveDefinitionNode} from 'graphql'; + +jest.mock('@graphql-toolkit/core', () => { + return { + loadTypedefsSync() { + return [ + { + document: parse(/* GraphQL */ ` + type Query { + foo: String @cache + } + `), + }, + ]; + }, + }; +}); + +import {LoadersRegistry} from '../src/loaders'; + +test('Middlewares', () => { + const registry = new LoadersRegistry({cwd: __dirname}); + + expect(() => { + registry.loadSchemaSync('anything'); + }).toThrow(/cache/); + + const cacheDirective: DirectiveDefinitionNode = { + kind: 'DirectiveDefinition', + name: { + kind: 'Name', + value: 'cache', + }, + repeatable: false, + locations: [ + { + kind: 'Name', + value: 'FIELD_DEFINITION', + }, + ], + }; + + registry.use(doc => { + return { + ...doc, + definitions: [...doc.definitions, cacheDirective], + }; + }); + + const schema = registry.loadSchemaSync('anything'); + + expect(schema.getDirective('cache')).toBeDefined(); +});