Browse Source

docs: Update documentation for new plugin architecture

Michael Bromley 6 years ago
parent
commit
9205554a06

+ 134 - 7
docs/content/docs/plugins/_index.md

@@ -1,7 +1,7 @@
 ---
 title: "Plugins"
 weight: 2
-showtoc: false
+showtoc: true
 ---
  
 # Plugins
@@ -21,10 +21,137 @@ This section details the official Vendure plugins included in the main Vendure r
 
 {{< figure src="plugin_architecture.png" >}}
 
-This diagram illustrates the how a plugin can integrate with and extend Vendure.
-
-1. A Plugin may define logic to be run by the Vendure Worker. This is suitable for long-running or resource-intensive tasks and is done by providing controllers via the [`VendurePlugin.defineWorkers()` method]({{< relref "vendure-plugin" >}}#defineworkers).
-2. A Plugin can modify any aspect of server configuration via the [`VendurePlugin.configure()` method]({{< relref "vendure-plugin" >}}#configure).
-3. A Plugin can extend the GraphQL APIs via the [`VendurePlugin.extendShopAPI()` method]({{< relref "vendure-plugin" >}}#extendshopapi) and the [`VendurePlugin.extendAdminAPI()` method]({{< relref "vendure-plugin" >}}#extendadminapi).
-4. A Plugin has full access to the ServiceModule, which means it may inject and of the core Vendure services (which are responsible for all interaction with the database as well as business logic). Additionally a plugin may define its own services via the [`VendurePlugin.defineProviders()` method]({{< relref "vendure-plugin" >}}#defineproviders) and may define new database entities via the [`VendurePlugin.defineEntities()` method]({{< relref "vendure-plugin" >}}#defineentities).
+A plugin in Vendure is a specialized Nestjs Module which is decorated with the [`VendurePlugin` class decorator]({{< relref "vendure-plugin" >}}). This diagram illustrates the how a plugin can integrate with and extend Vendure.
+ 
+1. A Plugin may define logic to be run by the [Vendure Worker]({{< relref "vendure-worker" >}}). This is suitable for long-running or resource-intensive tasks and is done by providing controllers via the [`workers` metadata property]({{< relref "vendure-plugin-metadata" >}}#workers).
+2. A Plugin can modify any aspect of server configuration via the [`configuration` metadata property]({{< relref "vendure-plugin-metadata" >}}#configuration).
+3. A Plugin can extend the GraphQL APIs via the [`shopApiExtensions` metadata property]({{< relref "vendure-plugin-metadata" >}}#shopapiextensions) and the [`adminApiExtensions` metadata property]({{< relref "vendure-plugin-metadata" >}}#adminapiextensions).
+4. A Plugin can interact with Vendure by importing the [`PluginCommonModule`]({{< relref "plugin-common-module" >}}), by which it may inject and of the core Vendure services (which are responsible for all interaction with the database as well as business logic). Additionally a plugin may define new database entities via the [`entities` metadata property]({{< relref "vendure-plugin-metadata" >}}#entities) and otherwise define any other providers and controllers just like any [Nestjs module](https://docs.nestjs.com/modules).
 5. A Plugin can run arbitrary code, which allows it to make use of external services. For example, a plugin could interface with a cloud storage provider, a payment gateway, or a video encoding service.
+
+## Plugin Examples
+
+Here are some simplified examples of plugins which serve to illustrate what can be done with Vendure plugins. *Note: implementation details are skipped in these examples for the sake of brevity. A complete example with explanation can be found in [Writing A Vendure Plugin]({{< relref "writing-a-vendure-plugin" >}}).*
+
+### Modifying the VendureConfig
+
+This example shows how to modify the VendureConfig, in this case by adding a custom field to allow product ratings.
+```TypeScript
+@VendurePlugin({
+  configure: config => {
+    config.customFields.Product.push({
+      name: 'rating',
+      type: 'float',
+      min: 0,
+      max: 5,
+    });
+    return config;
+  },
+})
+class ProductRatingPlugin {}
+```
+
+### Extending the GraphQL API
+
+This example adds a new query to the GraphQL Admin API. It also demonstrates how [Nest's dependency injection](https://docs.nestjs.com/providers) can be used to encapsulate and inject services within the plugin module.
+ 
+```TypeScript
+@VendurePlugin({
+  imports: [PluginCommonModule],
+  providers: [TopSellersService],
+  adminApiExtensions: {
+    schema: gql`
+      extend type Query {
+        topSellers(from: DateTime! to: DateTime!): [Product!]!
+      }
+    `,
+    resolvers: [TopSellersResolver]
+  }
+})
+export class TopSellersPlugin {}
+
+@Resolver()
+class TopSellersResolver {
+
+  constructor(private topSellersService: TopSellersService) {}
+
+  @Query()
+  topSellers(@Ctx() ctx: RequestContext, @Args() args: any) {
+    return this.topSellersService.getTopSellers(ctx, args.from, args.to);
+  }
+
+}
+
+@Injectable()
+class TopSellersService { 
+  getTopSellers() { /* ... */ }
+}
+```
+
+### Adding a REST endpoint
+
+This plugin adds a single REST endpoint at `<host:port>/products` which returns a list of all Products. Since this uses no Vendure-specific metadata, it could also be written using the Nestjs `@Module()` decorator rather than the `@VendurePlugin()` decorator.
+
+```TypeScript
+@VendurePlugin({
+    imports: [PluginCommonModule],
+    controllers: [ProductsController],
+})
+export class RestPlugin {}
+
+@Controller('products')
+export class ProductsController {
+    constructor(private productService: ProductService) {}
+
+    @Get()
+    findAll(@Ctx() ctx: RequestContext) {
+        return this.productService.findAll(ctx);
+    }
+}
+```
+
+### Running processes on the Worker
+
+This example shows how to set up a microservice running on the Worker process, as well as subcribing to events via the EventBus.
+
+```TypeScript
+@VendurePlugin({
+  imports: [PluginCommonModule],
+  workers: [OrderProcessingController],
+})
+export class OrderAnalyticsPlugin implements OnVendureBootstrap {
+
+  constructor(
+    @Inject(VENDURE_WORKER_CLIENT) private client: ClientProxy,
+    private eventBus: EventBus,
+  ) {}
+  
+  /**
+   * When the server bootstraps, set up a subscription for events 
+   * published whenever  an Order changes state. When an Order has 
+   * been fulfilled, we send a message to the controller running on
+   * the Worker process to let it process that order.
+   */
+  onVendureBootstrap() {
+    this.eventBus.subscribe(OrderStateTransitionEvent, event => {
+      if (event.toState === 'Fulfilled') {
+        this.client.send('ORDER_PLACED', event.order).subscribe();
+      }
+    });
+  }
+
+}
+
+/**
+ * This controller will run on the Worker process.
+ */
+@Controller()
+class OrderProcessingController {
+
+  @MessagePattern('ORDER_PLACED')
+  async processOrder(order) {
+    // Do some expensive computation
+  }
+
+}
+```

+ 130 - 131
docs/content/docs/plugins/writing-a-vendure-plugin.md

@@ -5,57 +5,53 @@ weight: 0
 
 # Writing a Vendure Plugin
 
-A Vendure plugin is a class which implements the [`VendurePlugin` interface]({{< relref "vendure-plugin" >}}). This interface allows the plugin to:
-
-1. Modify the [VendureConfig]({{< relref "vendure-config" >}}) object.
-2. Extend the GraphQL API, including modifying existing types and adding completely new queries and mutations.
-3. Define new database entities and interact directly with the database.
-4. Run code before the server bootstraps, such as starting webservers.
+This is a complete example of how to implement a simple plugin step-by-step.
 
 ## Example: RandomCatPlugin
 
-Let's learn about these capabilities by writing a plugin which defines a new database entity and GraphQL mutation.
+Let's learn about Vendure plugins by writing a plugin which defines a new database entity and GraphQL mutation.
 
-This plugin will add a new mutation, `addRandomCat`, to the GraphQL API which allows us to conveniently link a random cat image from [http://random.cat](http://random.cat) to any product in out catalog.
+This plugin will add a new mutation, `addRandomCat`, to the GraphQL API which allows us to conveniently link a random cat image from [http://random.cat](http://random.cat) to any product in our catalog.
 
 ### Step 1: Define a new custom field
 
-We need a place to store the url of the cat image, so we will add a custom field to the Product entity. This is done by modifying the VendureConfig object in the the plugin's [`configure` method]({{< relref "vendure-plugin" >}}#configure):
+We need a place to store the url of the cat image, so we will add a custom field to the Product entity. This is done by modifying the VendureConfig object via the plugin's [`configuration` metadata property]({{< relref "vendure-plugin-metadata" >}}#configuration):
 
-```ts 
+```TypeScript
 import { VendurePlugin } from '@vendure/core';
 
-export class RandomCatPlugin implements VendurePlugin {
-    configure(config) {
-        config.customFields.Product.push({
-            type: 'string',
-            name: 'catImageUrl',
-        });
-        return config;
-    }
-}
+@VendurePlugin({
+  configuration: config => {
+    config.customFields.Product.push({
+      type: 'string',
+      name: 'catImageUrl',
+    });
+    return config;
+  }
+})
+export class RandomCatPlugin {}
 ```
 
 ### Step 2: Create a service to fetch the data
 
 Now we will create a service which is responsible for making the HTTP call to the random.cat API and returning the URL of a random cat image:
 
-```ts 
+```TypeScript
 import http from 'http';
 import { Injectable } from '@nestjs/common';
 
 @Injectable()
 export class CatFetcher {
-    /** Fetch a random cat image url from random.cat */
-    fetchCat(): Promise<string> {
-        return new Promise((resolve) => {
-            http.get('http://aws.random.cat/meow', (resp) => {
-                let data = '';
-                resp.on('data', chunk => data += chunk);
-                resp.on('end', () => resolve(JSON.parse(data).file));
-            });
-        });
-    }
+  /** Fetch a random cat image url from random.cat */
+  fetchCat(): Promise<string> {
+    return new Promise((resolve) => {
+      http.get('http://aws.random.cat/meow', (resp) => {
+        let data = '';
+        resp.on('data', chunk => data += chunk);
+        resp.on('end', () => resolve(JSON.parse(data).file));
+      });
+    });
+  }
 }
 ```
 
@@ -72,30 +68,23 @@ To use decorators with TypeScript, you must set the "emitDecoratorMetadata" and
 
 Next we will define how the GraphQL API should be extended:
 
-```ts 
+```TypeScript
 import gql from 'graphql-tag';
 
-export class RandomCatPlugin implements VendurePlugin {
-
-    private schemaExtension = gql`
-        extend type Mutation {
-            addRandomCat(id: ID!): Product!
-        }
-    `;
-
-    configure(config) {
-        // as above
-    }
-}
+const schemaExtension = gql`
+  extend type Mutation {
+    addRandomCat(id: ID!): Product!
+  }
+`;
 ```
 
-We will use this private `schemaExtension` variable in a later step.
+We will use this `schemaExtension` variable in a later step.
 
 ### Step 4: Create a resolver
 
 Now that we've defined the new mutation, we'll need a resolver function to handle it. To do this, we'll create a new resolver class, following the [Nest GraphQL resolver architecture](https://docs.nestjs.com/graphql/resolvers-map). In short, this will be a class which has the `@Resolver()` decorator and features a method to handle our new mutation.
 
-```ts 
+```TypeScript
 import { Args, Mutation, Resolver } from '@nestjs/graphql';
 import { Ctx, Allow, ProductService, RequestContext } from '@vendure/core';
 import { Permission } from '@vendure/common/lib/generated-types';
@@ -103,17 +92,17 @@ import { Permission } from '@vendure/common/lib/generated-types';
 @Resolver()
 export class RandomCatResolver {
 
-    constructor(private productService: ProductService, private catFetcher: CatFetcher) {}
+  constructor(private productService: ProductService, private catFetcher: CatFetcher) {}
 
-    @Mutation()
-    @Allow(Permission.UpdateCatalog)
-    async addRandomCat(@Ctx() ctx: RequestContext, @Args() args) {
-        const catImageUrl = await this.catFetcher.fetchCat();
-        return this.productService.update(ctx, {
-            id: args.id,
-            customFields: { catImageUrl },
-        });
-    }
+  @Mutation()
+  @Allow(Permission.UpdateCatalog)
+  async addRandomCat(@Ctx() ctx: RequestContext, @Args() args) {
+    const catImageUrl = await this.catFetcher.fetchCat();
+    return this.productService.update(ctx, {
+      id: args.id,
+      customFields: { catImageUrl },
+    });
+  }
 }
 ```
 
@@ -126,55 +115,70 @@ Some explanations of this code are in order:
 * The `@Ctx()` decorator injects the current `RequestContext` into the resolver. This provides information about the current request such as the current Session, User and Channel. It is required by most of the internal service methods.
 * The `@Args()` decorator injects the arguments passed to the mutation as an object.
 
-### Step 5: Export any providers used in the resolver
+### Step 5: Import the PluginCommonModule
 
-In order that out resolver is able to use Nest's dependency injection to inject and instance of `CatFetcher`, we must export it via the [`defineProviders` method](({{< relref "vendure-plugin" >}}#defineproviders)) in our plugin:
+In the RandomCatResolver we make use of the ProductService. This is one of the built-in services which Vendure uses internally to interact with the database. It can be injected into our resolver only if we first import the [PluginCommonModule]({{< relref "plugin-common-module" >}}), which is a module which exports a number of providers which are usually needed by plugins.
 
-```ts 
-export class RandomCatPlugin implements VendurePlugin {
+```TypeScript
+@VendurePlugin({
+  imports: [PluginCommonModule],
+  configuration: config => {
+    // omitted
+  }
+})
+export class RandomCatPlugin {}
+``` 
 
-    // ...
+### Step 6: Declare any providers used in the resolver
 
-    defineProviders() {
-        return [CatFetcher];
-    }
-}
-```
+In order that out resolver is able to use Nest's dependency injection to inject and instance of `CatFetcher`, we must add it to the `providers` array in our plugin:
 
-### Step 6: Extend the GraphQL API
+```TypeScript
+@VendurePlugin({
+  imports: [PluginCommonModule],
+  providers: [CatFetcher],
+  configuration: config => {
+    // omitted
+  }
+})
+export class RandomCatPlugin {}
+```
 
-Now that we've defined the new mutation and we have a resolver capable of handling it, we just need to tell Vendure to extend the API. This is done with the [`extendAdminAPI` method]({{< relref "vendure-plugin" >}}#extendadminapi). If we wanted to extend the Shop API, we'd use the [`extendShopAPI` method]({{< relref "vendure-plugin" >}}#extendshopapi) method instead.
+### Step 7: Extend the GraphQL API
 
-```ts 
-export class RandomCatPlugin implements VendurePlugin {
+Now that we've defined the new mutation and we have a resolver capable of handling it, we just need to tell Vendure to extend the API. This is done with the [`adminApiExtensions` metadata property]({{< relref "vendure-plugin-metadata" >}}#adminapiextensions). If we wanted to extend the Shop API, we'd use the [`adminApiExtensions` metadata property]({{< relref "vendure-plugin-metadata" >}}#shopapiextensions) instead.
 
-    // ...
-    
-    extendAdminAPI() {
-        return {
-            schema: this.schemaExtension,
-            resolvers: [RandomCatResolver],
-        };
-    }
-}
+```TypeScript
+@VendurePlugin({
+  imports: [PluginCommonModule],
+  providers: [CatFetcher],
+  adminApiExtensions: {
+    schema: schemaExtension,
+    resolvers: [RandomCatResolver],
+  },
+  configuration: config => {
+    // omitted
+  }
+})
+export class RandomCatPlugin {}
 ```
 
-### Step 7: Add the plugin to the Vendure config
+### Step 8: Add the plugin to the Vendure config
 
 Finally we need to add an instance of our plugin to the config object with which we bootstrap out Vendure server:
 
-```ts 
+```TypeScript
 import { bootstrap } from '@vendure/core';
 
 bootstrap({
     // .. config options
     plugins: [
-        new RandomCatPlugin(),
+        RandomCatPlugin,
     ],
 });
 ```
 
-### Step 8: Test the plugin
+### Step 9: Test the plugin
 
 Once we have started the Vendure server with the new config, we should be able to send the following GraphQL query to the Admin API:
 
@@ -192,7 +196,7 @@ mutation {
 
 which should yield the following response:
 
-```JSON 
+```JSON
 {
   "data": {
     "addRandomCat": {
@@ -208,7 +212,7 @@ which should yield the following response:
 
 ### Full example plugin
 
-```ts
+```TypeScript
 import { Injectable } from '@nestjs/common';
 import { Args, Mutation, Resolver } from '@nestjs/graphql';
 import gql from 'graphql-tag';
@@ -216,60 +220,55 @@ import http from 'http';
 import { Allow, Ctx, ProductService, RequestContext, VendureConfig, VendurePlugin } from '@vendure/core';
 import { Permission } from '@vendure/common/lib/generated-types';
 
-export class RandomCatPlugin implements VendurePlugin {
-
-    private schemaExtension = gql`
-        extend type Mutation {
-            addRandomCat(id: ID!): Product!
-        }
-    `;
-
-    configure(config: Required<VendureConfig>) {
-        config.customFields.Product.push({
-            type: 'string',
-            name: 'catImageUrl',
-        });
-        return config;
-    }
-
-    defineProviders() {
-        return [CatFetcher];
+const schemaExtension = gql`
+    extend type Mutation {
+        addRandomCat(id: ID!): Product!
     }
-
-    extendAdminAPI() {
-        return {
-            schema: this.schemaExtension,
-            resolvers: [RandomCatResolver],
-        };
-    }
-}
+`;
+
+@VendurePlugin({
+  imports: [PluginCommonModule],
+  providers: [CatFetcher],
+  adminApiExtensions: {
+    schema: schemaExtension,
+    resolvers: [RandomCatResolver],
+  },
+  configuration: config => {
+    config.customFields.Product.push({
+      type: 'string',
+      name: 'catImageUrl',
+    });
+    return config;
+  }
+})
+export class RandomCatPlugin {}
 
 @Injectable()
 export class CatFetcher {
-    /** Fetch a random cat image url from random.cat */
-    fetchCat(): Promise<string> {
-        return new Promise((resolve) => {
-            http.get('http://aws.random.cat/meow', (resp) => {
-                let data = '';
-                resp.on('data', chunk => data += chunk);
-                resp.on('end', () => resolve(JSON.parse(data).file));
-            });
-        });
-    }
+  /** Fetch a random cat image url from random.cat */
+  fetchCat(): Promise<string> {
+    return new Promise((resolve) => {
+      http.get('http://aws.random.cat/meow', (resp) => {
+        let data = '';
+        resp.on('data', chunk => data += chunk);
+        resp.on('end', () => resolve(JSON.parse(data).file));
+      });
+    });
+  }
 }
 
 @Resolver()
 export class RandomCatResolver {
-    constructor(private productService: ProductService, private catFetcher: CatFetcher) {}
-
-    @Mutation()
-    @Allow(Permission.UpdateCatalog)
-    async addRandomCat(@Ctx() ctx: RequestContext, @Args() args) {
-        const catImageUrl = await this.catFetcher.fetchCat();
-        return this.productService.update(ctx, {
-            id: args.id,
-            customFields: { catImageUrl },
-        });
-    }
+  constructor(private productService: ProductService, private catFetcher: CatFetcher) {}
+
+  @Mutation()
+  @Allow(Permission.UpdateCatalog)
+  async addRandomCat(@Ctx() ctx: RequestContext, @Args() args) {
+    const catImageUrl = await this.catFetcher.fetchCat();
+    return this.productService.update(ctx, {
+      id: args.id,
+      customFields: { catImageUrl },
+    });
+  }
 }
 ```

+ 2 - 2
packages/admin-ui-plugin/src/plugin.ts

@@ -1,4 +1,4 @@
-import { AdminUiConfig } from '@vendure/common/lib/shared-types';
+import { AdminUiConfig, Type } from '@vendure/common/lib/shared-types';
 import {
     createProxyHandler,
     OnVendureBootstrap,
@@ -90,7 +90,7 @@ export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
      * @description
      * Set the plugin options
      */
-    static init(options: AdminUiOptions) {
+    static init(options: AdminUiOptions): Type<AdminUiPlugin> {
         this.options = options;
         return AdminUiPlugin;
     }

+ 7 - 1
packages/asset-server-plugin/src/plugin.ts

@@ -1,3 +1,4 @@
+import { Type } from '@vendure/common/lib/shared-types';
 import {
     AssetStorageStrategy,
     createProxyHandler,
@@ -200,11 +201,16 @@ export class AssetServerPlugin implements OnVendureBootstrap, OnVendureClose {
     ];
     private static options: AssetServerOptions;
 
-    static init(options: AssetServerOptions) {
+    /**
+     * @description
+     * Set the plugin options.
+     */
+    static init(options: AssetServerOptions): Type<AssetServerPlugin> {
         AssetServerPlugin.options = options;
         return this;
     }
 
+    /** @internal */
     static configure(config: Required<VendureConfig>) {
         this.assetStorage = this.createAssetStorageStrategy(this.options);
         config.assetOptions.assetPreviewStrategy = new SharpAssetPreviewStrategy({

+ 1 - 0
packages/core/src/plugin/default-search-plugin/default-search-plugin.ts

@@ -59,6 +59,7 @@ export interface DefaultSearchReindexResponse extends SearchReindexResponse {
     workers: [IndexerController],
 })
 export class DefaultSearchPlugin implements OnVendureBootstrap {
+    /** @internal */
     constructor(private eventBus: EventBus, private searchIndexService: SearchIndexService) {}
 
     /** @internal */

+ 9 - 0
packages/core/src/plugin/plugin-common.module.ts

@@ -8,9 +8,18 @@ import { ServiceModule } from '../service/service.module';
 import { VENDURE_WORKER_CLIENT } from '../worker/constants';
 
 /**
+ * @description
  * This module provides the common services, configuration, and event bus capabilities
  * required by a typical plugin. It should be imported into plugins to avoid having to
  * repeat the same boilerplate for each individual plugin.
+ *
+ * The PluginCommonModule exports:
+ *
+ * * EventBusModule, allowing the injection of the {@link EventBus} service.
+ * * ServiceModule allowing the injection of any of the various entity services such as ProductService, OrderService etc.
+ * * ConfigModule, allowing the injection of the ConfigService.
+ * * The `VENDURE_WORKER_CLIENT` token, allowing the injection of the Nest microservice ClientProxy.
+ * @docsCategory plugin
  */
 @Module({
     imports: [EventBusModule, ConfigModule, ServiceModule.forPlugin()],

+ 15 - 12
packages/core/src/plugin/plugin-utils.ts

@@ -43,21 +43,24 @@ export interface ProxyOptions {
  *
  * @example
  * ```ts
- * // Example usage in the `configure` method of a VendurePlugin.
+ * // Example usage in the `configuration` method of a VendurePlugin.
  * // Imagine that we have started a Node server on port 5678
  * // running some service which we want to access via the `/my-plugin/`
  * // route of the main Vendure server.
- * configure(config: Required<VendureConfig>): Required<VendureConfig> {
- *     config.middleware.push({
- *         handler: createProxyHandler({
- *             label: 'Admin UI',
- *             route: 'my-plugin',
- *             port: 5678,
- *         }),
- *         route: 'my-plugin',
- *     });
- *     return config;
- * }
+ * \@VendurePlugin({
+ *   configure: (config: Required<VendureConfig>) => {
+ *       config.middleware.push({
+ *           handler: createProxyHandler({
+ *               label: 'Admin UI',
+ *               route: 'my-plugin',
+ *               port: 5678,
+ *           }),
+ *           route: 'my-plugin',
+ *       });
+ *       return config;
+ *   }
+ * })
+ * export class MyPlugin {}
  * ```
  *
  * @docsCategory Plugin

+ 79 - 9
packages/core/src/plugin/vendure-plugin.ts

@@ -31,22 +31,36 @@ export interface APIExtensionDefinition {
     schema?: DocumentNode;
     /**
      * @description
-     * An array of resolvers for the schema extensions. Should be defined as Nest GraphQL resolver
-     * classes, i.e. using the Nest `@Resolver()` decorator etc.
+     * 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>>;
 }
 
 /**
  * @description
- * This method is called before the app bootstraps, and can modify the VendureConfig object and perform
- * other (potentially async) tasks needed.
+ * This method is called before the app bootstraps and should be used to perform any needed modifications to the {@link VendureConfig}.
+ *
+ * @docsCategory plugin
  */
 export type PluginConfigurationFn = (
     config: Required<VendureConfig>,
 ) => Required<VendureConfig> | Promise<Required<VendureConfig>>;
 
+/**
+ * @description
+ * Defines the metadata of a Vendure plugin. This interface is an superset of the [Nestjs ModuleMetadata](https://docs.nestjs.com/modules)
+ * (which allows the definition of `imports`, `exports`, `providers` and `controllers`), which means
+ * that any Nestjs Module is a valid Vendure plugin. In addition, the VendurePluginMetadata allows the definition of
+ * extra properties specific to Vendure.
+ *
+ * @docsCategory plugin
+ */
 export interface VendurePluginMetadata extends ModuleMetadata {
+    /**
+     * @description
+     * A function which can modify the {@link VendureConfig} object before the server bootstraps.
+     */
     configuration?: PluginConfigurationFn;
     /**
      * @description
@@ -62,26 +76,50 @@ export interface VendurePluginMetadata extends ModuleMetadata {
     adminApiExtensions?: APIExtensionDefinition;
     /**
      * @description
-     * The plugin may define providers which are run in the Worker context, i.e. Nest microservice controllers.
+     * The plugin may define [Nestjs microservice controllers](https://docs.nestjs.com/microservices/basics#request-response)
+     * which are run in the Worker context.
      */
     workers?: Array<Type<any>>;
     /**
      * @description
-     * The plugin may define custom database entities, which should be defined as classes annotated as per the
-     * TypeORM documentation.
+     * The plugin may define custom [TypeORM database entities](https://typeorm.io/#/entities).
      */
     entities?: Array<Type<any>>;
 }
 
 /**
  * @description
- * A VendurePlugin is a means of configuring and/or extending the functionality of the Vendure server. A Vendure Plugin is
- * a Nestjs Module, with optional additional metadata defining things like extensions to the GraphQL API, custom
+ * The VendurePlugin decorator is a means of configuring and/or extending the functionality of the Vendure server. A Vendure plugin is
+ * a [Nestjs Module](https://docs.nestjs.com/modules), with optional additional metadata defining things like extensions to the GraphQL API, custom
  * configuration or new database entities.
  *
  * As well as configuring the app, a plugin may also extend the GraphQL schema by extending existing types or adding
  * entirely new types. Database entities and resolvers can also be defined to handle the extended GraphQL types.
  *
+ * @example
+ * ```TypeScript
+ * import { Controller, Get } from '\@nestjs/common';
+ * import { Ctx, PluginCommonModule, ProductService, RequestContext, VendurePlugin } from '\@vendure/core';
+ *
+ * \@Controller('products')
+ * export class ProductsController {
+ *     constructor(private productService: ProductService) {}
+ *
+ *     \@Get()
+ *     findAll(\@Ctx() ctx: RequestContext) {
+ *         return this.productService.findAll(ctx);
+ *     }
+ * }
+ *
+ *
+ * //A simple plugin which adds a REST endpoint for querying products.
+ * \@VendurePlugin({
+ *     imports: [PluginCommonModule],
+ *     controllers: [ProductsController],
+ * })
+ * export class RestPlugin {}
+ * ```
+ *
  * @docsCategory plugin
  */
 export function VendurePlugin(pluginMetadata: VendurePluginMetadata): ClassDecorator {
@@ -98,18 +136,50 @@ export function VendurePlugin(pluginMetadata: VendurePluginMetadata): ClassDecor
     };
 }
 
+/**
+ * @description
+ * A plugin which implements this interface can define logic to run when the Vendure server is initialized.
+ *
+ * For example, this could be used to call out to an external API or to set up {@link EventBus} listeners.
+ *
+ * @docsCategory plugin
+ */
 export interface OnVendureBootstrap {
     onVendureBootstrap(): void | Promise<void>;
 }
 
+/**
+ * @description
+ * A plugin which implements this interface can define logic to run when the Vendure worker is initialized.
+ *
+ * For example, this could be used to start or connect to a server or databased used by the worker.
+ *
+ * @docsCategory plugin
+ */
 export interface OnVendureWorkerBootstrap {
     onVendureWorkerBootstrap(): void | Promise<void>;
 }
 
+/**
+ * @description
+ * A plugin which implements this interface can define logic to run before Vendure server is closed.
+ *
+ * For example, this could be used to clean up any processes started by the {@link OnVendureBootstrap} method.
+ *
+ * @docsCategory plugin
+ */
 export interface OnVendureClose {
     onVendureClose(): void | Promise<void>;
 }
 
+/**
+ * @description
+ * A plugin which implements this interface can define logic to run before Vendure worker is closed.
+ *
+ * For example, this could be used to close any open connections to external services.
+ *
+ * @docsCategory plugin
+ */
 export interface OnVendureWorkerClose {
     onVendureWorkerClose(): void | Promise<void>;
 }

+ 7 - 2
packages/elasticsearch-plugin/src/plugin.ts

@@ -12,6 +12,7 @@ import {
     ProductVariant,
     SearchService,
     TaxRateModificationEvent,
+    Type,
     VendurePlugin,
 } from '@vendure/core';
 
@@ -59,7 +60,7 @@ export interface ElasticsearchOptions {
 /**
  * @description
  * This plugin allows your product search to be powered by [Elasticsearch](https://github.com/elastic/elasticsearch) - a powerful Open Source search
- * engine. This is a drop-in replacement for the {@link DefaultSearchPlugin}.
+ * engine. This is a drop-in replacement for the DefaultSearchPlugin.
  *
  * ## Installation
  *
@@ -102,13 +103,17 @@ export class ElasticsearchPlugin implements OnVendureBootstrap, OnVendureClose {
     private static options: Required<ElasticsearchOptions>;
     private static client: Client;
 
+    /** @internal */
     constructor(
         private eventBus: EventBus,
         private elasticsearchService: ElasticsearchService,
         private elasticsearchIndexService: ElasticsearchIndexService,
     ) {}
 
-    static init(options: ElasticsearchOptions) {
+    /**
+     * Set the plugin options.
+     */
+    static init(options: ElasticsearchOptions): Type<ElasticsearchPlugin> {
         const { host, port } = options;
         this.options = { indexPrefix: 'vendure-', batchSize: 2000, ...options };
         this.client = new Client({

+ 6 - 1
packages/email-plugin/src/plugin.ts

@@ -6,6 +6,7 @@ import {
     Logger,
     OnVendureBootstrap,
     OnVendureClose,
+    Type,
     VendureConfig,
     VendurePlugin,
 } from '@vendure/core';
@@ -143,9 +144,13 @@ export class EmailPlugin implements OnVendureBootstrap, OnVendureClose {
     private generator: HandlebarsMjmlGenerator;
     private devMailbox: DevMailbox | undefined;
 
+    /** @internal */
     constructor(private eventBus: EventBus) {}
 
-    static init(options: EmailPluginOptions | EmailPluginDevModeOptions) {
+    /**
+     * Set the plugin options.
+     */
+    static init(options: EmailPluginOptions | EmailPluginDevModeOptions): Type<EmailPlugin> {
         this.options = options;
         return EmailPlugin;
     }