Skip to content

Commit

Permalink
Let extensions intercept schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilkisiela committed Mar 25, 2020
1 parent b0ee9da commit 9f6c8a0
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type GraphQLExtensionDeclaration = (
export interface ExtensionAPI {
logger: any;
loaders: {
schema: Pick<LoadersRegistry, 'register'>;
schema: Pick<LoadersRegistry, 'register' | 'use'>;
documents: Pick<LoadersRegistry, 'register'>;
};
}
Expand Down
12 changes: 12 additions & 0 deletions src/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,15 @@ export function isLegacyProjectConfig(
typeof (config as IGraphQLProjectLegacy).excludes !== 'undefined'
);
}

export type MiddlewareFn<T> = (input: T) => T;

export function useMiddleware<T>(fns: Array<MiddlewareFn<T>>) {
return (input: T) => {
if (fns.length) {
return fns.reduce((obj, cb) => cb(obj), input);
}

return input;
};
}
33 changes: 28 additions & 5 deletions src/loaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<LoadTypedefsOptions>;

export class LoadersRegistry {
private _loaders: Loader[] = [];
private _middlewares: MiddlewareFn<DocumentNode>[] = [];
private readonly cwd: string;

constructor({cwd}: {cwd: string}) {
Expand All @@ -28,6 +30,10 @@ export class LoadersRegistry {
}
}

use(middleware: MiddlewareFn<DocumentNode>): void {
this._middlewares.push(middleware);
}

async loadTypeDefs(pointer: Pointer, options?: Options): Promise<Source[]> {
return loadTypedefs(pointer, {
loaders: this._loaders,
Expand All @@ -52,11 +58,21 @@ export class LoadersRegistry {
pointer: Pointer,
options?: Options,
): Promise<GraphQLSchema> {
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<T extends object>(options?: T) {
Expand All @@ -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));
}
}
53 changes: 53 additions & 0 deletions test/loaders.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});

0 comments on commit 9f6c8a0

Please sign in to comment.