Browse Source

docs: Rearrange guides

Michael Bromley 2 years ago
parent
commit
eae35789b4
62 changed files with 223 additions and 1228 deletions
  1. 0 0
      docs/docs/guides/advanced-topics/error-handling.md
  2. 0 0
      docs/docs/guides/advanced-topics/logging.md
  3. 0 0
      docs/docs/guides/developer-guide-old/promotions.md
  4. 0 0
      docs/docs/guides/developer-guide-old/shipping.md
  5. 0 0
      docs/docs/guides/developer-guide-old/stand-alone-scripts.md
  6. 0 0
      docs/docs/guides/developer-guide-old/taxes.md
  7. 0 0
      docs/docs/guides/developer-guide-old/testing.md
  8. 0 0
      docs/docs/guides/developer-guide-old/translations.md
  9. 0 0
      docs/docs/guides/developer-guide-old/updating-vendure.md
  10. 0 0
      docs/docs/guides/developer-guide-old/uploading-files.md
  11. 0 0
      docs/docs/guides/developer-guide-old/vendure-worker.md
  12. 0 453
      docs/docs/guides/developer-guide/authentication.md
  13. BIN
      docs/docs/guides/developer-guide/channels/channels_currencies_diagram.png
  14. BIN
      docs/docs/guides/developer-guide/channels/channels_diagram.png
  15. BIN
      docs/docs/guides/developer-guide/channels/channels_prices_diagram.png
  16. 0 69
      docs/docs/guides/developer-guide/channels/index.md
  17. 0 0
      docs/docs/guides/developer-guide/configuration/index.md
  18. 0 0
      docs/docs/guides/developer-guide/custom-fields/custom-fields-data-table.webp
  19. 0 0
      docs/docs/guides/developer-guide/custom-fields/custom-fields-ui.webp
  20. 0 0
      docs/docs/guides/developer-guide/custom-fields/index.md
  21. BIN
      docs/docs/guides/developer-guide/customizing-the-order-process/custom-order-ui.webp
  22. 0 173
      docs/docs/guides/developer-guide/customizing-the-order-process/index.md
  23. 0 0
      docs/docs/guides/developer-guide/data-model/admin-role.webp
  24. 0 0
      docs/docs/guides/developer-guide/data-model/channels.webp
  25. 0 0
      docs/docs/guides/developer-guide/data-model/collection-filters.webp
  26. 0 0
      docs/docs/guides/developer-guide/data-model/collections.webp
  27. 0 0
      docs/docs/guides/developer-guide/data-model/customer.webp
  28. 0 0
      docs/docs/guides/developer-guide/data-model/facets.webp
  29. 1 1
      docs/docs/guides/developer-guide/data-model/index.mdx
  30. 0 0
      docs/docs/guides/developer-guide/data-model/order.webp
  31. 0 0
      docs/docs/guides/developer-guide/data-model/products-variants.webp
  32. 0 9
      docs/docs/guides/developer-guide/developer-guide.md
  33. 0 59
      docs/docs/guides/developer-guide/job-queue/index.md
  34. BIN
      docs/docs/guides/developer-guide/job-queue/job_queue_req_res.png
  35. BIN
      docs/docs/guides/developer-guide/job-queue/job_queue_req_res_with_queue.png
  36. BIN
      docs/docs/guides/developer-guide/job-queue/job_queue_sequence.png
  37. 0 107
      docs/docs/guides/developer-guide/migrations.md
  38. 221 0
      docs/docs/guides/developer-guide/migrations/index.md
  39. BIN
      docs/docs/guides/developer-guide/migrations/migration.webp
  40. 0 0
      docs/docs/guides/developer-guide/overview/Vendure_docs-architecture.webp
  41. 0 0
      docs/docs/guides/developer-guide/overview/index.md
  42. 0 293
      docs/docs/guides/developer-guide/payment-integrations/index.md
  43. BIN
      docs/docs/guides/developer-guide/payment-integrations/payment_sequence_one_step.png
  44. BIN
      docs/docs/guides/developer-guide/payment-integrations/payment_sequence_two_step.png
  45. 0 0
      docs/docs/guides/developer-guide/plugins/index.mdx
  46. BIN
      docs/docs/guides/developer-guide/stock-control/global-stock-control.webp
  47. 0 63
      docs/docs/guides/developer-guide/stock-control/index.md
  48. 0 0
      docs/docs/guides/developer-guide/strategies-configurable-operations/collection-filters-args.webp
  49. 0 0
      docs/docs/guides/developer-guide/strategies-configurable-operations/collection-filters.webp
  50. 0 0
      docs/docs/guides/developer-guide/strategies-configurable-operations/index.mdx
  51. 0 0
      docs/docs/guides/developer-guide/the-api-layer/Vendure_docs-api_request.webp
  52. 0 0
      docs/docs/guides/developer-guide/the-api-layer/index.mdx
  53. 0 0
      docs/docs/guides/developer-guide/the-service-layer/index.mdx
  54. 0 0
      docs/docs/guides/developer-guide/worker-job-queue/Vendure_docs-job-queue-2.webp
  55. 0 0
      docs/docs/guides/developer-guide/worker-job-queue/Vendure_docs-job-queue-3.webp
  56. 0 0
      docs/docs/guides/developer-guide/worker-job-queue/Vendure_docs-job-queue.webp
  57. 0 0
      docs/docs/guides/developer-guide/worker-job-queue/index.mdx
  58. 0 0
      docs/docs/guides/developer-guide/worker-job-queue/worker-job-queue.webp
  59. 0 0
      docs/docs/guides/how-to/importing-product-data.md
  60. 0 0
      docs/docs/guides/how-to/multi-vendor-marketplaces/aggregate-order.webp
  61. 0 0
      docs/docs/guides/how-to/multi-vendor-marketplaces/index.md
  62. 1 1
      packages/core/src/config/order/order-item-price-calculation-strategy.ts

+ 0 - 0
docs/docs/guides/developer-guide/error-handling.md → docs/docs/guides/advanced-topics/error-handling.md


+ 0 - 0
docs/docs/guides/developer-guide/logging.md → docs/docs/guides/advanced-topics/logging.md


+ 0 - 0
docs/docs/guides/developer-guide/promotions.md → docs/docs/guides/developer-guide-old/promotions.md


+ 0 - 0
docs/docs/guides/developer-guide/shipping.md → docs/docs/guides/developer-guide-old/shipping.md


+ 0 - 0
docs/docs/guides/developer-guide/stand-alone-scripts.md → docs/docs/guides/developer-guide-old/stand-alone-scripts.md


+ 0 - 0
docs/docs/guides/developer-guide/taxes.md → docs/docs/guides/developer-guide-old/taxes.md


+ 0 - 0
docs/docs/guides/developer-guide/testing.md → docs/docs/guides/developer-guide-old/testing.md


+ 0 - 0
docs/docs/guides/developer-guide/translations.md → docs/docs/guides/developer-guide-old/translations.md


+ 0 - 0
docs/docs/guides/developer-guide/updating-vendure.md → docs/docs/guides/developer-guide-old/updating-vendure.md


+ 0 - 0
docs/docs/guides/developer-guide/uploading-files.md → docs/docs/guides/developer-guide-old/uploading-files.md


+ 0 - 0
docs/docs/guides/developer-guide/vendure-worker.md → docs/docs/guides/developer-guide-old/vendure-worker.md


+ 0 - 453
docs/docs/guides/developer-guide/authentication.md

