Browse Source

docs: Start plugins & worker guides

Michael Bromley 2 years ago
parent
commit
74df8503c6

+ 0 - 0
docs/docs/guides/advanced-topics/index.md


+ 108 - 0
docs/docs/guides/concepts/plugins/index.mdx

@@ -0,0 +1,108 @@
+---
+title: 'Plugins'
+sidebar_position: 6
+---
+
+The heart of Vendure is its plugin system. Plugins not only allow you to instantly add new functionality to your
+Vendure server via third-part npm packages, they are also the means by which you build out the custom business
+logic of your application.
+
+Plugins in Vendure allow one to:
+
+- Modify the VendureConfig object, such as defining custom fields on existing entities.
+- Extend the GraphQL APIs, including modifying existing types and adding completely new queries and mutations.
+- Define new database entities and interact directly with the database.
+- Interact with external systems that you need to integrate with.
+- Respond to events such as new orders being placed.
+- Trigger background tasks to run on the worker process.
+
+… and more!
+
+In a typical Vendure application, custom logic and functionality is implemented as a set of plugins
+which are usually independent of one another. For example, there could be a plugin for each of the following:
+wishlists, product reviews, loyalty points, gift cards, etc.
+This allows for a clean separation of concerns and makes it easy to add or remove functionality as needed.
+
+## Core Plugins
+
+Vendure provides a set of core plugins covering common functionality such as assets handling,
+email sending, and search. For documentation on these, see the [Core Plugins reference](/reference/typescript-api/core-plugins/).
+
+## Plugin basics
+
+Here's a bare-minimum example of a plugin:
+
+```ts title="src/plugins/avatar-plugin/avatar.plugin.ts"
+import { LanguageCode, PluginCommonModule, VendurePlugin } from '@vendure/core';
+
+@VendurePlugin({
+    imports: [PluginCommonModule],
+    configure: config => {
+        config.customFields.Customer.push({
+            type: 'string',
+            name: 'avatarUrl',
+            label: [{ languageCode: LanguageCode.en, value: 'Avatar URL' }],
+            list: true,
+        });
+        return config;
+    },
+})
+export class AvatarPlugin {}
+```
+
+This plugin does one thing only: it adds a new custom field to the `Customer` entity.
+
+The plugin is then imported into the `VendureConfig`:
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+import { AvatarPlugin } from './plugins/avatar-plugin/avatar.plugin';
+
+export const config: VendureConfig = {
+    // ...
+    // highlight-next-line
+    plugins: [AvatarPlugin],
+};
+```
+
+The key feature is the `@VendurePlugin()` decorator, which marks the class as a Vendure plugin and accepts a configuration
+object on the type [`VendurePluginMetadata`](/reference/typescript-api/plugin/vendure-plugin-metadata/).
+
+A VendurePlugin is actually an enhanced version of a [NestJS Module](https://docs.nestjs.com/modules), and supports
+all the metadata properties that NestJS modules support:
+
+- `imports`: Allows importing other NestJS modules in order to make use of their exported providers.
+- `providers`: The providers (services) that will be instantiated by the Nest injector and that may
+    be shared across this plugin.
+- `controllers`: Controllers allow the plugin to define REST-style endpoints.
+- `exports`: The providers which will be exported from this plugin and made available to other plugins.
+
+Additionally, the `VendurePlugin` decorator adds the following Vendure-specific properties:
+
+- `configuration`: A function which can modify the `VendureConfig` object before the server bootstraps.
+- `shopApiExtensions`: Allows the plugin to extend the GraphQL Shop API with new queries, mutations, resolvers & scalars.
+- `adminApiExtensions`: Allows the plugin to extend the GraphQL Admin API with new queries, mutations, resolvers & scalars.
+- `entities`: Allows the plugin to define new database entities.
+- `compatibility`: Allows the plugin to declare which versions of Vendure it is compatible with.
+
+:::info
+Since a Vendure plugin is a superset of a NestJS module, this means that many NestJS modules are actually
+valid Vendure plugins!
+:::
+
+## Plugin lifecycle
+
+Since a VendurePlugin is built on top of the NestJS module system, any plugin (as well as any providers it defines)
+can make use of any of the [NestJS lifecycle hooks](https://docs.nestjs.com/fundamentals/lifecycle-events):
+
+- onModuleInit
+- onApplicationBootstrap
+- onModuleDestroy
+- beforeApplicationShutdown
+- onApplicationShutdown
+
+:::caution
+Note that lifecycle hooks are run in both the server and worker contexts.
+If you have code that should only run either in the server context or worker context,
+you can inject the [ProcessContext provider](/reference/typescript-api/common/process-context/).
+:::

+ 107 - 0
docs/docs/guides/concepts/worker-job-queue/index.mdx

@@ -0,0 +1,107 @@
+---
+title: 'Worker & Job Queue'
+sidebar_position: 5
+---
+
+The Vendure Worker is a Node.js process responsible for running computationally intensive
+or otherwise long-running tasks in the background. For example, updating a
+search index or sending emails. Running such tasks in the background allows
+the server to stay responsive, since a response can be returned immediately
+without waiting for the slower tasks to complete.
+
+Put another way, the Worker executes **jobs** which have been placed in the **job queue**.
+
+![Worker & Job Queue](./worker-job-queue.webp)
+
+## The worker
+
+The worker is started by calling the [`bootstrapWorker()`](/reference/typescript-api/worker/bootstrap-worker/) function with the same
+configuration as is passed to the main server `bootstrap()`. In a standard Vendure installation, this is found
+in the `index-worker.ts` file:
+
+```ts title="src/index-worker.ts"
+import { bootstrapWorker } from '@vendure/core';
+import { config } from './vendure-config';
+
+bootstrapWorker(config)
+    .then(worker => worker.startJobQueue())
+    .catch(err => {
+        console.log(err);
+    });
+```
+
+### Underlying architecture
+
+The Worker is a NestJS standalone application. This means it is almost identical to the main server app,
+but does not have any network layer listening for requests. The server communicates with the worker
+via a “job queue” architecture. The exact implementation of the job queue is dependent on the
+configured [`JobQueueStrategy`](/reference/typescript-api/job-queue/job-queue-strategy/), but by default
+the worker polls the database for new jobs.
+
+### Multiple workers
+
+It is possible to run multiple workers in parallel to better handle heavy loads. Using the
+[`JobQueueOptions.activeQueues`](/reference/typescript-api/job-queue/job-queue-options#activequeues) configuration, it is even possible to have particular workers dedicated
+to one or more specific types of jobs. For example, if your application does video transcoding,
+you might want to set up a dedicated worker just for that task:
+
+```ts title="src/transcoder-worker.ts"
+import { bootstrapWorker, mergeConfig } from '@vendure/core';
+import { config } from './vendure-config';
+
+const transcoderConfig = mergeConfig(config, {
+    jobQueueOptions: {
+      activeQueues: ['transcode-video'],
+    }
+});
+
+bootstrapWorker(transcoderConfig)
+  .then(worker => worker.startJobQueue())
+  .catch(err => {
+    console.log(err);
+  });
+```
+
+### Running jobs on the main process
+
+It is possible to run jobs from the Job Queue on the main server. This is mainly used for testing
+and automated tasks, and is not advised for production use, since it negates the benefits of
+running long tasks off of the main process. To do so, you need to manually start the JobQueueService:
+
+```ts title="src/index.ts"
+import { bootstrap, JobQueueService } from '@vendure/core';
+import { config } from './vendure-config';
+
+bootstrap(config)
+    .then(app => app.get(JobQueueService).start())
+    .catch(err => {
+        console.log(err);
+        process.exit(1);
+    });
+```
+
+### ProcessContext
+
+Sometimes your code may need to be aware of whether it is being run as part of a server or worker process.
+In this case you can inject the [`ProcessContext`](/reference/typescript-api/common/process-context/) provider and query it like this:
+
+```ts title="src/plugins/my-plugin/services/my.service.ts"
+import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
+import { ProcessContext } from '@vendure/core';
+
+@Injectable()
+export class MyService implements OnApplicationBootstrap {
+    constructor(private processContext: ProcessContext) {}
+
+    onApplicationBootstrap() {
+        if (this.processContext.isServer) {
+            // code which will only execute when running in
+            // the server process
+        }
+    }
+}
+```
+
+## The job queue
+
+

BIN
docs/docs/guides/concepts/worker-job-queue/worker-job-queue.webp


+ 74 - 0
docs/docs/guides/how-to/configurable-products/index.md

@@ -0,0 +1,74 @@
+---
+title: "Configurable Products"
+---
+
+
+One common use of custom fields is to support configurable products. Imagine we are selling pens which allow some text to be engraved. To do this we would add a **custom field on the OrderLine**:
+
+```ts
+OrderLine: [
+    {
+        name: 'engravingText',
+        type: 'string',
+        label: [
+            {
+                languageCode: LanguageCode.en,
+                value: 'The text to engrave on the product'
+            },
+        ],
+    },
+]
+```
+
+Once defined, the [addItemToOrder mutation]({{< relref "/reference/graphql-api/shop/mutations" >}}#additemtoorder) will have a third argument available, which accepts values for the custom field defined above:
+
+```graphql
+mutation {
+    addItemToOrder(
+        productVariantId: "42"
+        quantity: 1
+        customFields: {
+            engravingText: "Thanks for all the fish!"
+        }
+    ) {
+        ...on Order {
+            id
+            lines {
+                id
+                quantity
+                customFields {
+                    engravingText
+                }
+            }
+        }
+    }
+}
+```
+
+Furthermore, the values of these OrderLine custom fields can even be used to modify the price. This is done by defining a custom [OrderItemPriceCalculationStrategy]({{< relref "order-item-price-calculation-strategy" >}}):
+
+```ts
+import {
+    RequestContext, PriceCalculationResult,
+    ProductVariant, OrderItemPriceCalculationStrategy
+} from '@vendure/core';
+
+export class EngravingPriceStrategy implements OrderItemPriceCalculationStrategy {
+
+    calculateUnitPrice(
+        ctx: RequestContext,
+        productVariant: ProductVariant,
+        customFields: { engravingText?: string },
+    ) {
+        let price = productVariant.listPrice;
+        if (customFields.engravingText) {
+            // Add $5 for engraving
+            price += 500;
+        }
+        return {
+            price,
+            priceIncludesTax: productVariant.listPriceIncludesTax,
+        };
+    }
+}
+```

+ 69 - 0
docs/docs/reference/typescript-api/service-helpers/translator-service.md

@@ -0,0 +1,69 @@
+---
+title: "TranslatorService"
+weight: 10
+date: 2023-07-26T18:59:57.772Z
+showtoc: true
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## TranslatorService
+
+<GenerationInfo sourceFile="packages/core/src/service/helpers/translator/translator.service.ts" sourceLine="42" packageName="@vendure/core" />
+
+The TranslatorService is used to translate entities into the current language.
+
+*Example*
+
+```ts
+import { Injectable } from '@nestjs/common';
+import { ID, Product, RequestContext, TransactionalConnection, TranslatorService } from '@vendure/core';
+
+@Injectable()
+export class ProductService {
+
+    constructor(private connection: TransactionalConnection,
+                private translator: TranslatorService){}
+
+    async findOne(ctx: RequestContext, productId: ID): Promise<Product | undefined> {
+        const product = await this.connection.findOneInChannel(ctx, Product, productId, ctx.channelId, {
+            relations: {
+                 facetValues: {
+                     facet: true,
+                 }
+            }
+        });
+        if (!product) {
+            return;
+        }
+        return this.translator.translate(product, ctx, ['facetValues', ['facetValues', 'facet']]);
+    }
+}
+```
+
+```ts title="Signature"
+class TranslatorService {
+  constructor(configService: ConfigService)
+  translate(translatable: T, ctx: RequestContext, translatableRelations: DeepTranslatableRelations<T> = []) => ;
+}
+```
+
+<div className="members-wrapper">
+
+### constructor
+
+<MemberInfo kind="method" type={`(configService: ConfigService) => TranslatorService`}   />
+
+
+### translate
+
+<MemberInfo kind="method" type={`(translatable: T, ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, translatableRelations: DeepTranslatableRelations&#60;T&#62; = []) => `}   />
+
+
+
+
+</div>