diff --git a/.changeset/nice-eels-press.md b/.changeset/nice-eels-press.md new file mode 100644 index 0000000000..214ccec26c --- /dev/null +++ b/.changeset/nice-eels-press.md @@ -0,0 +1,6 @@ +--- +'graphql-modules': minor +'website': patch +--- + +Enabled support for resolver extensions for compatibility with such libraries as grafast or graphql-query-complexity diff --git a/packages/graphql-modules/src/module/resolvers.ts b/packages/graphql-modules/src/module/resolvers.ts index f74bebcc99..c2d59faed7 100644 --- a/packages/graphql-modules/src/module/resolvers.ts +++ b/packages/graphql-modules/src/module/resolvers.ts @@ -92,6 +92,18 @@ export function createResolvers( resolvers[typeName][fieldName].resolve = resolver; } + if (isDefined((obj[fieldName] as any).extensions)) { + // some extensions allow to omit the resolve function, e.g. grafast + const defaultResolver = (val: any) => val; + const resolver = wrapResolver({ + config, + resolver: (obj[fieldName] as any).resolve || defaultResolver, + middlewareMap, + path, + }); + resolvers[typeName][fieldName].resolve = resolver; + } + // { subscribe } if (isDefined((obj[fieldName] as any).subscribe)) { const resolver = wrapResolver({ @@ -286,6 +298,21 @@ function addObject({ container[typeName][fieldName].resolve = resolver.resolve; } + // extensions + if (isDefined(resolver.extensions)) { + if (container[typeName][fieldName].extensions) { + throw new ResolverDuplicatedError( + `Duplicated resolver of "${typeName}.${fieldName}" (extensions method)`, + useLocation({ dirname: config.dirname, id: config.id }) + ); + } + + (resolver.extensions as any)[resolverMetadataProp] = { + moduleId: config.id, + } as ResolverMetadata; + container[typeName][fieldName].extensions = resolver.extensions; + } + // subscribe if (isDefined(resolver.subscribe)) { if (container[typeName][fieldName].subscribe) { @@ -501,10 +528,15 @@ function isResolveFn(value: any): value is ResolveFn { interface ResolveOptions { resolve?: ResolveFn; subscribe?: ResolveFn; + extensions?: Record; } function isResolveOptions(value: any): value is ResolveOptions { - return isDefined(value.resolve) || isDefined(value.subscribe); + return ( + isDefined(value.resolve) || + isDefined(value.subscribe) || + isDefined(value.extensions) + ); } function isScalarResolver(obj: any): obj is GraphQLScalarType { diff --git a/packages/graphql-modules/tests/bootstrap.spec.ts b/packages/graphql-modules/tests/bootstrap.spec.ts index 0673766edb..97411358cd 100644 --- a/packages/graphql-modules/tests/bootstrap.spec.ts +++ b/packages/graphql-modules/tests/bootstrap.spec.ts @@ -396,3 +396,34 @@ test('fail when modules have non-DocumentNode typeDefs', async () => { }); }).toThrow(NonDocumentNodeError); }); + +test('should allow resolver extensions', async () => { + const m1 = createModule({ + id: 'test', + typeDefs: gql` + type Query { + dummy: String! + } + `, + resolvers: { + Query: { + dummy: { + resolve: () => '1', + extensions: { + test: 'test', + }, + }, + }, + }, + }); + + const app = createApplication({ + modules: [m1], + }); + + const schema = app.schema; + expect( + Object.keys(schema.getQueryType()?.getFields().dummy.extensions || {}) + .length + ).toBe(1); +}); diff --git a/website/src/content/essentials/resolvers.mdx b/website/src/content/essentials/resolvers.mdx index 22fe7b5ae0..0601935e54 100644 --- a/website/src/content/essentials/resolvers.mdx +++ b/website/src/content/essentials/resolvers.mdx @@ -60,10 +60,7 @@ npm i @graphql-tools/load-files Next, use it to load your files dynamically: ```ts -import MyQueryType from './query.type.graphql' import { createModule } from 'graphql-modules' -import { loadFilesSync } from '@graphql-tools/load-files' -import { join } from 'path' export const myModule = createModule({ id: 'my-module', @@ -72,3 +69,40 @@ export const myModule = createModule({ resolvers: loadFilesSync(join(__dirname, './resolvers/*.ts')) }) ``` + +## Resolver Extensions + +You can use resolver extensions to extend the functionality of your resolvers to make your modules work with such extensions as [Grafast Plan Resolver](https://grafast.org/grafast/plan-resolvers#specifying-a-field-plan-resolver) or [GraphQL Query Complexity](https://github.com/slicknode/graphql-query-complexity/blob/HEAD/src/estimators/fieldExtensions/README.md). + +To use resolver extensions, you can use the `extensions` property in your resolvers. + +```ts +import { createModule, gql } from 'graphql-modules' +import { constant } from "grafast"; + +export const myModule = createModule({ + id: 'my-module', + dirname: __dirname, + typeDefs: [ + gql` + type Query { + meaningOfLife: Int! + } + ` + ], + resolvers: { + Query: { + meaningOfLife: { + extensions: { + grafast: { + plan() { + return constant(42); + }, + }, + }, + }, + } + } +}) +``` +