@@ -1,453 +0,0 @@
----
-title: 'Authentication'
-showtoc: true
----
-
-# Authentication
-
-Authentication is the process of determining the identity of a user. Common ways of authenticating a user are by asking the user for secret credentials (username & password) or by a third-party authentication provider such as Facebook or Google login.
-
-By default, Vendure uses a username/email address and password to authenticate users, but also supports a wide range of authentication methods via configurable AuthenticationStrategies.
-
-{{< alert "primary" >}}
-See the [Managing Sessions guide]({{< relref "managing-sessions" >}}) for how to manage authenticated sessions in your storefront/client applications.
-{{< /alert >}}
-
-## Adding support for external authentication
-
-This is done via the [`VendureConfig.authOptions` object]({{< relref "auth-options" >}}#shopauthenticationstrategy):
-
-```ts
-export const config: VendureConfig = {
-  authOptions: {
-      shopAuthenticationStrategy: [
-        new NativeAuthenticationStrategy(),
-        new FacebookAuthenticationStrategy(),
-        new GoogleAuthenticationStrategy(),
-      ],
-      adminAuthenticationStrategy: [
-        new NativeAuthenticationStrategy(),
-        new KeycloakAuthenticationStrategy(),
-      ],
-  }
-}
-```
-
-In the above example, we define the strategies available for authenticating in the Shop API and the Admin API. The `NativeAuthenticationStrategy` is the only one actually provided by Vendure out-of-the-box, and this is the default username/email + password strategy.
-
-The other strategies would be custom-built (or provided by future npm packages) by creating classes that implement the [`AuthenticationStrategy` interface]({{< relref "authentication-strategy" >}}).
-
-Let's take a look at a couple of examples of what a custom AuthenticationStrategy implementation would look like.
-
-## Example: Google authentication
-
-This example demonstrates how to implement a Google login flow.
-
-### Storefront setup
-
-In your storefront, you need to integrate the Google sign-in button as described in ["Integrating Google Sign-In into your web app"](https://developers.google.com/identity/sign-in/web/sign-in). Successful authentication will result in a `onSignIn` function being called in your app. It will look something like this:
-
-```ts
-function onSignIn(googleUser) {
-  graphQlQuery(
-    `mutation Authenticate($token: String!) {
-        authenticate(input: {
-          google: { token: $token }
-        }) {
-        ...on CurrentUser {
-            id
-            identifier
-        }
-      }
-    }`,
-    { token: googleUser.getAuthResponse().id_token }
-  ).then(() => {
-    // redirect to account page
-  });
-}
-```
-
-### Backend
-
-On the backend, you'll need to define an AuthenticationStrategy to take the authorization token provided by the
-storefront in the `authenticate` mutation, and use it to get the necessary personal information on that user from
-Google.
-
-To do this you'll need to install the `google-auth-library` npm package as described in the ["Authenticate with a backend server" guide](https://developers.google.com/identity/sign-in/web/backend-auth).
-
-```ts
-import {
- AuthenticationStrategy,
-  ExternalAuthenticationService,
-  Injector,
-  RequestContext,
-  User,
-} from '@vendure/core';
-import { OAuth2Client } from 'google-auth-library';
-import { DocumentNode } from 'graphql';
-import gql from 'graphql-tag';
-
-export type GoogleAuthData = {
-  token: string;
-};
-
-export class GoogleAuthenticationStrategy implements AuthenticationStrategy<GoogleAuthData> {
-  readonly name = 'google';
-  private client: OAuth2Client;
-  private externalAuthenticationService: ExternalAuthenticationService;
-
-  constructor(private clientId: string) {
-    // The clientId is obtained by creating a new OAuth client ID as described
-    // in the Google guide linked above.
-    this.client = new OAuth2Client(clientId);
-  }
-
-  init(injector: Injector) {
-    // The ExternalAuthenticationService is a helper service which encapsulates much
-    // of the common functionality related to dealing with external authentication
-    // providers.
-    this.externalAuthenticationService = injector.get(ExternalAuthenticationService);
-  }
-
-  defineInputType(): DocumentNode {
-    // Here we define the expected input object expected by the `authenticate` mutation
-    // under the "google" key.
-    return gql`
-        input GoogleAuthInput {
-            token: String!
-        }
-    `;
-  }
-
-  async authenticate(ctx: RequestContext, data: GoogleAuthData): Promise<User | false> {
-    // Here is the logic that uses the token provided by the storefront and uses it
-    // to find the user data from Google.
-    const ticket = await this.client.verifyIdToken({
-        idToken: data.token,
-        audience: this.clientId,
-    });
-    const payload = ticket.getPayload();
-    if (!payload || !payload.email) {
-        return false;
-    }
-
-    // First we check to see if this user has already authenticated in our
-    // Vendure server using this Google account. If so, we return that
-    // User object, and they will be now authenticated in Vendure.
-    const user = await this.externalAuthenticationService.findCustomerUser(ctx, this.name, payload.sub);
-    if (user) {
-        return user;
-    }
-
-    // If no user was found, we need to create a new User and Customer based
-    // on the details provided by Google. The ExternalAuthenticationService
-    // provides a convenience method which encapsulates all of this into
-    // a single method call.
-    return this.externalAuthenticationService.createCustomerAndUser(ctx, {
-        strategy: this.name,
-        externalIdentifier: payload.sub,
-        verified: payload.email_verified || false,
-        emailAddress: payload.email,
-        firstName: payload.given_name,
-        lastName: payload.family_name,
-    });
-  }
-}
-```
-
-## Example: Facebook authentication
-
-This example demonstrates how to implement a Facebook login flow.
-
-### Storefront setup
-
-In this example, we are assuming the use of the [Facebook SDK for JavaScript](https://developers.facebook.com/docs/javascript/) in the storefront.
-
-An implementation in React might look like this:
-
-```tsx
-/**
- * Renders a Facebook login button.
- */
-export const FBLoginButton = () => {
-  const fnName = `onFbLoginButtonSuccess`;
-  const router = useRouter();
-  const [error, setError] = useState('');
-  const [socialLoginMutation] = useMutation(AuthenticateDocument);
-
-  useEffect(() => {
-    (window as any)[fnName] = function () {
-      FB.getLoginStatus(login);
-    };
-    return () => {
-      delete (window as any)[fnName];
-    };
-  }, []);
-
-  useEffect(() => {
-    window?.FB?.XFBML.parse();
-  }, []);
-
-  const login = async (response: any) => {
-    const { status, authResponse } = response;
-    if (status === 'connected') {
-      const result = await socialLoginMutation({ variables: { token: authResponse.accessToken } });
-      if (result.data?.authenticate.__typename === 'CurrentUser') {
-        // The user has logged in, refresh the browser
-        trackLogin('facebook');
-        router.reload();
-        return;
-      }
-    }
-    setError('An error occurred!');
-  };
-
-  return (
-    <div className="text-center" style={{ width: 188, height: 28 }}>
-      <FacebookSDK />
-      <div
-        className="fb-login-button"
-        data-width=""
-        data-size="medium"
-        data-button-type="login_with"
-        data-layout="default"
-        data-auto-logout-link="false"
-        data-use-continue-as="false"
-        data-scope="public_profile,email"
-        data-onlogin={`${fnName}();`}
-      />
-      {error && <div className="text-sm text-red-500">{error}</div>}
-    </div>
-  );
-};
-```
-
-```ts
-import {
-  AuthenticationStrategy,
-  ExternalAuthenticationService,
-  Injector,
-  Logger,
-  RequestContext,
-  User,
-  UserService,
-} from '@vendure/core';
-
-import { DocumentNode } from 'graphql';
-import gql from 'graphql-tag';
-import fetch from 'node-fetch';
-
-export type FacebookAuthData = {
-  token: string;
-};
-
-export type FacebookAuthConfig = {
-  appId: string;
-  appSecret: string;
-  clientToken: string;
-};
-
-export class FacebookAuthenticationStrategy implements AuthenticationStrategy<FacebookAuthData> {
-  readonly name = 'facebook';
-  private externalAuthenticationService: ExternalAuthenticationService;
-  private userService: UserService;
-
-  constructor(private config: FacebookAuthConfig) {}
-
-  init(injector: Injector) {
-    // The ExternalAuthenticationService is a helper service which encapsulates much
-    // of the common functionality related to dealing with external authentication
-    // providers.
-    this.externalAuthenticationService = injector.get(ExternalAuthenticationService);
-    this.userService = injector.get(UserService);
-  }
-
-  defineInputType(): DocumentNode {
-    // Here we define the expected input object expected by the `authenticate` mutation
-    // under the "google" key.
-    return gql`
-      input FacebookAuthInput {
-        token: String!
-      }
-    `;
-  }
-
-  private async getAppAccessToken() {
-    const resp = await fetch(
-      `https://graph.facebook.com/oauth/access_token?client_id=${this.config.appId}&client_secret=${this.config.appSecret}&grant_type=client_credentials`,
-    );
-    return await resp.json();
-  }
-
-  async authenticate(ctx: RequestContext, data: FacebookAuthData): Promise<User | false> {
-    const { token } = data;
-    const { access_token } = await this.getAppAccessToken();
-    const resp = await fetch(
-      `https://graph.facebook.com/debug_token?input_token=${token}&access_token=${access_token}`,
-    );
-    const result = await resp.json();
-
-    if (!result.data) {
-      return false;
-    }
-
-    const uresp = await fetch(`https://graph.facebook.com/me?access_token=${token}&fields=email,first_name,last_name`);
-    const uresult = (await uresp.json()) as { id?: string; email: string; first_name: string; last_name: string };
-
-    if (!uresult.id) {
-      return false;
-    }
-
-    const existingUser = await this.externalAuthenticationService.findCustomerUser(ctx, this.name, uresult.id);
-
-    if (existingUser) {
-      // This will select all the auth methods
-      return (await this.userService.getUserById(ctx, existingUser.id))!;
-    }
-
-    Logger.info(`User Create: ${JSON.stringify(uresult)}`);
-    const user = await this.externalAuthenticationService.createCustomerAndUser(ctx, {
-      strategy: this.name,
-      externalIdentifier: uresult.id,
-      verified: true,
-      emailAddress: uresult.email,
-      firstName: uresult.first_name,
-      lastName: uresult.last_name,
-    });
-
-    user.verified = true;
-    return user;
-  }
-}
-```
-
-## Example: Keycloak authentication
-
-Here's an example of an AuthenticationStrategy intended to be used on the Admin API. The use-case is when the company has an existing identity server for employees, and you'd like your Vendure shop admins to be able to authenticate with their existing accounts.
-
-This example uses [Keycloak](https://www.keycloak.org/), a popular open-source identity management server. To get your own Keycloak server up and running in minutes, follow the [Keycloak on Docker](https://www.keycloak.org/getting-started/getting-started-docker) guide.
-
-### Configure a login page & Admin UI
-
-In this example, we'll assume the login page is hosted at `http://intranet/login`. We'll also assume that a "login to Vendure" button has been added to that page and that the page is using the [Keycloak JavaScript adapter](https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter), which can be used to get the current user's authorization token:
-
-```JavaScript
-vendureLoginButton.addEventListener('click', () => {
-  return graphQlQuery(`
-    mutation Authenticate($token: String!) {
-      authenticate(input: {
-        keycloak: {
-          token: $token
-        }
-      }) {
-        ...on CurrentUser { id }
-      }
-    }`,
-    { token: keycloak.token },
-  )
-  .then((result) => {
-      if (result.data?.authenticate.user) {
-          // successfully authenticated - redirect to Vendure Admin UI
-          window.location.replace('http://localhost:3000/admin');
-      }
-  });
-});
-```
-
-We also need to tell the Admin UI application about the custom login URL, since we have no need for the default "username/password" login form. This can be done by setting the [`loginUrl` property]({{< relref "admin-ui-config" >}}#loginurl) in the AdminUiConfig:
-
-```ts
-// vendure-config.ts
-plugins: [
-  AdminUiPlugin.init({
-    port: 5001,
-    adminUiConfig: {
-      loginUrl: 'http://intranet/login',
-    },
-  }),
-]
-```
-
-### Backend
-
-The backend part is very similar to the Google authentication example (they both use the OpenID Connect standard), so we'll not duplicate the explanatory comments here:
-
-Install `@nestjs/axios`.
-
-```ts
-import { HttpService } from '@nestjs/axios';
-import {
-    AuthenticationStrategy,
-    ExternalAuthenticationService,
-    Injector,
-    Logger,
-    RequestContext,
-    RoleService,
-    User,
-} from '@vendure/core';
-import { DocumentNode } from 'graphql';
-import gql from 'graphql-tag';
-
-export type KeycloakAuthData = {
-    token: string;
-};
-
-export class KeycloakAuthenticationStrategy implements AuthenticationStrategy<KeycloakAuthData> {
-  readonly name = 'keycloak';
-  private externalAuthenticationService: ExternalAuthenticationService;
-  private httpService: HttpService;
-  private roleService: RoleService;
-
-  init(injector: Injector) {
-    this.externalAuthenticationService = injector.get(ExternalAuthenticationService);
-    this.httpService = injector.get(HttpService);
-    this.roleService = injector.get(RoleService);
-  }
-
-  defineInputType(): DocumentNode {
-    return gql`
-      input KeycloakAuthInput {
-        token: String!
-      }
-    `;
-  }
-
-  async authenticate(ctx: RequestContext, data: KeycloakAuthData): Promise<User | false> {
-    const { data: userInfo } = await this.httpService
-      .get('http://localhost:9000/auth/realms/myrealm/protocol/openid-connect/userinfo', {
-          headers: {
-              Authorization: `Bearer ${data.token}`,
-          },
-      }).toPromise();
-
-    if (!userInfo) {
-        return false;
-    }
-    const user = await this.externalAuthenticationService.findAdministratorUser(ctx, this.name, userInfo.sub);
-    if (user) {
-        return user;
-    }
-
-    // When creating an Administrator, we need to know what Role(s) to assign.
-    // In this example, we've created a "merchant" role and assign that to all
-    // new Administrators. In a real implementation, you can have more complex
-    // logic to map an external user to a given role.
-    const roles = await this.roleService.findAll();
-    const merchantRole = roles.items.find((r) => r.code === 'merchant');
-    if (!merchantRole) {
-        Logger.error(`Could not find "merchant" role`);
-        return false;
-    }
-
-    return this.externalAuthenticationService.createAdministratorAndUser(ctx, {
-        strategy: this.name,
-        externalIdentifier: userInfo.sub,
-        identifier: userInfo.preferred_username,
-        emailAddress: userInfo.email,
-        firstName: userInfo.given_name,
-        lastName: userInfo.family_name,
-        roles: [merchantRole],
-    });
-  }
-}
-```

BIN
docs/docs/guides/developer-guide/channels/channels_currencies_diagram.png


BIN
docs/docs/guides/developer-guide/channels/channels_diagram.png


BIN
docs/docs/guides/developer-guide/channels/channels_prices_diagram.png


+ 0 - 69
docs/docs/guides/developer-guide/channels/index.md

@@ -1,69 +0,0 @@
----
-title: "Channels"
-showtoc: true
----
-
-# Channels
-
-Channels are a feature of Vendure which allows multiple sales channels to be represented in a single Vendure instance. A Channel allows you to:
-
-* Set Channel-specific currency, language, tax and shipping defaults
-* Assign only specific Products to the Channel (with Channel-specific prices)
-* Create Administrator roles limited to the Channel
-* Assign specific StockLocations, Assets, Facets, Collections, Promotions, ShippingMethods & PaymentMethods to the Channel
-* Have Orders and Customers associated with specific Channels.
-
-Every Vendure server always has a **default Channel**, which contains _all_ entities. Subsequent channels can then contain a subset of the above entities.
-
-![channels_diagram.png](channels_diagram.png)
-
-Use-cases of Channels include:
-
-* Multi-region stores, where there is a distinct website for each territory with its own available inventory, pricing, tax and shipping rules.
-* Creating distinct rules and inventory for different sales channels such as Amazon.
-* Specialized stores offering a subset of the main inventory.
-* Implementing [multi-vendor marketplace]({{< relref "multi-vendor-marketplaces" >}}) applications.
-
-## Channels, Currencies & Prices
-
-Each Channel has a set of `availableCurrencyCodes`, and one of these is designated as the `defaultCurrencyCode`, which sets the currency for all monetary values in that channel.
-
-![channels_currencies_diagram.png](channels_currencies_diagram.png)
-
-Internally, there is a one-to-many relation from [ProductVariant]({{< relref "product-variant" >}}) to [ProductVariantPrice]({{< relref "product-variant-price" >}}). So the ProductVariant does _not_ hold a price for the product - this is actually stored on the ProductVariantPrice entity, and there will be at least one for each Channel to which the ProductVariant has been assigned.
-
-![channels_prices_diagram.png](channels_prices_diagram.png)
-
-{{< alert "warning" >}}
-**Note:** in the diagram above that the ProductVariant is **always assigned to the default Channel**, and thus will have a price in the default channel too. Likewise, the default Channel also has a defaultCurrencyCode.
-{{< /alert >}}
-
-### Use-case: single shop
-
-This is the simplest set-up. You just use the default Channel for everything
-
-### Use-case: Multiple separate shops
-
-Let's say you are running multiple distinct businesses, each with its own distinct inventory and possibly different currencies. In this case, you set up a Channel for each shop and create the Product & Variants in the relevant shop's Channel.
-
-The default Channel can then be used by the superadmin for administrative purposes, but other than that the default Channel would not be used. Storefronts would only target a specific shop's Channel.
-
-### Use-case: Multiple shops sharing inventory
-
-Let's say you have a single inventory but want to split it between multiple shops. There might be overlap in the inventory, e.g. the US & EU shops share 80% of inventory, and then the rest is specific to either shop.
-
-In this case, you can create the entire inventory in the default Channel and then assign the Products & ProductVariants to each Channel as needed, setting the price as appropriate for the currency used by each shop.
-
-{{< alert "warning" >}}
-**Note:** When creating a new Product & ProductVariants inside a sub-Channel, it will also **always get assigned to the default Channel**. If your sub-Channel uses a different currency from the default Channel, you should be aware that in the default Channel, that ProductVariant will be assigned the **same price** as it has in the sub-Channel. If the currency differs between the Channels, you need to make sure to set the correct price in the default Channel if you are exposing it to Customers via a storefront. 
-{{< /alert >}}
-
-### Use-case: Multi-vendor marketplace
-
-This is the most advanced use of channels. For a detailed guide to this use-case, see our [Multi-vendor marketplace guide]({{< relref "multi-vendor-marketplaces" >}}).
-
-
-## How to set the channel when using the GraphQL API
-
-To specify which channel to use when making an API call, set the `'vendure-token'` header to match the token of the desired Channel.
-

+ 0 - 0
docs/docs/guides/getting-started/configuration.md → docs/docs/guides/developer-guide/configuration/index.md


+ 0 - 0
docs/docs/guides/concepts/custom-fields/custom-fields-data-table.webp → docs/docs/guides/developer-guide/custom-fields/custom-fields-data-table.webp


+ 0 - 0
docs/docs/guides/concepts/custom-fields/custom-fields-ui.webp → docs/docs/guides/developer-guide/custom-fields/custom-fields-ui.webp


+ 0 - 0
docs/docs/guides/concepts/custom-fields/index.md → docs/docs/guides/developer-guide/custom-fields/index.md


BIN
docs/docs/guides/developer-guide/customizing-the-order-process/custom-order-ui.webp


+ 0 - 173
docs/docs/guides/developer-guide/customizing-the-order-process/index.md

@@ -1,173 +0,0 @@
----
-title: 'Customizing the Order Process'
-showtoc: true
----
-
-# Customizing the Order Process
-
-Vendure defines an order process which is based on a [finite state machine]({{< relref "fsm" >}}). This means that the [`Order.state` property]({{< relref "order" >}}#state) will be one of a set of [pre-defined states]({{< relref "order-process" >}}#orderstate). From the current state, the Order can then transition (change) to another state, and the available next states depend on what the current state is.
-
-So, as an example, all orders begin in the `AddingItems` state. This means that the Customer is adding items to his or her shopping cart. From there, the Order can transition to the `ArrangingPayment` state. A diagram of the default states and transitions can be found in the [Order Workflow guide]({{< relref "order-workflow" >}}).
-
-## Customizing the Default Order Process
-
-Vendure ships with a [defaultOrderProcess]({{< relref "order-process" >}}#defaultorderprocess) which is suitable for most use-cases. However, it is possible to customize the process to better match your business needs. For example, you might want to disable some of the constraints that are imposed by the default process, such as the requirement that a Customer must have a shipping address before the Order can be completed.
-
-This can be done by creating a custom version of the default process using the [configureDefaultOrderProcess]({{< relref "order-process" >}}#configuredefaultorderprocess) function, and then passing it to the [`OrderOptions.process`]({{< relref "order-options" >}}#process) config property.
-
-```ts
-import { configureDefaultOrderProcess, VendureConfig } from '\@vendure/core';
-
-const myCustomOrderProcess = configureDefaultOrderProcess({
-  // Disable the constraint that requires
-  // Orders to have a shipping method assigned
-  // before payment.
-  arrangingPaymentRequiresShipping: false,
-});
-
-export const config: VendureConfig = {
-  orderOptions: {
-    process: [myCustomOrderProcess],
-  },
-};
-```
-
-## Defining custom states and transitions
-
-Sometimes you might need to extend things beyond what is provided by the default Order process to better match your business needs. This is done by defining one or more [`OrderProcess`]({{< relref "order-process" >}}) objects and passing them to the [`OrderOptions.process`]({{< relref "order-options" >}}#process) config property.
-
-### Example: Adding a new state
-
-Let's say your company can only sell to customers with a valid EU tax ID. We'll assume that you've already used a [custom field]({{< relref "customizing-models" >}}) to store that code on the Customer entity.
-
-Now you want to add a step _before_ the customer handles payment, where we can collect and verify the tax ID.
-
-So we want to change the default process of:
-
-```text
-AddingItems -> ArrangingPayment
-```
-
-to instead be:
-
-```text
-AddingItems -> ValidatingCustomer -> ArrangingPayment
-```
-
-Here's how we would define the new state:
-
-```ts
-// customer-validation-process.ts
-import { OrderProcess } from '@vendure/core';
-
-export const customerValidationProcess: OrderProcess<'ValidatingCustomer'> = {
-  transitions: {
-    AddingItems: {
-      to: ['ValidatingCustomer'],
-      mergeStrategy: 'replace',
-    },
-    ValidatingCustomer: {
-      to: ['ArrangingPayment', 'AddingItems'],
-    },
-  },
-};
-```
-
-This object means:
-
-* the `AddingItems` state may _only_ transition to the `ValidatingCustomer` state (`mergeStrategy: 'replace'` tells Vendure to discard any existing transition targets and replace with this one). 
-* the `ValidatingCustomer` may transition to the `ArrangingPayment` state (assuming the tax ID is valid) or back to the `AddingItems` state.
-
-And then add this configuration to our main VendureConfig:
-
-```ts
-// vendure-config.ts
-import { defaultOrderProcess, VendureConfig } from '@vendure/core';
-import { customerValidationProcess } from './customer-validation-process';
-
-export const config: VendureConfig = {
-  // ...
-  orderOptions: {
-    process: [defaultOrderProcess, customerValidationProcess],
-  },
-};
-```
-
-Note that we also include the `defaultOrderProcess` in the array, otherwise we will lose all the default states and transitions.
-
- To add multiple new States you need to extend the generic type like this:
- ```ts
-import { OrderProcess } from '@vendure/core';
-
-export const customerValidationProcess: OrderProcess<'ValidatingCustomer'|'AnotherState'> = {...}
- ```
-This way multiple custom states gets defined.
-
-### Example: Intercepting a state transition
-
-Now we have defined our new `ValidatingCustomer` state, but there is as yet nothing to enforce that the tax ID is valid. To add this constraint, we'll use the [`onTransitionStart` state transition hook]({{< relref "state-machine-config" >}}#ontransitionstart).
-
-This allows us to perform our custom logic and potentially prevent the transition from occurring. We will also assume that we have a provider named `TaxIdService` available which contains the logic to validate a tax ID.
-
-```ts
-// customer-validation-process.ts
-
-// We declare this in the outer scope and can then use it 
-// in our onTransitionStart function.
-let taxIdService: TaxIdService;
-
-const customerValidationProcess: OrderProcess<'ValidatingCustomer'> = {
-  transitions: {
-    AddingItems: {
-      to: ['ValidatingCustomer'],
-      mergeStrategy: 'replace',
-    },
-    ValidatingCustomer: {
-      to: ['ArrangingPayment', 'AddingItems'],
-    },
-  },
-
-  // The init method allows us to inject services
-  // and other providers
-  init(injector) {
-    taxIdService = injector.get(TaxIdService);
-  },
-
-  // The logic for enforcing our validation goes here
-  async onTransitionStart(fromState, toState, data) {
-    if (fromState === 'ValidatingCustomer' && toState === 'ArrangingPayment') {
-      const isValid = await taxIdService.verifyTaxId(data.order.customer);
-      if (!isValid) {
-        // Returning a string is interpreted as an error message.
-        // The state transition will fail.
-        return `The tax ID is not valid`;
-      }
-    }
-  },
-};
-
-```
-
-## TypeScript Typings
-
-To make your custom states compatible with standard services you should declare your new states in the following way:
-
-```ts
-// types.ts
-import { CustomOrderStates } from '@vendure/core';
-
-declare module '@vendure/core' {
-  interface CustomOrderStates {
-    ValidatingCustomer: never;
-  }
-}
-```
-
-This technique uses advanced TypeScript features - [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces) and  [ambient modules](https://www.typescriptlang.org/docs/handbook/modules.html#ambient-modules).
-
-## Controlling custom states in the Admin UI
-
-If you have defined custom order states, the Admin UI will allow you to manually transition an 
-order from one state to another:
-
-![./custom-order-ui.webp](./custom-order-ui.webp)

+ 0 - 0
docs/docs/guides/concepts/data-model/admin-role.webp → docs/docs/guides/developer-guide/data-model/admin-role.webp


+ 0 - 0
docs/docs/guides/concepts/data-model/channels.webp → docs/docs/guides/developer-guide/data-model/channels.webp


+ 0 - 0
docs/docs/guides/concepts/data-model/collection-filters.webp → docs/docs/guides/developer-guide/data-model/collection-filters.webp


+ 0 - 0
docs/docs/guides/concepts/data-model/collections.webp → docs/docs/guides/developer-guide/data-model/collections.webp


+ 0 - 0
docs/docs/guides/concepts/data-model/customer.webp → docs/docs/guides/developer-guide/data-model/customer.webp


+ 0 - 0
docs/docs/guides/concepts/data-model/facets.webp → docs/docs/guides/developer-guide/data-model/facets.webp


+ 1 - 1
docs/docs/guides/concepts/data-model/index.mdx → docs/docs/guides/developer-guide/data-model/index.mdx

@@ -1,5 +1,5 @@
 ---
-title: 'Vendure Data Model'
+title: 'Data Model'
 sidebar_position: 0
 ---
 

+ 0 - 0
docs/docs/guides/concepts/data-model/order.webp → docs/docs/guides/developer-guide/data-model/order.webp


+ 0 - 0
docs/docs/guides/concepts/data-model/products-variants.webp → docs/docs/guides/developer-guide/data-model/products-variants.webp


+ 0 - 9
docs/docs/guides/developer-guide/developer-guide.md

@@ -1,9 +0,0 @@
----
-title: "Developer Guide"
-weight: 1
-showtoc: false
----
- 
-# Developer Guide
-
-This section contains guides for developers building applications with Vendure.

+ 0 - 59
docs/docs/guides/developer-guide/job-queue/index.md

@@ -1,59 +0,0 @@
----
-title: 'Job Queue'
-showtoc: true
----
-
-# The Vendure Job Queue
-
-## What is a job queue?
-
-Vendure uses a [job queue](https://en.wikipedia.org/wiki/Job_queue) to handle the processing of certain tasks which are typically too slow to run in the normal request-response cycle. A normal request-response looks like this:
-
-![./job_queue_req_res.png](./job_queue_req_res.png)
-
-In the normal request-response, all intermediate tasks (looking up data in the database, performing business logic etc.) occur before the response can be returned. For most operations this is fine, since those intermediate tasks are very fast.
-
-Some operations however will need to perform much longer-running tasks. For example, updating the search index on thousands of products could take up to a minute or more. In this case, we certainly don't want to delay the reponse until that processing has completed. That's where a job queue comes in:
-
-![./job_queue_req_res_with_queue.png](./job_queue_req_res_with_queue.png)
-
-## What does Vendure use the job queue for?
-
-By default, Vendure uses the job queue for the following tasks:
-
-- Re-building the search index
-- Updating the search index when changes are made to Products, ProductVariants, Assets etc.
-- Updating the contents of Collections
-- Sending transactional emails
-
-## How does the Job Queue work?
-
-This diagram illustrates the job queue mechanism:
-
-![./job_queue_sequence.png](./job_queue_sequence.png)
-
-The server adds jobs to the queue. The worker then picks up these jobs from the queue and processes them in sequence, one by one (it is possible to increase job queue throughput by [running multiple workers]({{< relref "vendure-worker" >}}#multiple-workers)).
-
-### JobQueueStrategy
-
-The actual queue part is defined by the configured [JobQueueStrategy]({{< relref "job-queue-strategy" >}}).
-
-If no strategy is defined, Vendure uses an [in-memory store]({{< relref "in-memory-job-queue-strategy" >}}) of the contents of each queue. While this has the advantage of requiring no external dependencies, it is not suitable for production because when the server is stopped, the entire queue will be lost and any pending jobs will never be processed. Moreover, it cannot be used when running the worker as a separate process.
-
-A better alternative is to use the [DefaultJobQueuePlugin]({{< relref "default-job-queue-plugin" >}}) (which will be used in a standard `@vendure/create` installation), which configures Vendure to use the [SqlJobQueueStrategy]({{< relref "sql-job-queue-strategy" >}}). This strategy uses the database as a queue, and means that even if the Vendure server stops, pending jobs will be persisted and upon re-start, they will be processed.
-
-It is also possible to implement your own JobQueueStrategy to take advantage of other technologies. Examples include RabbitMQ, Google Cloud Pub Sub & Amazon SQS. It may make sense to implement a custom strategy based on one of these if the default database-based approach does not meet your performance requirements.
-
-## Job Queue Performance
-
-It is common for larger Vendure projects to define multiple custom job queues, When using the [DefaultJobQueuePlugin]({{< relref "default-job-queue-plugin" >}}) with many queues, performance may be impacted. This is because the `SqlJobQueueStrategy` uses polling to check for new jobs in the database. Each queue will (by default) query the database every 200ms. So if there are 10 queues, this will result in a constant 50 queries/second.
-
-In this case it is recommended to try the [BullMQJobQueuePlugin]({{< relref "bull-mqjob-queue-plugin" >}}), which uses an efficient push-based strategy built on Redis.
-
-## Using Job Queues in a plugin
-
-If you create a [Vendure plugin]({{< relref "/guides/plugins" >}}) which involves some long-running tasks, you can also make use of the job queue. See the [JobQueue plugin example]({{< relref "using-job-queue-service" >}}) for a detailed annotated example.
-
-{{< alert "primary" >}}
-A real example of this can be seen in the [EmailPlugin source](https://github.com/vendure-ecommerce/vendure/blob/master/packages/email-plugin/src/plugin.ts)
-{{< /alert >}}

BIN
docs/docs/guides/developer-guide/job-queue/job_queue_req_res.png


BIN
docs/docs/guides/developer-guide/job-queue/job_queue_req_res_with_queue.png


BIN
docs/docs/guides/developer-guide/job-queue/job_queue_sequence.png


+ 0 - 107
docs/docs/guides/developer-guide/migrations.md

@@ -1,107 +0,0 @@
----
-title: "Migrations"
-showtoc: true
----
-# Migrations
-
-Database migrations are needed whenever the database schema changes. This can be caused by:
-
-* changes to the [CustomFields](https://www.vendure.io/docs/developer-guide/customizing-models/) configuration.
-* new [database entities defined by plugins](https://www.vendure.io/docs/typescript-api/plugin/vendure-plugin-metadata/#entities)
-* occasional changes to the core Vendure database schema when updating to newer versions
-
-## Synchronize vs migrate
-
-TypeORM (which Vendure uses to interact with the database) has a `synchronize` option which, when set to `true`, will automatically update your database schema to reflect the current Vendure configuration.
-
-This is convenient while developing, but should not be used in production, since a misconfiguration could potentially delete production data. In this case, migrations should be used.
-
-## Migrations in Vendure
-
-Vendure exposes a some helper function which wrap around the underlying [TypeORM migration functionality](https://typeorm.io/#/migrations). The reason for using these helper functions rather than using the TypeORM CLI directly is that Vendure generates additional schema information based on custom fields and plugin configurations which are not available to the TypeORM CLI.
-
-To run and revert migrations, ensure that the `dbConnectionOptions.migrations` option is set in your VendureConfig:
-
-```ts
-export const config: VendureConfig = {
-  // ...
-  dbConnectionOptions: {
-    // ...
-    migrations: [path.join(__dirname, '../migrations/*.ts')],
-  }
-}
-```
-
-### Generate a migration
-
-The [`generateMigration` function]({{< relref "generate-migration" >}}) will compare the provided VendureConfig against the current database schema and generate a new migration file containing SQL statements which, when applied to the current database, will modify the schema to fit with the configuration. It will also contain statements to revert these changes.
-
-### Run migrations
-
-The [`runMigrations` function]({{< relref "run-migrations" >}}) will apply any migrations files found according to the pattern provided to `dbConnectionOptions.migrations` to the database. TypeORM keeps a track of which migrations have already been applied, so running this function multiple times will not apply the same migration more than once.
-
-{{% alert "warning" %}}
-⚠ TypeORM will attempt to run each migration inside a transaction. This means that if one of the migration commands fails, then the entire transaction will be rolled back to its original state.
-
-_However_ this is **not supported by MySQL / MariaDB**. This means that when using MySQL or MariaDB, errors in your migration script could leave your database in a broken or inconsistent state. Therefore it is **critical** that you first create a backup of your database before running a migration.
-{{< /alert >}}
-
-### Revert a migration
-
-The [`revertLastMigration` function]({{< relref "revert-last-migration" >}}) will revert the last applied migration. If run again it will then revert the one before that, and so on.
-
-## Example
-
-Here is an example script (which ships with projects generated with `@vendure/create`) which provides a command-line program for managing migrations:
-
-```ts
-// migrations.ts
-import { generateMigration, revertLastMigration, runMigrations } from '@vendure/core';
-import program from 'commander';
-
-import { config } from './src/config';
-
-program
-    .command('generate <name>')
-    .description('Generate a new migration file with the given name')
-    .action(name => {
-        return generateMigration(config, { name, outputDir: './migrations' });
-    });
-
-program
-    .command('run')
-    .description('Run all pending migrations')
-    .action(() => {
-        return runMigrations(config);
-    });
-
-program
-    .command('revert')
-    .description('Revert the last applied migration')
-    .action(() => {
-        return revertLastMigration(config);
-    });
-
-program.parse(process.argv);
-```
-
-This script can then be run e.g. using `ts-node`:
-
-```shell
-ts-node migration.ts generate my-migration
-```
-
-This will generate a new migration in the directory specified by the `dbConnectionOptions.migrations` option:
-
-```ts
-dbConnectionOptions: {
-  migrations: [path.join(__dirname, '../migrations/*.ts')],
-}
-```
-
-The migration file will be named `migrations/<timestamp>-my-migration.ts`. You should inspect this file to verify the migration commands to be run.
-
-You can then run all migrations which have not yet been run with:
-```shell
-ts-node migration.ts run
-```

+ 221 - 0
docs/docs/guides/developer-guide/migrations/index.md

@@ -0,0 +1,221 @@
+---
+title: "Migrations"
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+Database migrations are needed whenever the database schema changes. This can be caused by:
+
+* changes to the [custom fields](/guides/developer-guide/custom-fields/) configuration.
+* new [database entities defined by plugins](/reference/typescript-api/plugin/vendure-plugin-metadata#entities)
+* occasional changes to the core Vendure database schema when updating to newer versions
+
+## Synchronize vs migrate
+
+TypeORM (which Vendure uses to interact with the database) has a `synchronize` option which, when set to `true`, will automatically update your database schema to reflect the current Vendure configuration. This is equivalent to automatically generating and running a migration _every time_ the server starts up.
+
+This is convenient while developing, but **should not be used in production**, since a misconfiguration could potentially delete production data. In this case, migrations should be used.
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+
+export const config: VendureConfig = {
+    // ...
+    dbConnectionOptions: {
+        // ...
+        // highlight-next-line
+        synchronize: false,
+    }
+};
+```
+
+## Migration workflow
+
+This section assumes a standard Vendure installation based on `@vendure/create`. 
+
+Let's assume you have defined a new "keywords" custom field on the Product entity. The next time you start your server you'll see a message like this:
+
+```bash
+[server] Your database schema does not match your current configuration. Generate a new migration for the following changes:
+[server]  - ALTER TABLE "product" ADD "customFieldsKeywords" character varying(255)
+```
+
+Since we have `synchronize` set to `false`, we need to generate a migration to apply these changes to the database. The workflow for this is as follows:
+
+### 1. Generate a migration
+
+Run the following command:
+
+
+<Tabs>
+<TabItem value="npm" label="npm" default>
+
+```
+npm run migration:generate add-keywords-field
+```
+
+</TabItem>
+<TabItem value="yarn" label="yarn">
+
+```
+yarn migration:generate add-keywords-field
+```
+
+</TabItem>
+</Tabs>
+
+### 2. Check the migration file
+
+This will have created a new migration file in the `src/migrations` directory. Open this file and check that it looks correct. It should look something like this:
+
+```ts title="src/migrations/1690558104092-add-keywords-field.ts"
+import {MigrationInterface, QueryRunner} from "typeorm";
+
+export class addKeywordsField1690558104092 implements MigrationInterface {
+
+   public async up(queryRunner: QueryRunner): Promise<any> {
+        await queryRunner.query(`ALTER TABLE "product" ADD "customFieldsKeywords" character varying(255)`, undefined);
+   }
+
+   public async down(queryRunner: QueryRunner): Promise<any> {
+        await queryRunner.query(`ALTER TABLE "product" DROP COLUMN "customFieldsKeywords"`, undefined);
+   }
+
+}
+```
+
+The `up()` function is what will be executed when the migration is run. The `down()` function is what will be executed if the migration is reverted. In this case, the `up()` function is adding a new column to the `product` table, and the `down()` function is removing it.
+
+:::note
+The exact query will depend on the database you are using. The above example is for PostgreSQL.
+:::
+
+### 3. Run the migration
+
+Assuming the migration file looks correct, the next time you start the server, the migration will
+be run automatically. This is because the `runMigrations` function is called in the `src/index.ts` file:
+
+```ts title="src/index.ts"
+import { bootstrap, runMigrations } from '@vendure/core';
+import { config } from './vendure-config';
+
+// highlight-next-line
+runMigrations(config)
+    .then(() => bootstrap(config))
+    .catch(err => {
+        console.log(err);
+    });
+```
+
+It is also possible to run the migration manually without starting the server:
+
+<Tabs>
+<TabItem value="npm" label="npm" default>
+
+```
+npm run migration:run
+```
+
+</TabItem>
+<TabItem value="yarn" label="yarn">
+
+```
+yarn migration:run
+```
+
+</TabItem>
+</Tabs>
+
+:::caution
+TypeORM will attempt to run each migration inside a transaction. This means that if one of the migration commands fails, then the entire transaction will be rolled back to its original state.
+
+_However_ this is **not supported by MySQL / MariaDB**. This means that when using MySQL or MariaDB, errors in your migration script could leave your database in a broken or inconsistent state. Therefore it is **critical** that you first create a backup of your database before running a migration.
+
+You can read more about this issue in [typeorm/issues/7054](https://github.com/typeorm/typeorm/issues/7054)
+:::
+
+## Migrations in-depth
+
+Now we'll dive into what's going on under the hood.
+
+Vendure exposes a some helper function which wrap around the underlying [TypeORM migration functionality](https://typeorm.io/migrations). The reason for using these helper functions rather than using the TypeORM CLI directly is that Vendure generates additional schema information based on custom fields and plugin configurations which are not available to the TypeORM CLI.
+
+In a standard Vendure installation, you'll see the following migration script in your project root directory:
+
+```ts title="migration.ts"
+import { generateMigration, revertLastMigration, runMigrations } from '@vendure/core';
+import program from 'commander';
+
+import { config } from './src/vendure-config';
+
+program
+    .command('generate <name>')
+    .description('Generate a new migration file with the given name')
+    .action(name => {
+        return generateMigration(config, { name, outputDir: './src/migrations' });
+    });
+
+program
+    .command('run')
+    .description('Run all pending migrations')
+    .action(() => {
+        return runMigrations(config);
+    });
+
+program
+    .command('revert')
+    .description('Revert the last applied migration')
+    .action(() => {
+        return revertLastMigration(config);
+    });
+
+program.parse(process.argv);
+```
+
+and a set of scripts in your `package.json` file:
+
+```json
+{
+  // ...
+  "scripts": {
+    "migration:generate": "ts-node migration.ts generate",
+    "migration:run": "ts-node migration.ts run",
+    "migration:revert": "ts-node migration.ts revert"
+  }
+}
+```
+
+When running and reverting migrations, Vendure is looking for migration files in the directory specified by the `dbConnectionOptions.migrations` option is set in your VendureConfig:
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+import path from 'path';
+
+export const config: VendureConfig = {
+    // ...
+    dbConnectionOptions: {
+        // ...
+        // highlight-next-line
+        migrations: [path.join(__dirname, './migrations/*.+(js|ts)')],
+    }
+};
+```
+
+TypeORM keeps track of which migrations have been run by creating a new `migrations` table in your database, and each time a migration is successfully run
+it adds a row to this table with the name of the migration class and a timestamp. This prevents the same migration from being run twice, and also allows
+TypeORM to know which migration to revert when the `revertLastMigration` function is called.
+
+![Migrations table](./migration.webp)
+
+These are the underlying function exposed by Vendure which are used to generate, run and revert migrations:
+
+- [`generateMigration` function](/reference/typescript-api/migration/generate-migration/)
+- [`runMigrations` function](/reference/typescript-api/migration/run-migrations/)
+- [`revertLastMigration` function](/reference/typescript-api/migration/revert-last-migration/)
+
+### Reverting a migration
+
+The `revertLastMigration` function will revert the last applied migration by applying the `down()` method. If run again it will then revert the one before that, and so on.
+In doing so, it will also remove the corresponding row from the `migrations` table.
+

BIN
docs/docs/guides/developer-guide/migrations/migration.webp


+ 0 - 0
docs/docs/guides/getting-started/overview/Vendure_docs-architecture.webp → docs/docs/guides/developer-guide/overview/Vendure_docs-architecture.webp


+ 0 - 0
docs/docs/guides/getting-started/overview/index.md → docs/docs/guides/developer-guide/overview/index.md


+ 0 - 293
docs/docs/guides/developer-guide/payment-integrations/index.md

@@ -1,293 +0,0 @@
----
-title: 'Payment Integrations'
-showtoc: true
----
-
-# Payment Integrations
-
-Vendure can support many kinds of payment workflows, such as authorizing and capturing payment in a single step upon checkout or authorizing on checkout and then capturing on fulfillment.
-
-{{< alert "primary" >}}
-For complete working examples of real payment integrations, see the [payments-plugins](https://github.com/vendure-ecommerce/vendure/tree/master/packages/payments-plugin/src)
-{{< /alert >}}
-
-## Authorization & Settlement
-
-Typically, there are 2 parts to an online payment: **authorization** and **settlement**:
-
--   **Authorization** is the process by which the customer's bank is contacted to check whether the transaction is allowed. At this stage, no funds are removed from the customer's account.
--   **Settlement** (also known as "capture") is the process by which the funds are transferred from the customer's account to the merchant.
-
-Some merchants do both of these steps at once, when the customer checks out of the store. Others do the authorize step at checkout, and only do the settlement at some later point, e.g. upon shipping the goods to the customer.
-
-This two-step workflow can also be applied to other non-card forms of payment: e.g. if providing a "payment on delivery" option, the authorization step would occur on checkout, and the settlement step would be triggered upon delivery, either manually by an administrator of via an app integration with the Admin API.
-
-## Creating an integration
-
-Payment integrations are created by defining a new [PaymentMethodHandler]({{< relref "payment-method-handler" >}}) and passing that handler into the [`paymentOptions.paymentMethodHandlers`]({{< relref "payment-options" >}}) array in the VendureConfig.
-
-```ts
-import {
-  CancelPaymentResult,
-  CancelPaymentErrorResult,
-  PaymentMethodHandler,
-  VendureConfig,
-  CreatePaymentResult,
-  SettlePaymentResult,
-  SettlePaymentErrorResult
-} from '@vendure/core';
-import { CancelPaymentErrorResult } from '@vendure/core/src/index';
-import { sdk } from 'payment-provider-sdk';
-
-/**
- * This is a handler which integrates Vendure with an imaginary
- * payment provider, who provide a Node SDK which we use to
- * interact with their APIs.
- */
-const myPaymentIntegration = new PaymentMethodHandler({
-  code: 'my-payment-method',
-  description: [{
-    languageCode: LanguageCode.en,
-    value: 'My Payment Provider',
-  }],
-  args: {
-    apiKey: { type: 'string' },
-  },
-
-  /** This is called when the `addPaymentToOrder` mutation is executed */
-  createPayment: async (ctx, order, amount, args, metadata): Promise<CreatePaymentResult> => {
-    try {
-      const result = await sdk.charges.create({
-        amount,
-        apiKey: args.apiKey,
-        source: metadata.token,
-      });
-      return {
-        amount: order.total,
-        state: 'Authorized' as const,
-        transactionId: result.id.toString(),
-        metadata: {
-          cardInfo: result.cardInfo,
-          // Any metadata in the `public` field
-          // will be available in the Shop API,
-          // All other metadata is private and
-          // only available in the Admin API.
-          public: {
-            referenceCode: result.publicId,
-          }
-        },
-      };
-    } catch (err) {
-      return {
-        amount: order.total,
-        state: 'Declined' as const,
-        metadata: {
-          errorMessage: err.message,
-        },
-      };
-    }
-  },
-
-  /** This is called when the `settlePayment` mutation is executed */
-  settlePayment: async (ctx, order, payment, args): Promise<SettlePaymentResult | SettlePaymentErrorResult> => {
-    try {
-      const result = await sdk.charges.capture({
-        apiKey: args.apiKey,
-        id: payment.transactionId,
-      });
-      return { success: true };
-    } catch (err) {
-      return {
-        success: false,
-        errorMessage: err.message,
-      }
-    }
-  },
-  
-  /** This is called when a payment is cancelled. */  
-  cancelPayment: async (ctx, order, payment, args): Promise<CancelPaymentResult | CancelPaymentErrorResult> => {
-    try {
-      const result = await sdk.charges.cancel({
-        apiKey: args.apiKey,
-        id: payment.transactionId,
-      });
-      return { success: true };
-    } catch (err) {
-      return {
-        success: false,
-        errorMessage: err.message,
-      }
-    }
-  },
-});
-
-/**
- * We now add this handler to our config
- */
-export const config: VendureConfig = {
-    // ...
-    paymentOptions: {
-        paymentMethodHandlers: [myPaymentIntegration],
-    },
-};
-```
-
-{{% alert %}}
-**Dependency Injection**
-
-If your PaymentMethodHandler needs access to the database or other providers, see the [ConfigurableOperationDef Dependency Injection guide]({{< relref "configurable-operation-def" >}}#dependency-injection).
-{{< /alert >}}
-
-### Creating a PaymentMethod
-
-Once the PaymentMethodHandler is defined as above, you can use it to create a new PaymentMethod via the Admin UI (_Settings_ -> _Payment methods_, then _Create new payment method_) or via the Admin API `createPaymentMethod` mutation.
-
-## Payment flow
-
-1. Once the active Order has been transitioned to the ArrangingPayment state (see the [Order Workflow guide]({{< relref "order-workflow" >}})), one or more Payments are created by executing the [`addPaymentToOrder` mutation]({{< relref "/reference/graphql-api/shop/mutations#addpaymenttoorder" >}}). This mutation has a required `method` input field, which _must_ match the `code` of one of the configured PaymentMethodHandlers. In the case above, this would be set to `"my-payment-method"`.
-    ```graphql
-    mutation {
-        addPaymentToOrder(input: {
-            method: "my-payment-method",
-            metadata: { token: "<some token from the payment provider>" }) {
-            ...Order
-        }
-    }
-    ```
-    The `metadata` field is used to store the specific data required by the payment provider. E.g. some providers have a client-side part which begins the transaction and returns a token which must then be verified on the server side.
-2. This mutation internally invokes the [PaymentMethodHandler's `createPayment()` function]({{< relref "payment-method-config-options" >}}#createpayment). This function returns a [CreatePaymentResult object]({{< relref "payment-method-types" >}}#payment-method-types) which is used to create a new [Payment]({{< relref "/reference/typescript-api/entities/payment" >}}). If the Payment amount equals the order total, then the Order is transitioned to either the "PaymentAuthorized" or "PaymentSettled" state and the customer checkout flow is complete.
-
-### Single-step
-
-If the `createPayment()` function returns a result with the state set to `'Settled'`, then this is a single-step ("authorize & capture") flow, as illustrated below:
-
-![./payment_sequence_one_step.png](./payment_sequence_one_step.png)
-
-### Two-step
-
-If the `createPayment()` function returns a result with the state set to `'Authorized'`, then this is a two-step flow, and the settlement / capture part is performed at some later point, e.g. when shipping the goods, or on confirmation of payment-on-delivery.
-
-![./payment_sequence_two_step.png](./payment_sequence_two_step.png)
-
-## Custom Payment Flows
-
-If you need to support an entirely different payment flow than the above, it is also possible to do so by configuring a [PaymentProcess]({{< relref "payment-process" >}}). This allows new Payment states and transitions to be defined, as well as allowing custom logic to run on Payment state transitions.
-
-Here's an example which adds a new "Validating" state to the Payment state machine, and combines it with a [OrderProcess]({{< relref "order-process" >}}), [PaymentMethodHandler]({{< relref "payment-method-handler" >}}) and [OrderPlacedStrategy]({{< relref "order-placed-strategy" >}}).
-
-```ts
-// types.ts
-import {
-  defaultPaymentProcess,
-  defaultOrderprocess,
-  CustomOrderStates,
-  OrderProcess,
-  PaymentProcess,
-  PaymentMethodHandler,
-  LanguageCode,
-  OrderPlacedStrategy,
-  RequestContext
-} from '@vendure/core';
-
-/**
- * Declare your custom state in special interface to make it type-safe
- */
-declare module '@vendure/core' {
-  interface PaymentStates {
-    Validating: never;
-  }
-}
-
-/**
- * Define a new "Validating" Payment state, and set up the
- * permitted transitions to/from it.
- */
-const customPaymentProcess: PaymentProcess<'Validating'> = {
-  transitions: {
-    Created: {
-      to: ['Validating'],
-      mergeStrategy: 'replace',
-    },
-    Validating: {
-      to: ['Settled', 'Declined', 'Cancelled'],
-    },
-  },
-};
-
-/**
- * Define a new "ValidatingPayment" Order state, and set up the
- * permitted transitions to/from it.
- */
-const customOrderProcess: OrderProcess<'ValidatingPayment'> = {
-  transitions: {
-    ArrangingPayment: {
-      to: ['ValidatingPayment'],
-      mergeStrategy: 'replace',
-    },
-    ValidatingPayment: {
-      to: ['PaymentAuthorized', 'PaymentSettled', 'ArrangingAdditionalPayment'],
-    },
-  },
-};
-
-/**
- * This PaymentMethodHandler creates the Payment in the custom "Validating"
- * state.
- */
-const myPaymentHandler = new PaymentMethodHandler({
-  code: 'my-payment-handler',
-  description: [{ languageCode: LanguageCode.en, value: 'My payment handler' }],
-  args: {},
-  createPayment: (ctx, order, amount, args, metadata) => {
-    // payment provider logic omitted
-    return {
-      state: 'Validating' as any,
-      amount,
-      metadata,
-    };
-  },
-  settlePayment: (ctx, order, payment) => {
-    return {
-      success: true,
-    };
-  },
-});
-
-/**
- * This OrderPlacedStrategy tells Vendure to set the Order as "placed"
- * when it transitions to the custom "ValidatingPayment" state.
- */
-class MyOrderPlacedStrategy implements OrderPlacedStrategy {
-  shouldSetAsPlaced(ctx: RequestContext, fromState: OrderState, toState: OrderState): boolean | Promise<boolean> {
-    return fromState === 'ArrangingPayment' && toState === ('ValidatingPayment' as any);
-  }
-}
-
-// Combine the above in the VendureConfig
-export const config: VendureConfig = {
-    // ...
-    orderOptions: {
-        process: [defaultOrderProcess, customOrderProcess],
-        orderPlacedStrategy: new MyOrderPlacedStrategy(),
-    },
-    paymentOptions: {
-        process: [defaultPaymentProcess, customPaymentProcess],
-        paymentMethodHandlers: [myPaymentHandler],
-    },
-};
-```
-
-### Integration with hosted payment pages
-
-A hosted payment page is a system that works similar to (Stripe checkout)[https://stripe.com/payments/checkout]. The idea behind this flow is that the customer does not enter any credit card data anywhere on the merchant's site which waives the merchant from the responsibility to take care of sensitive data.
-
-The checkout flow works as follows:
-
-1. The user makes a POST to the card processor's URL via a Vendure served page
-2. The card processor accepts card information from the user and authorizes a payment
-3. The card processor redirects the user back to Vendure via a POST which contains details about the processed payment
-4. There is a pre-shared secret between the merchant and processor used to sign cross-site POST requests
-
-When integrating with a system like this, you would need to create a Controller to accept POST redirects from the payment processor (usually a success and a failure URL), as well as serve a POST form on your store frontend.
-
-With a hosted payment form the payment is already authorized by the time the card processor makes the POST request to Vendure, possibly settled even, so the payment handler won't do anything in particular - just return the data it has been passed. The validation of the POST request is done in the controller or service and the payment amount and payment reference are just passed to the payment handler which passes them on.

BIN
docs/docs/guides/developer-guide/payment-integrations/payment_sequence_one_step.png


BIN
docs/docs/guides/developer-guide/payment-integrations/payment_sequence_two_step.png


+ 0 - 0
docs/docs/guides/concepts/plugins/index.mdx → docs/docs/guides/developer-guide/plugins/index.mdx


BIN
docs/docs/guides/developer-guide/stock-control/global-stock-control.webp


+ 0 - 63
docs/docs/guides/developer-guide/stock-control/index.md

@@ -1,63 +0,0 @@
----
-title: "Stock Control"
-showtoc: true
----
-
-# Stock Control
-
-Vendure includes features to help manage your stock levels, stock allocations and back orders. The basic purpose is to help you keep track of how many of a given ProductVariant you have available to sell.
-
-Stock control is enabled globally via the Global Settings:
-
-![./global-stock-control.webp](./global-stock-control.webp)
-
-It can be disabled if, for example, you manage your stock with a separate inventory management system and synchronize stock levels into Vendure automatically. The setting can also be overridden at the individual ProductVariant level.
-
-## Stock Control Concepts
-
-* **Stock on hand:** This refers to the number of physical units of a particular variant which you have in stock right now. This can be zero or more, but not negative.
-* **Allocated:** This refers to the number of units which have been assigned to Orders, but which have not yet been fulfilled.
-* **Out-of-stock threshold:** This value determines the stock level at which the variant is considered "out of stock". This value is set globally, but can be overridden for specific variants. It defaults to `0`.
-* **Saleable:** This means the number of units that can be sold right now. The formula is:
-    `saleable = stockOnHand - allocated - outOfStockThreshold`
-
-Here's a table to better illustrate the relationship between these concepts:
-
-Stock on hand | Allocated | Out-of-stock threshold | Saleable
---------------|-----------|------------------------|----------
-10            | 0         | 0                      | 10
-10            | 0         | 3                      | 7
-10            | 5         | 0                      | 5
-10            | 5         | 3                      | 2
-10            | 10        | 0                      | 0
-10            | 10        | -5                     | 5
-
-The saleable value is what determines whether the customer is able to add a variant to an order. If there is 0 saleable stock, then any attempt to add to the order will result in an [`InsufficientStockError`]({{< relref "/reference/graphql-api/shop/object-types" >}}#insufficientstockerror).
-
-```JSON
-{
-  "data": {
-    "addItemToOrder": {
-      "errorCode": "INSUFFICIENT_STOCK_ERROR",
-      "message": "Only 105 items were added to the order due to insufficient stock",
-      "quantityAvailable": 105,
-      "order": {
-        "id": "2",
-        "totalQuantity": 106
-      }
-    }
-  }
-}
-```
-
-### Back orders
-
-You may have noticed that the `outOfStockThreshold` value can be set to a negative number. This allows you to sell variants even when you don't physically have them in stock. This is known as a "back order". 
-
-Back orders can be really useful to allow orders to keep flowing even when stockOnHand temporarily drops to zero. For many businesses with predictable re-supply schedules they make a lot of sense.
-
-Once a customer completes checkout, those variants in the order are marked as `allocated`. When a Fulfillment is created, those allocations are converted to Sales and the `stockOnHand` of each variant is adjusted. Fulfillments may only be created if there is sufficient stock on hand.
-
-### Configurable stock allocation
-
-By default, stock is allocated when checkout completes, which means when the Order transitions to the `'PaymentAuthorized'` or `'PaymentSettled'` state. However, you may have special requirements which mean you wish to allocate stock earlier or later in the order process. With the new [StockAllocationStrategy]({{< relref "stock-allocation-strategy" >}}) you can tailor allocation to your exact needs.

+ 0 - 0
docs/docs/guides/concepts/strategies-configurable-operations/collection-filters-args.webp → docs/docs/guides/developer-guide/strategies-configurable-operations/collection-filters-args.webp


+ 0 - 0
docs/docs/guides/concepts/strategies-configurable-operations/collection-filters.webp → docs/docs/guides/developer-guide/strategies-configurable-operations/collection-filters.webp


+ 0 - 0
docs/docs/guides/concepts/strategies-configurable-operations/index.mdx → docs/docs/guides/developer-guide/strategies-configurable-operations/index.mdx


+ 0 - 0
docs/docs/guides/concepts/the-api-layer/Vendure_docs-api_request.webp → docs/docs/guides/developer-guide/the-api-layer/Vendure_docs-api_request.webp


+ 0 - 0
docs/docs/guides/concepts/the-api-layer/index.mdx → docs/docs/guides/developer-guide/the-api-layer/index.mdx


+ 0 - 0
docs/docs/guides/concepts/the-service-layer/index.mdx → docs/docs/guides/developer-guide/the-service-layer/index.mdx


+ 0 - 0
docs/docs/guides/concepts/worker-job-queue/Vendure_docs-job-queue-2.webp → docs/docs/guides/developer-guide/worker-job-queue/Vendure_docs-job-queue-2.webp


+ 0 - 0
docs/docs/guides/concepts/worker-job-queue/Vendure_docs-job-queue-3.webp → docs/docs/guides/developer-guide/worker-job-queue/Vendure_docs-job-queue-3.webp


+ 0 - 0
docs/docs/guides/concepts/worker-job-queue/Vendure_docs-job-queue.webp → docs/docs/guides/developer-guide/worker-job-queue/Vendure_docs-job-queue.webp


+ 0 - 0
docs/docs/guides/concepts/worker-job-queue/index.mdx → docs/docs/guides/developer-guide/worker-job-queue/index.mdx


+ 0 - 0
docs/docs/guides/concepts/worker-job-queue/worker-job-queue.webp → docs/docs/guides/developer-guide/worker-job-queue/worker-job-queue.webp


+ 0 - 0
docs/docs/guides/developer-guide/importing-product-data.md → docs/docs/guides/how-to/importing-product-data.md


+ 0 - 0
docs/docs/guides/developer-guide/multi-vendor-marketplaces/aggregate-order.webp → docs/docs/guides/how-to/multi-vendor-marketplaces/aggregate-order.webp


+ 0 - 0
docs/docs/guides/developer-guide/multi-vendor-marketplaces/index.md → docs/docs/guides/how-to/multi-vendor-marketplaces/index.md


+ 1 - 1
packages/core/src/config/order/order-item-price-calculation-strategy.ts

@@ -51,7 +51,7 @@ import { ProductVariant } from '../../entity/product-variant/product-variant.ent
  * * A gift-wrapping service, where a boolean custom field is defined on the OrderLine. If `true`,
  *   a gift-wrapping surcharge would be added to the price.
  * * A product-configurator where e.g. various finishes, colors, and materials can be selected and stored
- *   as OrderLine custom fields (see [the Custom Fields guide](/guides/concepts/custom-fields/).
+ *   as OrderLine custom fields (see [the Custom Fields guide](/guides/developer-guide/custom-fields/).
  * * Price lists or bulk pricing, where different price bands are stored e.g. in a customField on the ProductVariant, and this
  *   is used to calculate the price based on the current quantity.
  *