Răsfoiți Sursa

feat(core): Allow lazy evaluation of APIExtensionDefinitions

This change allows you to define extensions to the GraphQL APIs in plugins which may rely on runtime execution, e.g. to dynamically build up the DocumentNode data based on some config options.
Michael Bromley 6 ani în urmă
părinte
comite
69dad0b664

+ 20 - 0
packages/core/e2e/fixtures/test-plugins.ts

@@ -96,6 +96,26 @@ export class TestShopPluginResolver {
 })
 export class TestAPIExtensionPlugin {}
 
+@Resolver()
+export class TestLazyResolver {
+    @Query()
+    lazy() {
+        return 'sleeping';
+    }
+}
+
+@VendurePlugin({
+    shopApiExtensions: {
+        resolvers: () => [TestLazyResolver],
+        schema: () => gql`
+            extend type Query {
+                lazy: String!
+            }
+        `,
+    },
+})
+export class TestLazyExtensionPlugin {}
+
 @Injectable()
 export class NameService {
     getNames(): string[] {

+ 11 - 0
packages/core/e2e/plugin.e2e-spec.ts

@@ -7,6 +7,7 @@ import { ConfigService } from '../src/config/config.service';
 import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
 import {
     TestAPIExtensionPlugin,
+    TestLazyExtensionPlugin,
     TestPluginWithAllLifecycleHooks,
     TestPluginWithConfigAndBootstrap,
     TestPluginWithProvider,
@@ -41,6 +42,7 @@ describe('Plugins', () => {
                     TestPluginWithConfigAndBootstrap.setup(bootstrapMockFn),
                     TestAPIExtensionPlugin,
                     TestPluginWithProvider,
+                    TestLazyExtensionPlugin,
                 ],
             },
         );
@@ -85,6 +87,15 @@ describe('Plugins', () => {
         expect(result.baz).toEqual(['quux']);
     });
 
+    it('allows lazy evaluation of API extension', async () => {
+        const result = await shopClient.query(gql`
+            query {
+                lazy
+            }
+        `);
+        expect(result.lazy).toEqual('sleeping');
+    });
+
     it('generates ListQueryOptions for api extensions', async () => {
         const result = await adminClient.query(gql`
             query {

+ 1 - 1
packages/core/src/api/config/configure-graphql-module.ts

@@ -164,7 +164,7 @@ async function createGraphQLOptions(
         let schema = buildSchema(typeDefs);
 
         getPluginAPIExtensions(configService.plugins, apiType)
-            .map(e => e.schema)
+            .map(e => (typeof e.schema === 'function' ? e.schema() : e.schema))
             .filter(notNullOrUndefined)
             .forEach(documentNode => (schema = extendSchema(schema, documentNode)));
         schema = generateListOptions(schema);

+ 6 - 2
packages/core/src/plugin/plugin-metadata.ts

@@ -69,12 +69,16 @@ export function graphQLResolversFor(
     plugin: Type<any> | DynamicModule,
     apiType: 'shop' | 'admin',
 ): Array<Type<any>> {
-    const apiExtensions =
+    const apiExtensions: APIExtensionDefinition =
         apiType === 'shop'
             ? reflectMetadata(plugin, PLUGIN_METADATA.SHOP_API_EXTENSIONS)
             : reflectMetadata(plugin, PLUGIN_METADATA.ADMIN_API_EXTENSIONS);
 
-    return apiExtensions ? apiExtensions.resolvers : [];
+    return apiExtensions
+        ? typeof apiExtensions.resolvers === 'function'
+            ? apiExtensions.resolvers()
+            : apiExtensions.resolvers
+        : [];
 }
 
 function reflectMetadata(metatype: Type<any> | DynamicModule, metadataKey: string) {

+ 2 - 2
packages/core/src/plugin/vendure-plugin.ts

@@ -70,13 +70,13 @@ export interface APIExtensionDefinition {
      * }`;
      * ```
      */
-    schema?: DocumentNode;
+    schema?: DocumentNode | (() => DocumentNode);
     /**
      * @description
      * An array of resolvers for the schema extensions. Should be defined as [Nestjs GraphQL resolver](https://docs.nestjs.com/graphql/resolvers-map)
      * classes, i.e. using the Nest `\@Resolver()` decorator etc.
      */
-    resolvers: Array<Type<any>>;
+    resolvers: Array<Type<any>> | (() => Array<Type<any>>);
 }
 
 /**