浏览代码

docs(core): Add documentation to services and helpers

Relates to #1103
Michael Bromley 4 年之前
父节点
当前提交
75130cda2e
共有 34 个文件被更改,包括 704 次插入31 次删除
  1. 1 1
      packages/asset-server-plugin/src/hashed-asset-naming-strategy.ts
  2. 4 1
      packages/common/src/shared-types.ts
  3. 9 4
      packages/core/src/entity/authentication-method/authentication-method.entity.ts
  4. 8 0
      packages/core/src/entity/authentication-method/external-authentication-method.entity.ts
  5. 8 0
      packages/core/src/entity/authentication-method/native-authentication-method.entity.ts
  6. 7 0
      packages/core/src/entity/product-variant/product-variant-price.entity.ts
  7. 6 0
      packages/core/src/service/helpers/active-order/active-order.service.ts
  8. 1 0
      packages/core/src/service/helpers/entity-hydrator/entity-hydrator-types.ts
  9. 6 2
      packages/core/src/service/helpers/entity-hydrator/entity-hydrator.service.ts
  10. 83 0
      packages/core/src/service/helpers/list-query-builder/list-query-builder.ts
  11. 5 2
      packages/core/src/service/services/auth.service.ts
  12. 14 1
      packages/core/src/service/services/channel.service.ts
  13. 27 0
      packages/core/src/service/services/collection.service.ts
  14. 5 0
      packages/core/src/service/services/country.service.ts
  15. 4 0
      packages/core/src/service/services/customer-group.service.ts
  16. 69 0
      packages/core/src/service/services/customer.service.ts
  17. 5 0
      packages/core/src/service/services/facet-value.service.ts
  18. 4 0
      packages/core/src/service/services/facet.service.ts
  19. 19 1
      packages/core/src/service/services/fulfillment.service.ts
  20. 5 0
      packages/core/src/service/services/global-settings.service.ts
  21. 3 1
      packages/core/src/service/services/history.service.ts
  22. 3 1
      packages/core/src/service/services/order-testing.service.ts
  23. 180 1
      packages/core/src/service/services/order.service.ts
  24. 34 0
      packages/core/src/service/services/payment.service.ts
  25. 29 3
      packages/core/src/service/services/product-variant.service.ts
  26. 12 0
      packages/core/src/service/services/product.service.ts
  27. 10 0
      packages/core/src/service/services/promotion.service.ts
  28. 10 0
      packages/core/src/service/services/role.service.ts
  29. 32 1
      packages/core/src/service/services/session.service.ts
  30. 1 0
      packages/core/src/service/services/shipping-method.service.ts
  31. 34 0
      packages/core/src/service/services/stock-movement.service.ts
  32. 7 2
      packages/core/src/service/services/tax-rate.service.ts
  33. 58 10
      packages/core/src/service/services/user.service.ts
  34. 1 0
      packages/core/src/service/services/zone.service.ts

+ 1 - 1
packages/asset-server-plugin/src/hashed-asset-naming-strategy.ts

@@ -12,7 +12,7 @@ import path from 'path';
  * and the purpose is to reduce the number of files in a single directory, since a very large
  * number of files can lead to performance issues when reading and writing to that directory.
  *
- * With this strategory, even with 200,000 total assets stored, each directory would
+ * With this strategy, even with 200,000 total assets stored, each directory would
  * only contain less than 800 files.
  *
  * @docsCategory AssetServerPlugin

+ 4 - 1
packages/common/src/shared-types.ts

@@ -57,7 +57,10 @@ export type JsonCompatible<T> = {
 };
 
 /**
- * A type describing the shape of a paginated list response
+ * @description
+ * A type describing the shape of a paginated list response.
+ *
+ * @docsCategory common
  */
 export type PaginatedList<T> = {
     items: T[];

+ 9 - 4
packages/core/src/entity/authentication-method/authentication-method.entity.ts

@@ -3,12 +3,17 @@ import { Entity, ManyToOne, TableInheritance } from 'typeorm';
 import { VendureEntity } from '../base/base.entity';
 import { User } from '../user/user.entity';
 
+/**
+ * @description
+ * An AuthenticationMethod represents the means by which a {@link User} is authenticated. There are two kinds:
+ * {@link NativeAuthenticationMethod} and {@link ExternalAuthenticationMethod}.
+ *
+ * @docsCategory entities
+ * @docsPage AuthenticationMethod
+ */
 @Entity()
 @TableInheritance({ column: { type: 'varchar', name: 'type' } })
 export abstract class AuthenticationMethod extends VendureEntity {
-    @ManyToOne(
-        type => User,
-        user => user.authenticationMethods,
-    )
+    @ManyToOne(type => User, user => user.authenticationMethods)
     user: User;
 }

+ 8 - 0
packages/core/src/entity/authentication-method/external-authentication-method.entity.ts

@@ -3,6 +3,14 @@ import { ChildEntity, Column } from 'typeorm';
 
 import { AuthenticationMethod } from './authentication-method.entity';
 
+/**
+ * @description
+ * This method is used when an external authentication service is used to authenticate Vendure Users.
+ * Examples of external auth include social logins or corporate identity servers.
+ *
+ * @docsCategory entities
+ * @docsPage AuthenticationMethod
+ */
 @ChildEntity()
 export class ExternalAuthenticationMethod extends AuthenticationMethod {
     constructor(input: DeepPartial<ExternalAuthenticationMethod>) {

+ 8 - 0
packages/core/src/entity/authentication-method/native-authentication-method.entity.ts

@@ -3,6 +3,14 @@ import { ChildEntity, Column } from 'typeorm';
 
 import { AuthenticationMethod } from './authentication-method.entity';
 
+/**
+ * @description
+ * This is the default, built-in authentication method which uses a identifier (typically username or email address)
+ * and password combination to authenticate a User.
+ *
+ * @docsCategory entities
+ * @docsPage AuthenticationMethod
+ */
 @ChildEntity()
 export class NativeAuthenticationMethod extends AuthenticationMethod {
     constructor(input?: DeepPartial<NativeAuthenticationMethod>) {

+ 7 - 0
packages/core/src/entity/product-variant/product-variant-price.entity.ts

@@ -6,6 +6,13 @@ import { EntityId } from '../entity-id.decorator';
 
 import { ProductVariant } from './product-variant.entity';
 
+/**
+ * @description
+ * A ProductVariantPrice is a Channel-specific price for a ProductVariant. For every Channel to
+ * which a ProductVariant is assigned, there will be a corresponding ProductVariantPrice entity.
+ *
+ * @docsCategory entities
+ */
 @Entity()
 export class ProductVariantPrice extends VendureEntity {
     constructor(input?: DeepPartial<ProductVariantPrice>) {

+ 6 - 0
packages/core/src/service/helpers/active-order/active-order.service.ts

@@ -7,6 +7,12 @@ import { Order } from '../../../entity/order/order.entity';
 import { OrderService } from '../../services/order.service';
 import { SessionService } from '../../services/session.service';
 
+/**
+ * @description
+ * This helper class is used to get a reference to the active Order from the current RequestContext.
+ *
+ * @docsCategory orders
+ */
 @Injectable()
 export class ActiveOrderService {
     constructor(

+ 1 - 0
packages/core/src/service/helpers/entity-hydrator/entity-hydrator-types.ts

@@ -6,6 +6,7 @@ import { VendureEntity } from '../../../entity/base/base.entity';
  * when using the {@link EntityHydrator} helper.
  *
  * @since 1.3.0
+ * @docsCategory data-access
  */
 export interface HydrateOptions<Entity extends VendureEntity> {
     /**

+ 6 - 2
packages/core/src/service/helpers/entity-hydrator/entity-hydrator.service.ts

@@ -21,8 +21,11 @@ import { HydrateOptions } from './entity-hydrator-types';
  *
  * @example
  * ```TypeScript
- * const product = this.productVariantService.getProductForVariant(ctx, variantId);
- * await this.entityHydrator.hydrate(ctx, product, { relations: ['facetValues.facet' ]});
+ * const product = this.productVariantService
+ *   .getProductForVariant(ctx, variantId);
+ *
+ * await this.entityHydrator
+ *   .hydrate(ctx, product, { relations: ['facetValues.facet' ]});
  *```
  *
  * In this above example, the `product` instance will now have the `facetValues` relation
@@ -33,6 +36,7 @@ import { HydrateOptions } from './entity-hydrator-types';
  * options is used (see {@link HydrateOptions}), any related ProductVariant will have the correct
  * Channel-specific prices applied to them.
  *
+ * @docsCategory data-access
  * @since 1.3.0
  */
 @Injectable()

+ 83 - 0
packages/core/src/service/helpers/list-query-builder/list-query-builder.ts

@@ -21,17 +21,26 @@ import { parseChannelParam } from './parse-channel-param';
 import { parseFilterParams } from './parse-filter-params';
 import { parseSortParams } from './parse-sort-params';
 
+/**
+ * @description
+ * Options which can be passed to the ListQueryBuilder's `build()` method.
+ *
+ * @docsCategory data-access
+ * @docsPage ListQueryBuilder
+ */
 export type ExtendedListQueryOptions<T extends VendureEntity> = {
     relations?: string[];
     channelId?: ID;
     where?: FindConditions<T>;
     orderBy?: FindOneOptions<T>['order'];
     /**
+     * @description
      * When a RequestContext is passed, then the query will be
      * executed as part of any outer transaction.
      */
     ctx?: RequestContext;
     /**
+     * @description
      * One of the main tasks of the ListQueryBuilder is to auto-generate filter and sort queries based on the
      * available columns of a given entity. However, it may also be sometimes desirable to allow filter/sort
      * on a property of a relation. In this case, the `customPropertyMap` can be used to define a property
@@ -54,15 +63,89 @@ export type ExtendedListQueryOptions<T extends VendureEntity> = {
     customPropertyMap?: { [name: string]: string };
 };
 
+/**
+ * @description
+ * This helper class is used when fetching entities the database from queries which return a {@link PaginatedList} type.
+ * These queries all follow the same format:
+ *
+ * In the GraphQL definition, they return a type which implements the `Node` interface, and the query returns a
+ * type which implements the `PaginatedList` interface:
+ *
+ * ```GraphQL
+ * type BlogPost implements Node {
+ *   id: ID!
+ *   published: DataTime!
+ *   title: String!
+ *   body: String!
+ * }
+ *
+ * type BlogPostList implements PaginatedList {
+ *   items: [BlogPost!]!
+ *   totalItems: Int!
+ * }
+ *
+ * # Generated at run-time by Vendure
+ * input ProductListOptions
+ *
+ * extend type Query {
+ *    blogPosts(options: BlogPostListOptions): BlogPostList!
+ * }
+ * ```
+ * When Vendure bootstraps, it will find the `ProductListOptions` input and, because it is used in a query
+ * returning a `PaginatedList` type, it knows that it should dynamically generate this input. This means
+ * all primitive field of the `BlogPost` type (namely, "published", "title" and "body") will have `filter` and
+ * `sort` inputs created for them, as well a `skip` and `take` fields for pagination.
+ *
+ * Your resolver function will then look like this:
+ *
+ * ```TypeScript
+ * \@Resolver()
+ * export class BlogPostResolver
+ *   constructor(private blogPostService: BlogPostService) {}
+ *
+ *   \@Query()
+ *   async blogPosts(
+ *     \@Ctx() ctx: RequestContext,
+ *     \@Args() args: any,
+ *   ): Promise<PaginatedList<BlogPost>> {
+ *     return this.blogPostService.findAll(ctx, args.options || undefined);
+ *   }
+ * }
+ * ```
+ *
+ * and the corresponding service will use the ListQueryBuilder:
+ *
+ * ```TypeScript
+ * \@Injectable()
+ * export class BlogPostService {
+ *   constructor(private listQueryBuilder: ListQueryBuilder) {}
+ *
+ *   findAll(ctx: RequestContext, options?: ListQueryOptions<BlogPost>) {
+ *     return this.listQueryBuilder
+ *       .build(BlogPost, options)
+ *       .getManyAndCount()
+ *       .then(async ([items, totalItems]) => {
+ *         return { items, totalItems };
+ *       });
+ *   }
+ * }
+ * ```
+ *
+ * @docsCategory data-access
+ * @docsPage ListQueryBuilder
+ * @docsWeight 0
+ */
 @Injectable()
 export class ListQueryBuilder implements OnApplicationBootstrap {
     constructor(private connection: TransactionalConnection, private configService: ConfigService) {}
 
+    /** @internal */
     onApplicationBootstrap(): any {
         this.registerSQLiteRegexpFunction();
     }
 
     /**
+     * @description
      * Creates and configures a SelectQueryBuilder for queries that return paginated lists of entities.
      */
     build<T extends VendureEntity>(

+ 5 - 2
packages/core/src/service/services/auth.service.ts

@@ -43,7 +43,7 @@ export class AuthService {
 
     /**
      * @description
-     * Authenticates a user's credentials and if okay, creates a new session.
+     * Authenticates a user's credentials and if okay, creates a new {@link AuthenticatedSession}.
      */
     async authenticate(
         ctx: RequestContext,
@@ -105,7 +105,9 @@ export class AuthService {
     }
 
     /**
-     * Verify the provided password against the one we have for the given user.
+     * @description
+     * Verify the provided password against the one we have for the given user. Requires
+     * the {@link NativeAuthenticationStrategy} to be configured.
      */
     async verifyUserPassword(
         ctx: RequestContext,
@@ -124,6 +126,7 @@ export class AuthService {
     }
 
     /**
+     * @description
      * Deletes all sessions for the user associated with the given session token.
      */
     async destroyAuthenticatedSession(ctx: RequestContext, sessionToken: string): Promise<void> {

+ 14 - 1
packages/core/src/service/services/channel.service.ts

@@ -51,6 +51,8 @@ export class ChannelService {
     /**
      * When the app is bootstrapped, ensure a default Channel exists and populate the
      * channel lookup array.
+     *
+     * @internal
      */
     async initChannels() {
         await this.ensureDefaultChannelExists();
@@ -62,6 +64,7 @@ export class ChannelService {
     }
 
     /**
+     * @description
      * Assigns a ChannelAware entity to the default Channel as well as any channel
      * specified in the RequestContext.
      */
@@ -73,6 +76,7 @@ export class ChannelService {
     }
 
     /**
+     * @description
      * Assigns the entity to the given Channels and saves.
      */
     async assignToChannels<T extends ChannelAware & VendureEntity>(
@@ -93,6 +97,7 @@ export class ChannelService {
     }
 
     /**
+     * @description
      * Removes the entity from the given Channels and saves.
      */
     async removeFromChannels<T extends ChannelAware & VendureEntity>(
@@ -115,7 +120,9 @@ export class ChannelService {
     }
 
     /**
-     * Given a channel token, returns the corresponding Channel if it exists.
+     * @description
+     * Given a channel token, returns the corresponding Channel if it exists, else will throw
+     * a {@link ChannelNotFoundError}.
      */
     async getChannelFromToken(token: string): Promise<Channel> {
         const allChannels = await this.allChannels.value();
@@ -131,6 +138,7 @@ export class ChannelService {
     }
 
     /**
+     * @description
      * Returns the default Channel.
      */
     async getDefaultChannel(): Promise<Channel> {
@@ -229,6 +237,11 @@ export class ChannelService {
         };
     }
 
+    /**
+     * @description
+     * Type guard method which returns true if the given entity is an
+     * instance of a class which implements the {@link ChannelAware} interface.
+     */
     public isChannelAware(entity: VendureEntity): entity is VendureEntity & ChannelAware {
         const entityType = Object.getPrototypeOf(entity).constructor;
         return !!this.connection.rawConnection

+ 27 - 0
packages/core/src/service/services/collection.service.ts

@@ -76,6 +76,9 @@ export class CollectionService implements OnModuleInit {
         private customFieldRelationService: CustomFieldRelationService,
     ) {}
 
+    /**
+     * @internal
+     */
     async onModuleInit() {
         const productEvents$ = this.eventBus.ofType(ProductEvent);
         const variantEvents$ = this.eventBus.ofType(ProductVariantEvent);
@@ -194,6 +197,10 @@ export class CollectionService implements OnModuleInit {
         return this.findOne(ctx, bestMatch.base.id);
     }
 
+    /**
+     * @description
+     * Returns all configured CollectionFilters, as specified by the {@link CatalogOptions}.
+     */
     getAvailableFilters(ctx: RequestContext): ConfigurableOperationDefinition[] {
         return this.configService.catalogOptions.collectionFilters.map(f => f.toGraphQlType(ctx));
     }
@@ -221,10 +228,19 @@ export class CollectionService implements OnModuleInit {
         return parent && translateDeep(parent, ctx.languageCode);
     }
 
+    /**
+     * @description
+     * Returns all child Collections of the Collection with the given id.
+     */
     async getChildren(ctx: RequestContext, collectionId: ID): Promise<Collection[]> {
         return this.getDescendants(ctx, collectionId, 1);
     }
 
+    /**
+     * @description
+     * Returns an array of name/id pairs representing all ancestor Collections up
+     * to the Root Collection.
+     */
     async getBreadcrumbs(
         ctx: RequestContext,
         collection: Collection,
@@ -238,6 +254,10 @@ export class CollectionService implements OnModuleInit {
         return [pickProps(rootCollection), ...ancestors.map(pickProps).reverse(), pickProps(collection)];
     }
 
+    /**
+     * @description
+     * Returns all Collections which are associated with the given Product ID.
+     */
     async getCollectionsByProductId(
         ctx: RequestContext,
         productId: ID,
@@ -261,6 +281,7 @@ export class CollectionService implements OnModuleInit {
     }
 
     /**
+     * @description
      * Returns the descendants of a Collection as a flat array. The depth of the traversal can be limited
      * with the maxDepth argument. So to get only the immediate children, set maxDepth = 1.
      */
@@ -287,6 +308,7 @@ export class CollectionService implements OnModuleInit {
     }
 
     /**
+     * @description
      * Gets the ancestors of a given collection. Note that since ProductCategories are implemented as an adjacency list, this method
      * will produce more queries the deeper the collection is in the tree.
      */
@@ -399,6 +421,11 @@ export class CollectionService implements OnModuleInit {
         };
     }
 
+    /**
+     * @description
+     * Moves a Collection by specifying the parent Collection ID, and an index representing the order amongst
+     * its siblings.
+     */
     async move(ctx: RequestContext, input: MoveCollectionInput): Promise<Translated<Collection>> {
         const target = await this.connection.getEntityOrThrow(ctx, Collection, input.collectionId, {
             channelId: ctx.channelId,

+ 5 - 0
packages/core/src/service/services/country.service.ts

@@ -61,6 +61,7 @@ export class CountryService {
     }
 
     /**
+     * @description
      * Returns an array of enabled Countries, intended for use in a public-facing (ie. Shop) API.
      */
     findAllAvailable(ctx: RequestContext): Promise<Array<Translated<Country>>> {
@@ -70,6 +71,10 @@ export class CountryService {
             .then(items => items.map(country => translateDeep(country, ctx.languageCode)));
     }
 
+    /**
+     * @description
+     * Returns a Country based on its ISO country code.
+     */
     async findOneByCode(ctx: RequestContext, countryCode: string): Promise<Translated<Country>> {
         const country = await this.connection.getRepository(ctx, Country).findOne({
             where: {

+ 4 - 0
packages/core/src/service/services/customer-group.service.ts

@@ -51,6 +51,10 @@ export class CustomerGroupService {
         return this.connection.getRepository(ctx, CustomerGroup).findOne(customerGroupId);
     }
 
+    /**
+     * @description
+     * Returns a {@link PaginatedList} of all the Customers in the group.
+     */
     getGroupCustomers(
         ctx: RequestContext,
         customerGroupId: ID,

+ 69 - 0
packages/core/src/service/services/customer.service.ts

@@ -103,6 +103,12 @@ export class CustomerService {
         });
     }
 
+    /**
+     * @description
+     * Returns the Customer entity associated with the given userId, if one exists.
+     * Setting `filterOnChannel` to `true` will limit the results to Customers which are assigned
+     * to the current active Channel only.
+     */
     findOneByUserId(ctx: RequestContext, userId: ID, filterOnChannel = true): Promise<Customer | undefined> {
         let query = this.connection
             .getRepository(ctx, Customer)
@@ -117,6 +123,10 @@ export class CustomerService {
         return query.getOne();
     }
 
+    /**
+     * @description
+     * Returns all {@link Address} entities associated with the specified Customer.
+     */
     findAddressesByCustomerId(ctx: RequestContext, customerId: ID): Promise<Address[]> {
         return this.connection
             .getRepository(ctx, Address)
@@ -133,6 +143,10 @@ export class CustomerService {
             });
     }
 
+    /**
+     * @description
+     * Returns a list of all {@link CustomerGroup} entities.
+     */
     async getCustomerGroups(ctx: RequestContext, customerId: ID): Promise<CustomerGroup[]> {
         const customerWithGroups = await this.connection.findOneInChannel(
             ctx,
@@ -153,6 +167,17 @@ export class CustomerService {
         }
     }
 
+    /**
+     * @description
+     * Creates a new Customer, including creation of a new User with the special `customer` Role.
+     *
+     * If the `password` argument is specified, the Customer will be immediately verified. If not,
+     * then an {@link AccountRegistrationEvent} is published, so that the customer can have their
+     * email address verified and set their password in a later step using the `verifyCustomerEmailAddress()`
+     * method.
+     *
+     * This method is intended to be used in admin-created Customer flows.
+     */
     async create(
         ctx: RequestContext,
         input: CreateCustomerInput,
@@ -305,6 +330,14 @@ export class CustomerService {
         return assertFound(this.findOne(ctx, customer.id));
     }
 
+    /**
+     * @description
+     * Registers a new Customer account with the {@link NativeAuthenticationStrategy} and starts
+     * the email verification flow (unless {@link AuthOptions} `requireVerification` is set to `false`)
+     * by publishing an {@link AccountRegistrationEvent}.
+     *
+     * This method is intended to be used in storefront Customer-creation flows.
+     */
     async registerCustomerAccount(
         ctx: RequestContext,
         input: RegisterCustomerInput,
@@ -382,6 +415,11 @@ export class CustomerService {
         return { success: true };
     }
 
+    /**
+     * @description
+     * Refreshes a stale email address verification token by generating a new one and
+     * publishing a {@link AccountRegistrationEvent}.
+     */
     async refreshVerificationToken(ctx: RequestContext, emailAddress: string): Promise<void> {
         const user = await this.userService.getUserByEmailAddress(ctx, emailAddress);
         if (user) {
@@ -392,6 +430,11 @@ export class CustomerService {
         }
     }
 
+    /**
+     * @description
+     * Given a valid verification token which has been published in an {@link AccountRegistrationEvent}, this
+     * method is used to set the Customer as `verified` as part of the account registration flow.
+     */
     async verifyCustomerEmailAddress(
         ctx: RequestContext,
         verificationToken: string,
@@ -419,6 +462,11 @@ export class CustomerService {
         return assertFound(this.findOneByUserId(ctx, result.id));
     }
 
+    /**
+     * @description
+     * Publishes a new {@link PasswordResetEvent} for the given email address. This event creates
+     * a token which can be used in the `resetPassword()` method.
+     */
     async requestPasswordReset(ctx: RequestContext, emailAddress: string): Promise<void> {
         const user = await this.userService.setPasswordResetToken(ctx, emailAddress);
         if (user) {
@@ -436,6 +484,11 @@ export class CustomerService {
         }
     }
 
+    /**
+     * @description
+     * Given a valid password reset token created by a call to the `requestPasswordReset()` method,
+     * this method will change the Customer's password to that given as the `password` argument.
+     */
     async resetPassword(
         ctx: RequestContext,
         passwordResetToken: string,
@@ -458,6 +511,12 @@ export class CustomerService {
         return result;
     }
 
+    /**
+     * @description
+     * Publishes a {@link IdentifierChangeRequestEvent} for the given User. This event contains a token
+     * which is then used in the `updateEmailAddress()` method to change the email address of the User &
+     * Customer.
+     */
     async requestUpdateEmailAddress(
         ctx: RequestContext,
         userId: ID,
@@ -513,6 +572,11 @@ export class CustomerService {
         }
     }
 
+    /**
+     * @description
+     * Given a valid email update token published in a {@link IdentifierChangeRequestEvent}, this method
+     * will update the Customer & User email address.
+     */
     async updateEmailAddress(
         ctx: RequestContext,
         token: string,
@@ -545,6 +609,7 @@ export class CustomerService {
     }
 
     /**
+     * @description
      * For guest checkouts, we assume that a matching email address is the same customer.
      */
     async createOrUpdate(
@@ -576,6 +641,10 @@ export class CustomerService {
         return this.connection.getRepository(ctx, Customer).save(customer);
     }
 
+    /**
+     * @description
+     * Creates a new {@link Address} for the given Customer.
+     */
     async createAddress(ctx: RequestContext, customerId: ID, input: CreateAddressInput): Promise<Address> {
         const customer = await this.connection.getEntityOrThrow(ctx, Customer, customerId, {
             where: { deletedAt: null },

+ 5 - 0
packages/core/src/service/services/facet-value.service.ts

@@ -67,6 +67,10 @@ export class FacetValueService {
         );
     }
 
+    /**
+     * @description
+     * Returns all FacetValues belonging to the Facet with the given id.
+     */
     findByFacetId(ctx: RequestContext, id: ID): Promise<Array<Translated<FacetValue>>> {
         return this.connection
             .getRepository(ctx, FacetValue)
@@ -143,6 +147,7 @@ export class FacetValueService {
     }
 
     /**
+     * @description
      * Checks for usage of the given FacetValues in any Products or Variants, and returns the counts.
      */
     async checkFacetValueUsage(

+ 4 - 0
packages/core/src/service/services/facet.service.ts

@@ -84,6 +84,10 @@ export class FacetService {
             .then(facet => facet && translateDeep(facet, lang, ['values', ['values', 'facet']]));
     }
 
+    /**
+     * @description
+     * Returns the Facet which contains the given FacetValue id.
+     */
     async findByFacetValueId(ctx: RequestContext, id: ID): Promise<Translated<Facet> | undefined> {
         const facet = await this.connection
             .getRepository(ctx, Facet)

+ 19 - 1
packages/core/src/service/services/fulfillment.service.ts

@@ -37,6 +37,11 @@ export class FulfillmentService {
         private customFieldRelationService: CustomFieldRelationService,
     ) {}
 
+    /**
+     * @description
+     * Creates a new Fulfillment for the given Orders and OrderItems, using the specified
+     * {@link FulfillmentHandler}.
+     */
     async create(
         ctx: RequestContext,
         orders: Order[],
@@ -84,7 +89,7 @@ export class FulfillmentService {
         return newFulfillment;
     }
 
-    async findOneOrThrow(
+    private async findOneOrThrow(
         ctx: RequestContext,
         id: ID,
         relations: string[] = ['orderItems'],
@@ -94,11 +99,20 @@ export class FulfillmentService {
         });
     }
 
+    /**
+     * @description
+     * Returns all OrderItems associated with the specified Fulfillment.
+     */
     async getOrderItemsByFulfillmentId(ctx: RequestContext, id: ID): Promise<OrderItem[]> {
         const fulfillment = await this.findOneOrThrow(ctx, id);
         return fulfillment.orderItems;
     }
 
+    /**
+     * @description
+     * Transitions the specified Fulfillment to a new state and upon successful transition
+     * publishes a {@link FulfillmentStateTransitionEvent}.
+     */
     async transitionToState(
         ctx: RequestContext,
         fulfillmentId: ID,
@@ -133,6 +147,10 @@ export class FulfillmentService {
         return { fulfillment, orders, fromState, toState: state };
     }
 
+    /**
+     * @description
+     * Returns an array of the next valid states for the Fulfillment.
+     */
     getNextStates(fulfillment: Fulfillment): ReadonlyArray<FulfillmentState> {
         return this.fulfillmentStateMachine.getNextStates(fulfillment);
     }

+ 5 - 0
packages/core/src/service/services/global-settings.service.ts

@@ -25,6 +25,7 @@ export class GlobalSettingsService {
 
     /**
      * Ensure there is a single global settings row in the database.
+     * @internal
      */
     async initGlobalSettings() {
         try {
@@ -45,6 +46,10 @@ export class GlobalSettingsService {
         }
     }
 
+    /**
+     * @description
+     * Returns the GlobalSettings entity.
+     */
     async getSettings(ctx: RequestContext): Promise<GlobalSettings> {
         const settings = await this.connection.getRepository(ctx, GlobalSettings).findOne({
             order: {

+ 3 - 1
packages/core/src/service/services/history.service.ts

@@ -138,7 +138,9 @@ export interface UpdateCustomerHistoryEntryArgs<T extends keyof CustomerHistoryE
 
 /**
  * @description
- * Contains methods relating to {@link HistoryEntry} entities.
+ * Contains methods relating to {@link HistoryEntry} entities. Histories are timelines of actions
+ * related to a particular Customer or Order, recording significant events such as creation, state changes,
+ * notes, etc.
  *
  * @docsCategory services
  */

+ 3 - 1
packages/core/src/service/services/order-testing.service.ts

@@ -44,6 +44,7 @@ export class OrderTestingService {
     ) {}
 
     /**
+     * @description
      * Runs a given ShippingMethod configuration against a mock Order to test for eligibility and resulting
      * price.
      */
@@ -74,7 +75,8 @@ export class OrderTestingService {
     }
 
     /**
-     * Tests all available ShippingMethods against a mock Order and return those whic hare eligible. This
+     * @description
+     * Tests all available ShippingMethods against a mock Order and return those which are eligible. This
      * is intended to simulate a call to the `eligibleShippingMethods` query of the Shop API.
      */
     async testEligibleShippingMethods(

+ 180 - 1
packages/core/src/service/services/order.service.ts

@@ -156,6 +156,12 @@ export class OrderService {
         private customFieldRelationService: CustomFieldRelationService,
     ) {}
 
+    /**
+     * @description
+     * Returns an array of all the configured states and transitions of the order process. This is
+     * based on the default order process plus all configured {@link CustomOrderProcess} objects
+     * defined in the {@link OrderOptions} `process` array.
+     */
     getOrderProcessStates(): OrderProcessState[] {
         return Object.entries(this.orderStateMachine.config.transitions).map(([name, { to }]) => ({
             name,
@@ -273,6 +279,10 @@ export class OrderService {
             });
     }
 
+    /**
+     * @description
+     * Returns all {@link Payment} entities associated with the Order.
+     */
     getOrderPayments(ctx: RequestContext, orderId: ID): Promise<Payment[]> {
         return this.connection.getRepository(ctx, Payment).find({
             relations: ['refunds'],
@@ -282,6 +292,10 @@ export class OrderService {
         });
     }
 
+    /**
+     * @description
+     * Returns all OrderItems associated with the given {@link Refund}.
+     */
     async getRefundOrderItems(ctx: RequestContext, refundId: ID): Promise<OrderItem[]> {
         const refund = await this.connection.getEntityOrThrow(ctx, Refund, refundId, {
             relations: ['orderItems'],
@@ -289,6 +303,10 @@ export class OrderService {
         return refund.orderItems;
     }
 
+    /**
+     * @description
+     * Returns an array of any {@link OrderModification} entities associated with the Order.
+     */
     getOrderModifications(ctx: RequestContext, orderId: ID): Promise<OrderModification[]> {
         return this.connection.getRepository(ctx, OrderModification).find({
             where: {
@@ -298,6 +316,10 @@ export class OrderService {
         });
     }
 
+    /**
+     * @description
+     * Returns any {@link Refund}s associated with a {@link Payment}.
+     */
     getPaymentRefunds(ctx: RequestContext, paymentId: ID): Promise<Refund[]> {
         return this.connection.getRepository(ctx, Refund).find({
             where: {
@@ -306,6 +328,11 @@ export class OrderService {
         });
     }
 
+    /**
+     * @description
+     * Returns any Order associated with the specified User's Customer account
+     * that is still in the `active` state.
+     */
     async getActiveOrderForUser(ctx: RequestContext, userId: ID): Promise<Order | undefined> {
         const customer = await this.customerService.findOneByUserId(ctx, userId);
         if (customer) {
@@ -327,6 +354,11 @@ export class OrderService {
         }
     }
 
+    /**
+     * @description
+     * Creates a new, empty Order. If a `userId` is passed, the Order will get associated with that
+     * User's Customer account.
+     */
     async create(ctx: RequestContext, userId?: ID): Promise<Order> {
         const newOrder = new Order({
             code: await this.configService.orderOptions.orderCodeStrategy.generate(ctx),
@@ -357,6 +389,10 @@ export class OrderService {
         return transitionResult;
     }
 
+    /**
+     * @description
+     * Updates the custom fields of an Order.
+     */
     async updateCustomFields(ctx: RequestContext, orderId: ID, customFields: any) {
         let order = await this.getOrderOrThrow(ctx, orderId);
         order = patchEntity(order, { customFields });
@@ -365,6 +401,7 @@ export class OrderService {
     }
 
     /**
+     * @description
      * Adds an OrderItem to the Order, either creating a new OrderLine or
      * incrementing an existing one.
      */
@@ -431,7 +468,8 @@ export class OrderService {
     }
 
     /**
-     * Adjusts the quantity of an existing OrderLine
+     * @description
+     * Adjusts the quantity and/or custom field values of an existing OrderLine.
      */
     async adjustOrderLine(
         ctx: RequestContext,
@@ -479,6 +517,10 @@ export class OrderService {
         }
     }
 
+    /**
+     * @description
+     * Removes the specified OrderLine from the Order.
+     */
     async removeItemFromOrder(
         ctx: RequestContext,
         orderId: ID,
@@ -496,6 +538,10 @@ export class OrderService {
         return updatedOrder;
     }
 
+    /**
+     * @description
+     * Removes all OrderLines from the Order.
+     */
     async removeAllItemsFromOrder(
         ctx: RequestContext,
         orderId: ID,
@@ -511,6 +557,10 @@ export class OrderService {
         return updatedOrder;
     }
 
+    /**
+     * @description
+     * Adds a {@link Surcharge} to the Order.
+     */
     async addSurchargeToOrder(
         ctx: RequestContext,
         orderId: ID,
@@ -531,6 +581,10 @@ export class OrderService {
         return updatedOrder;
     }
 
+    /**
+     * @description
+     * Removes a {@link Surcharge} from the Order.
+     */
     async removeSurchargeFromOrder(ctx: RequestContext, orderId: ID, surchargeId: ID): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         const surcharge = await this.connection.getEntityOrThrow(ctx, Surcharge, surchargeId);
@@ -544,6 +598,11 @@ export class OrderService {
         }
     }
 
+    /**
+     * @description
+     * Applies a coupon code to the Order, which should be a valid coupon code as specified in the configuration
+     * of an active {@link Promotion}.
+     */
     async applyCouponCode(
         ctx: RequestContext,
         orderId: ID,
@@ -571,6 +630,10 @@ export class OrderService {
         return this.applyPriceAdjustments(ctx, order);
     }
 
+    /**
+     * @description
+     * Removes a coupon code from the Order.
+     */
     async removeCouponCode(ctx: RequestContext, orderId: ID, couponCode: string) {
         const order = await this.getOrderOrThrow(ctx, orderId);
         if (order.couponCodes.includes(couponCode)) {
@@ -599,6 +662,11 @@ export class OrderService {
         }
     }
 
+    /**
+     * @description
+     * Returns all {@link Promotion}s associated with an Order. A Promotion only gets associated with
+     * and Order once the order has been placed (see {@link OrderPlacedStrategy}).
+     */
     async getOrderPromotions(ctx: RequestContext, orderId: ID): Promise<Promotion[]> {
         const order = await this.connection.getEntityOrThrow(ctx, Order, orderId, {
             channelId: ctx.channelId,
@@ -607,10 +675,18 @@ export class OrderService {
         return order.promotions || [];
     }
 
+    /**
+     * @description
+     * Returns the next possible states that the Order may transition to.
+     */
     getNextOrderStates(order: Order): ReadonlyArray<OrderState> {
         return this.orderStateMachine.getNextStates(order);
     }
 
+    /**
+     * @description
+     * Sets the shipping address for the Order.
+     */
     async setShippingAddress(ctx: RequestContext, orderId: ID, input: CreateAddressInput): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         const country = await this.countryService.findOneByCode(ctx, input.countryCode);
@@ -618,6 +694,10 @@ export class OrderService {
         return this.connection.getRepository(ctx, Order).save(order);
     }
 
+    /**
+     * @description
+     * Sets the billing address for the Order.
+     */
     async setBillingAddress(ctx: RequestContext, orderId: ID, input: CreateAddressInput): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         const country = await this.countryService.findOneByCode(ctx, input.countryCode);
@@ -625,6 +705,14 @@ export class OrderService {
         return this.connection.getRepository(ctx, Order).save(order);
     }
 
+    /**
+     * @description
+     * Returns an array of quotes stating which {@link ShippingMethod}s may be applied to this Order.
+     * This is determined by the configured {@link ShippingEligibilityChecker} of each ShippingMethod.
+     *
+     * The quote also includes a price for each method, as determined by the configured
+     * {@link ShippingCalculator} of each eligible ShippingMethod.
+     */
     async getEligibleShippingMethods(ctx: RequestContext, orderId: ID): Promise<ShippingMethodQuote[]> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         const eligibleMethods = await this.shippingCalculator.getEligibleShippingMethods(ctx, order);
@@ -642,11 +730,19 @@ export class OrderService {
         });
     }
 
+    /**
+     * @description
+     * Returns an array of quotes stating which {@link PaymentMethod}s may be used on this Order.
+     */
     async getEligiblePaymentMethods(ctx: RequestContext, orderId: ID): Promise<PaymentMethodQuote[]> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         return this.paymentMethodService.getEligiblePaymentMethods(ctx, order);
     }
 
+    /**
+     * @description
+     * Sets the ShippingMethod to be used on this Order.
+     */
     async setShippingMethod(
         ctx: RequestContext,
         orderId: ID,
@@ -687,6 +783,10 @@ export class OrderService {
         return this.connection.getRepository(ctx, Order).save(order);
     }
 
+    /**
+     * @description
+     * Transitions the Order to the given state.
+     */
     async transitionToState(
         ctx: RequestContext,
         orderId: ID,
@@ -706,6 +806,11 @@ export class OrderService {
         return order;
     }
 
+    /**
+     * @description
+     * Transitions a Fulfillment to the given state and then transitions the Order state based on
+     * whether all Fulfillments of the Order are shipped or delivered.
+     */
     async transitionFulfillmentToState(
         ctx: RequestContext,
         fulfillmentId: ID,
@@ -722,6 +827,18 @@ export class OrderService {
         return fulfillment;
     }
 
+    /**
+     * @description
+     * Allows the Order to be modified, which allows several aspects of the Order to be changed:
+     *
+     * * Changes to OrderLine quantities
+     * * New OrderLines being added
+     * * Arbitrary {@link Surcharge}s being added
+     * * Shipping or billing address changes
+     *
+     * Setting the `dryRun` input property to `true` will apply all changes, including updating the price of the
+     * Order, but will not actually persist any of those changes to the database.
+     */
     async modifyOrder(
         ctx: RequestContext,
         input: ModifyOrderInput,
@@ -778,6 +895,12 @@ export class OrderService {
         }
     }
 
+    /**
+     * @description
+     * Transitions the given {@link Payment} to a new state. If the order totalWithTax price is then
+     * covered by Payments, the Order state will be automatically transitioned to `PaymentSettled`
+     * or `PaymentAuthorized`.
+     */
     async transitionPaymentToState(
         ctx: RequestContext,
         paymentId: ID,
@@ -795,6 +918,11 @@ export class OrderService {
         return result;
     }
 
+    /**
+     * @description
+     * Adds a new Payment to the Order. If the Order totalWithTax is covered by Payments, then the Order
+     * state will get automatically transitioned to the `PaymentSettled` or `PaymentAuthorized` state.
+     */
     async addPaymentToOrder(
         ctx: RequestContext,
         orderId: ID,
@@ -846,6 +974,15 @@ export class OrderService {
         return order;
     }
 
+    /**
+     * @description
+     * This method is used after modifying an existing completed order using the `modifyOrder()` method. If the modifications
+     * cause the order total to increase (such as when adding a new OrderLine), then there will be an outstanding charge to
+     * pay.
+     *
+     * This method allows you to add a new Payment and assumes the actual processing has been done manually, e.g. in the
+     * dashboard of your payment provider.
+     */
     async addManualPaymentToOrder(
         ctx: RequestContext,
         input: ManualPaymentInput,
@@ -878,6 +1015,11 @@ export class OrderService {
         return order;
     }
 
+    /**
+     * @description
+     * Settles a payment by invoking the {@link PaymentMethodHandler}'s `settlePayment()` method. Automatically
+     * transitions the Order state if all Payments are settled.
+     */
     async settlePayment(
         ctx: RequestContext,
         paymentId: ID,
@@ -899,6 +1041,10 @@ export class OrderService {
         return payment;
     }
 
+    /**
+     * @description
+     * Creates a new Fulfillment associated with the given Order and OrderItems.
+     */
     async createFulfillment(
         ctx: RequestContext,
         input: FulfillOrderInput,
@@ -975,6 +1121,10 @@ export class OrderService {
         }
     }
 
+    /**
+     * @description
+     * Returns an array of all Fulfillments associated with the Order.
+     */
     async getOrderFulfillments(ctx: RequestContext, order: Order): Promise<Fulfillment[]> {
         let lines: OrderLine[];
         if (order.lines?.[0]?.items?.[0]?.fulfillments !== undefined) {
@@ -995,6 +1145,10 @@ export class OrderService {
         return unique(fulfillments, 'id');
     }
 
+    /**
+     * @description
+     * Returns an array of all Surcharges associated with the Order.
+     */
     async getOrderSurcharges(ctx: RequestContext, orderId: ID): Promise<Surcharge[]> {
         const order = await this.connection.getEntityOrThrow(ctx, Order, orderId, {
             channelId: ctx.channelId,
@@ -1003,6 +1157,11 @@ export class OrderService {
         return order.surcharges || [];
     }
 
+    /**
+     * @description
+     * Cancels an Order by transitioning it to the `Cancelled` state. If stock is being tracked for the ProductVariants
+     * in the Order, then new {@link StockMovement}s will be created to correct the stock levels.
+     */
     async cancelOrder(
         ctx: RequestContext,
         input: CancelOrderInput,
@@ -1088,6 +1247,11 @@ export class OrderService {
         return orderItemsAreAllCancelled(orderWithItems);
     }
 
+    /**
+     * @description
+     * Creates a {@link Refund} against the order and in doing so invokes the `createRefund()` method of the
+     * {@link PaymentMethodHandler}.
+     */
     async refundOrder(
         ctx: RequestContext,
         input: RefundOrderInput,
@@ -1134,6 +1298,10 @@ export class OrderService {
         return await this.paymentService.createRefund(ctx, input, order, items, payment);
     }
 
+    /**
+     * @description
+     * Settles a Refund by transitioning it to the `Settled` state.
+     */
     async settleRefund(ctx: RequestContext, input: SettleRefundInput): Promise<Refund> {
         const refund = await this.connection.getEntityOrThrow(ctx, Refund, input.id, {
             relations: ['payment', 'payment.order'],
@@ -1149,6 +1317,10 @@ export class OrderService {
         return refund;
     }
 
+    /**
+     * @description
+     * Associates a Customer with the Order.
+     */
     async addCustomerToOrder(ctx: RequestContext, orderId: ID, customer: Customer): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         order.customer = customer;
@@ -1175,6 +1347,10 @@ export class OrderService {
         return order;
     }
 
+    /**
+     * @description
+     * Creates a new "ORDER_NOTE" type {@link OrderHistoryEntry} in the Order's history timeline.
+     */
     async addNoteToOrder(ctx: RequestContext, input: AddNoteToOrderInput): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, input.id);
         await this.historyService.createHistoryEntryForOrder(
@@ -1216,8 +1392,11 @@ export class OrderService {
     }
 
     /**
+     * @description
      * When a guest user with an anonymous Order signs in and has an existing Order associated with that Customer,
      * we need to reconcile the contents of the two orders.
+     *
+     * The logic used to do the merging is specified in the {@link OrderOptions} `mergeStrategy` config setting.
      */
     async mergeOrders(
         ctx: RequestContext,

+ 34 - 0
packages/core/src/service/services/payment.service.ts

@@ -62,6 +62,14 @@ export class PaymentService {
         });
     }
 
+    /**
+     * @description
+     * Transitions a Payment to the given state.
+     *
+     * When updating a Payment in the context of an Order, it is
+     * preferable to use the {@link OrderService} `transitionPaymentToState()` method, which will also handle
+     * updating the Order state too.
+     */
     async transitionToState(
         ctx: RequestContext,
         paymentId: ID,
@@ -89,6 +97,14 @@ export class PaymentService {
         return this.paymentStateMachine.getNextStates(payment);
     }
 
+    /**
+     * @description
+     * Creates a new Payment.
+     *
+     * When creating a Payment in the context of an Order, it is
+     * preferable to use the {@link OrderService} `addPaymentToOrder()` method, which will also handle
+     * updating the Order state too.
+     */
     async createPayment(
         ctx: RequestContext,
         order: Order,
@@ -125,6 +141,14 @@ export class PaymentService {
         return payment;
     }
 
+    /**
+     * @description
+     * Settles a Payment.
+     *
+     * When settling a Payment in the context of an Order, it is
+     * preferable to use the {@link OrderService} `settlePayment()` method, which will also handle
+     * updating the Order state too.
+     */
     async settlePayment(ctx: RequestContext, paymentId: ID): Promise<PaymentStateTransitionError | Payment> {
         const payment = await this.connection.getEntityOrThrow(ctx, Payment, paymentId, {
             relations: ['order'],
@@ -162,7 +186,12 @@ export class PaymentService {
     }
 
     /**
+     * @description
      * Creates a Payment from the manual payment mutation in the Admin API
+     *
+     * When creating a manual Payment in the context of an Order, it is
+     * preferable to use the {@link OrderService} `addManualPaymentToOrder()` method, which will also handle
+     * updating the Order state too.
      */
     async createManualPayment(ctx: RequestContext, order: Order, amount: number, input: ManualPaymentInput) {
         const initialState = 'Created';
@@ -184,9 +213,14 @@ export class PaymentService {
     }
 
     /**
+     * @description
      * Creates a Refund against the specified Payment. If the amount to be refunded exceeds the value of the
      * specified Payment (in the case of multiple payments on a single Order), then the remaining outstanding
      * refund amount will be refunded against the next available Payment from the Order.
+     *
+     * When creating a Refund in the context of an Order, it is
+     * preferable to use the {@link OrderService} `refundOrder()` method, which performs additional
+     * validation.
      */
     async createRefund(
         ctx: RequestContext,

+ 29 - 3
packages/core/src/service/services/product-variant.service.ts

@@ -194,6 +194,10 @@ export class ProductVariantService {
         });
     }
 
+    /**
+     * @description
+     * Returns a {@link PaginatedList} of all ProductVariants associated with the given Collection.
+     */
     getVariantsByCollectionId(
         ctx: RequestContext,
         collectionId: ID,
@@ -228,6 +232,10 @@ export class ProductVariantService {
         });
     }
 
+    /**
+     * @description
+     * Returns all Channels to which the ProductVariant is assigned.
+     */
     async getProductVariantChannels(ctx: RequestContext, productVariantId: ID): Promise<Channel[]> {
         const variant = await this.connection.getEntityOrThrow(ctx, ProductVariant, productVariantId, {
             relations: ['channels'],
@@ -236,6 +244,10 @@ export class ProductVariantService {
         return variant.channels;
     }
 
+    /**
+     * @description
+     * Returns the ProductVariant associated with the given {@link OrderLine}.
+     */
     async getVariantByOrderLineId(ctx: RequestContext, orderLineId: ID): Promise<Translated<ProductVariant>> {
         const { productVariant } = await this.connection.getEntityOrThrow(ctx, OrderLine, orderLineId, {
             relations: ['productVariant'],
@@ -243,6 +255,10 @@ export class ProductVariantService {
         return translateDeep(productVariant, ctx.languageCode);
     }
 
+    /**
+     * @description
+     * Returns the {@link ProductOption}s for the given ProductVariant.
+     */
     getOptionsForVariant(ctx: RequestContext, variantId: ID): Promise<Array<Translated<ProductOption>>> {
         return this.connection
             .findOneInChannel(ctx, ProductVariant, variantId, ctx.channelId, {
@@ -262,9 +278,10 @@ export class ProductVariantService {
     }
 
     /**
+     * @description
      * Returns the Product associated with the ProductVariant. Whereas the `ProductService.findOne()`
      * method performs a large multi-table join with all the typical data needed for a "product detail"
-     * page, this method returns on the Product itself.
+     * page, this method returns only the Product itself.
      */
     async getProductForVariant(ctx: RequestContext, variant: ProductVariant): Promise<Translated<Product>> {
         const product = await this.connection.getEntityOrThrow(ctx, Product, variant.productId, {
@@ -276,7 +293,8 @@ export class ProductVariantService {
     /**
      * @description
      * Returns the number of saleable units of the ProductVariant, i.e. how many are available
-     * for purchase by Customers.
+     * for purchase by Customers. This is determined by the ProductVariant's `stockOnHand` value,
+     * as well as the local and global `outOfStockThreshold` settings.
      */
     async getSaleableStockLevel(ctx: RequestContext, variant: ProductVariant): Promise<number> {
         const { outOfStockThreshold, trackInventory } = await this.requestCache.get(
@@ -490,7 +508,8 @@ export class ProductVariantService {
     }
 
     /**
-     * Creates a ProductVariantPrice for the given ProductVariant/Channel combination.
+     * @description
+     * Creates a {@link ProductVariantPrice} for the given ProductVariant/Channel combination.
      */
     async createOrUpdateProductVariantPrice(
         ctx: RequestContext,
@@ -528,6 +547,7 @@ export class ProductVariantService {
     }
 
     /**
+     * @description
      * This method is intended to be used by the ProductVariant GraphQL entity resolver to resolve the
      * price-related fields which need to be populated at run-time using the `applyChannelPriceAndTax`
      * method.
@@ -575,6 +595,7 @@ export class ProductVariantService {
     }
 
     /**
+     * @description
      * Populates the `price` field with the price for the specified channel.
      */
     async applyChannelPriceAndTax(
@@ -585,6 +606,11 @@ export class ProductVariantService {
         return this.productPriceApplicator.applyChannelPriceAndTax(variant, ctx, order);
     }
 
+    /**
+     * @description
+     * Assigns the specified ProductVariants to the specified Channel. In doing so, it will create a new
+     * {@link ProductVariantPrice} and also assign the associated Product and any Assets to the Channel too.
+     */
     async assignProductVariantsToChannel(
         ctx: RequestContext,
         input: AssignProductVariantsToChannelInput,

+ 12 - 0
packages/core/src/service/services/product.service.ts

@@ -122,6 +122,10 @@ export class ProductService {
             );
     }
 
+    /**
+     * @description
+     * Returns all Channels to which the Product is assigned.
+     */
     async getProductChannels(ctx: RequestContext, productId: ID): Promise<Channel[]> {
         const product = await this.connection.getEntityOrThrow(ctx, Product, productId, {
             relations: ['channels'],
@@ -240,6 +244,14 @@ export class ProductService {
         };
     }
 
+    /**
+     * @description
+     * Assigns a Product to the specified Channel, and optionally uses a `priceFactor` to set the ProductVariantPrices
+     * on the new Channel.
+     *
+     * Internally, this method will also call {@link ProductVariantService} `assignassignProductVariantsToChannel()` for
+     * each of the Product's variants, and will assign the Product's Assets to the Channel too.
+     */
     async assignProductsToChannel(
         ctx: RequestContext,
         input: AssignProductsToChannelInput,

+ 10 - 0
packages/core/src/service/services/promotion.service.ts

@@ -194,6 +194,12 @@ export class PromotionService {
         return promotions;
     }
 
+    /**
+     * @description
+     * Checks the validity of a coupon code, by checking that it is associated with an existing,
+     * enabled and non-expired Promotion. Additionally, if there is a usage limit on the coupon code,
+     * this method will enforce that limit against the specified Customer.
+     */
     async validateCouponCode(
         ctx: RequestContext,
         couponCode: string,
@@ -221,6 +227,10 @@ export class PromotionService {
         return promotion;
     }
 
+    /**
+     * @description
+     * Used internally to associate a Promotion with an Order, once an Order has been placed.
+     */
     async addPromotionsToOrder(ctx: RequestContext, order: Order): Promise<Order> {
         const allPromotionIds = order.discounts.map(
             a => AdjustmentSource.decodeSourceId(a.adjustmentSource).id,

+ 10 - 0
packages/core/src/service/services/role.service.ts

@@ -77,6 +77,10 @@ export class RoleService {
         return this.findOne(ctx, roleId).then(role => (role ? role.channels : []));
     }
 
+    /**
+     * @description
+     * Returns the special SuperAdmin Role, which always exists in Vendure.
+     */
     getSuperAdminRole(): Promise<Role> {
         return this.getRoleByCode(SUPER_ADMIN_ROLE_CODE).then(role => {
             if (!role) {
@@ -86,6 +90,10 @@ export class RoleService {
         });
     }
 
+    /**
+     * @description
+     * Returns the special Customer Role, which always exists in Vendure.
+     */
     getCustomerRole(): Promise<Role> {
         return this.getRoleByCode(CUSTOMER_ROLE_CODE).then(role => {
             if (!role) {
@@ -96,6 +104,7 @@ export class RoleService {
     }
 
     /**
+     * @description
      * Returns all the valid Permission values
      */
     getAllPermissions(): string[] {
@@ -103,6 +112,7 @@ export class RoleService {
     }
 
     /**
+     * @description
      * Returns true if the User has the specified permission on that Channel
      */
     async userHasPermissionOnChannel(

+ 32 - 1
packages/core/src/service/services/session.service.ts

@@ -42,14 +42,17 @@ export class SessionService implements EntitySubscriberInterface {
         this.connection.rawConnection.subscribers.push(this);
     }
 
+    /** @internal */
     afterInsert(event: InsertEvent<any>): Promise<any> | void {
         this.clearSessionCacheOnDataChange(event);
     }
 
+    /** @internal */
     afterRemove(event: RemoveEvent<any>): Promise<any> | void {
         this.clearSessionCacheOnDataChange(event);
     }
 
+    /** @internal */
     afterUpdate(event: UpdateEvent<any>): Promise<any> | void {
         this.clearSessionCacheOnDataChange(event);
     }
@@ -67,6 +70,10 @@ export class SessionService implements EntitySubscriberInterface {
         }
     }
 
+    /**
+     * @description
+     * Creates a new {@link AuthenticatedSession}. To be used after successful authentication.
+     */
     async createNewAuthenticatedSession(
         ctx: RequestContext,
         user: User,
@@ -94,7 +101,9 @@ export class SessionService implements EntitySubscriberInterface {
     }
 
     /**
-     * Create an anonymous session.
+     * @description
+     * Create an {@link AnonymousSession} and caches it using the configured {@link SessionCacheStrategy},
+     * and returns the cached session object.
      */
     async createAnonymousSession(): Promise<CachedSession> {
         const token = await this.generateSessionToken();
@@ -111,6 +120,10 @@ export class SessionService implements EntitySubscriberInterface {
         return serializedSession;
     }
 
+    /**
+     * @description
+     * Returns the cached session object matching the given session token.
+     */
     async getSessionFromToken(sessionToken: string): Promise<CachedSession | undefined> {
         let serializedSession = await this.sessionCacheStrategy.get(sessionToken);
         const stale = !!(serializedSession && serializedSession.cacheExpiry < new Date().getTime() / 1000);
@@ -128,6 +141,10 @@ export class SessionService implements EntitySubscriberInterface {
         return serializedSession;
     }
 
+    /**
+     * @description
+     * Serializes a {@link Session} instance into a simplified plain object suitable for caching.
+     */
     serializeSession(session: AuthenticatedSession | AnonymousSession): CachedSession {
         const expiry =
             Math.floor(new Date().getTime() / 1000) + this.configService.authOptions.sessionCacheTTL;
@@ -172,6 +189,10 @@ export class SessionService implements EntitySubscriberInterface {
         }
     }
 
+    /**
+     * @description
+     * Sets the `activeOrder` on the given cached session object and updates the cache.
+     */
     async setActiveOrder(
         ctx: RequestContext,
         serializedSession: CachedSession,
@@ -190,6 +211,10 @@ export class SessionService implements EntitySubscriberInterface {
         return serializedSession;
     }
 
+    /**
+     * @description
+     * Clears the `activeOrder` on the given cached session object and updates the cache.
+     */
     async unsetActiveOrder(ctx: RequestContext, serializedSession: CachedSession): Promise<CachedSession> {
         if (serializedSession.activeOrderId) {
             const session = await this.connection
@@ -206,6 +231,10 @@ export class SessionService implements EntitySubscriberInterface {
         return serializedSession;
     }
 
+    /**
+     * @description
+     * Sets the `activeChannel` on the given cached session object and updates the cache.
+     */
     async setActiveChannel(serializedSession: CachedSession, channel: Channel): Promise<CachedSession> {
         const session = await this.connection
             .getRepository(Session)
@@ -221,6 +250,7 @@ export class SessionService implements EntitySubscriberInterface {
     }
 
     /**
+     * @description
      * Deletes all existing sessions for the given user.
      */
     async deleteSessionsByUser(ctx: RequestContext, user: User): Promise<void> {
@@ -234,6 +264,7 @@ export class SessionService implements EntitySubscriberInterface {
     }
 
     /**
+     * @description
      * Deletes all existing sessions with the given activeOrder.
      */
     async deleteSessionsByActiveOrderId(ctx: RequestContext, activeOrderId: ID): Promise<void> {

+ 1 - 0
packages/core/src/service/services/shipping-method.service.ts

@@ -45,6 +45,7 @@ export class ShippingMethodService {
         private customFieldRelationService: CustomFieldRelationService,
     ) {}
 
+    /** @internal */
     async initShippingMethods() {
         if (this.configService.shippingOptions.fulfillmentHandlers.length === 0) {
             throw new Error(

+ 34 - 0
packages/core/src/service/services/stock-movement.service.ts

@@ -43,6 +43,10 @@ export class StockMovementService {
         private eventBus: EventBus,
     ) {}
 
+    /**
+     * @description
+     * Returns a {@link PaginatedList} of all StockMovements associated with the specified ProductVariant.
+     */
     getStockMovementsByProductVariantId(
         ctx: RequestContext,
         productVariantId: ID,
@@ -61,6 +65,11 @@ export class StockMovementService {
             });
     }
 
+    /**
+     * @description
+     * Adjusts the stock level of the ProductVariant, creating a new {@link StockAdjustment} entity
+     * in the process.
+     */
     async adjustProductVariantStock(
         ctx: RequestContext,
         productVariantId: ID,
@@ -82,6 +91,12 @@ export class StockMovementService {
         return adjustment;
     }
 
+    /**
+     * @description
+     * Creates a new {@link Allocation} for each OrderLine in the Order. For ProductVariants
+     * which are configured to track stock levels, the `ProductVariant.stockAllocated` value is
+     * increased, indicating that this quantity of stock is allocated and cannot be sold.
+     */
     async createAllocationsForOrder(ctx: RequestContext, order: Order): Promise<Allocation[]> {
         if (order.active !== false) {
             throw new InternalServerError('error.cannot-create-allocations-for-active-order');
@@ -115,6 +130,13 @@ export class StockMovementService {
         return savedAllocations;
     }
 
+    /**
+     * @description
+     * Creates {@link Sale}s for each OrderLine in the Order. For ProductVariants
+     * which are configured to track stock levels, the `ProductVariant.stockAllocated` value is
+     * reduced and the `stockOnHand` value is also reduced the the OrderLine quantity, indicating
+     * that the stock is no longer allocated, but is actually sold and no longer available.
+     */
     async createSalesForOrder(ctx: RequestContext, orderItems: OrderItem[]): Promise<Sale[]> {
         const sales: Sale[] = [];
         const globalTrackInventory = (await this.globalSettingsService.getSettings(ctx)).trackInventory;
@@ -162,6 +184,12 @@ export class StockMovementService {
         return savedSales;
     }
 
+    /**
+     * @description
+     * Creates a {@link Cancellation} for each of the specified OrderItems. For ProductVariants
+     * which are configured to track stock levels, the `ProductVariant.stockOnHand` value is
+     * increased for each Cancellation, allowing that stock to be sold again.
+     */
     async createCancellationsForOrderItems(ctx: RequestContext, items: OrderItem[]): Promise<Cancellation[]> {
         const orderItems = await this.connection.getRepository(ctx, OrderItem).findByIds(
             items.map(i => i.id),
@@ -203,6 +231,12 @@ export class StockMovementService {
         return savedCancellations;
     }
 
+    /**
+     * @description
+     * Creates a {@link Release} for each of the specified OrderItems. For ProductVariants
+     * which are configured to track stock levels, the `ProductVariant.stockAllocated` value is
+     * reduced, indicating that this stock is once again available to buy.
+     */
     async createReleasesForOrderItems(ctx: RequestContext, items: OrderItem[]): Promise<Release[]> {
         const orderItems = await this.connection.getRepository(ctx, OrderItem).findByIds(
             items.map(i => i.id),

+ 7 - 2
packages/core/src/service/services/tax-rate.service.ts

@@ -128,16 +128,21 @@ export class TaxRateService {
         }
     }
 
+    /**
+     * @description
+     * Returns the applicable TaxRate based on the specified Zone and TaxCategory. Used when calculating Order
+     * prices.
+     */
     async getApplicableTaxRate(ctx: RequestContext, zone: Zone, taxCategory: TaxCategory): Promise<TaxRate> {
         const rate = (await this.getActiveTaxRates(ctx)).find(r => r.test(zone, taxCategory));
         return rate || this.defaultTaxRate;
     }
 
-    async getActiveTaxRates(ctx: RequestContext): Promise<TaxRate[]> {
+    private async getActiveTaxRates(ctx: RequestContext): Promise<TaxRate[]> {
         return this.cacheService.get(ctx, activeTaxRatesKey, () => this.findActiveTaxRates(ctx));
     }
 
-    async updateActiveTaxRates(ctx: RequestContext) {
+    private async updateActiveTaxRates(ctx: RequestContext) {
         this.cacheService.set(ctx, activeTaxRatesKey, await this.findActiveTaxRates(ctx));
     }
 

+ 58 - 10
packages/core/src/service/services/user.service.ts

@@ -57,6 +57,10 @@ export class UserService {
         });
     }
 
+    /**
+     * @description
+     * Creates a new User with the special `customer` Role and using the {@link NativeAuthenticationStrategy}.
+     */
     async createCustomerUser(ctx: RequestContext, identifier: string, password?: string): Promise<User> {
         const user = new User();
         user.identifier = identifier;
@@ -67,6 +71,12 @@ export class UserService {
             .save(await this.addNativeAuthenticationMethod(ctx, user, identifier, password));
     }
 
+    /**
+     * @description
+     * Adds a new {@link NativeAuthenticationMethod} to the User. If the {@link AuthOptions} `requireVerification`
+     * is set to `true` (as is the default), the User will be marked as unverified until the email verification
+     * flow is completed.
+     */
     async addNativeAuthenticationMethod(
         ctx: RequestContext,
         user: User,
@@ -103,6 +113,10 @@ export class UserService {
         return user;
     }
 
+    /**
+     * @description
+     * Creates a new verified User using the {@link NativeAuthenticationStrategy}.
+     */
     async createAdminUser(ctx: RequestContext, identifier: string, password: string): Promise<User> {
         const user = new User({
             identifier,
@@ -125,6 +139,11 @@ export class UserService {
         await this.connection.getRepository(ctx, User).update({ id: userId }, { deletedAt: new Date() });
     }
 
+    /**
+     * @description
+     * Sets the {@link NativeAuthenticationMethod} `verificationToken` as part of the User email verification
+     * flow.
+     */
     async setVerificationToken(ctx: RequestContext, user: User): Promise<User> {
         const nativeAuthMethod = user.getNativeAuthenticationMethod();
         nativeAuthMethod.verificationToken = this.verificationTokenGenerator.generateVerificationToken();
@@ -133,6 +152,13 @@ export class UserService {
         return this.connection.getRepository(ctx, User).save(user);
     }
 
+    /**
+     * @description
+     * Verifies a verificationToken by looking for a User which has previously had it set using the
+     * `setVerificationToken()` method, and checks that the token is valid and has not expired.
+     *
+     * If valid, the User will be set to `verified: true`.
+     */
     async verifyUserByToken(
         ctx: RequestContext,
         verificationToken: string,
@@ -170,18 +196,29 @@ export class UserService {
         }
     }
 
+    /**
+     * @description
+     * Sets the {@link NativeAuthenticationMethod} `passwordResetToken` as part of the User password reset
+     * flow.
+     */
     async setPasswordResetToken(ctx: RequestContext, emailAddress: string): Promise<User | undefined> {
         const user = await this.getUserByEmailAddress(ctx, emailAddress);
         if (!user) {
             return;
         }
         const nativeAuthMethod = user.getNativeAuthenticationMethod();
-        nativeAuthMethod.passwordResetToken =
-            await this.verificationTokenGenerator.generateVerificationToken();
+        nativeAuthMethod.passwordResetToken = this.verificationTokenGenerator.generateVerificationToken();
         await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(nativeAuthMethod);
         return user;
     }
 
+    /**
+     * @description
+     * Verifies a passwordResetToken by looking for a User which has previously had it set using the
+     * `setPasswordResetToken()` method, and checks that the token is valid and has not expired.
+     *
+     * If valid, the User's credentials will be updated with the new password.
+     */
     async resetPasswordByToken(
         ctx: RequestContext,
         passwordResetToken: string,
@@ -208,6 +245,7 @@ export class UserService {
     }
 
     /**
+     * @description
      * Changes the User identifier without an email verification step, so this should be only used when
      * an Administrator is setting a new email address.
      */
@@ -235,8 +273,21 @@ export class UserService {
     }
 
     /**
+     * @description
+     * Sets the {@link NativeAuthenticationMethod} `identifierChangeToken` as part of the User email address change
+     * flow.
+     */
+    async setIdentifierChangeToken(ctx: RequestContext, user: User): Promise<User> {
+        const nativeAuthMethod = user.getNativeAuthenticationMethod();
+        nativeAuthMethod.identifierChangeToken = this.verificationTokenGenerator.generateVerificationToken();
+        await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(nativeAuthMethod);
+        return user;
+    }
+
+    /**
+     * @description
      * Changes the User identifier as part of the storefront flow used by Customers to set a
-     * new email address.
+     * new email address, with the token previously set using the `setIdentifierChangeToken()` method.
      */
     async changeIdentifierByToken(
         ctx: RequestContext,
@@ -277,6 +328,10 @@ export class UserService {
         return { user, oldIdentifier };
     }
 
+    /**
+     * @description
+     * Updates the password for a User with the {@link NativeAuthenticationMethod}.
+     */
     async updatePassword(
         ctx: RequestContext,
         userId: ID,
@@ -304,11 +359,4 @@ export class UserService {
             .save(nativeAuthMethod, { reload: false });
         return true;
     }
-
-    async setIdentifierChangeToken(ctx: RequestContext, user: User): Promise<User> {
-        const nativeAuthMethod = user.getNativeAuthenticationMethod();
-        nativeAuthMethod.identifierChangeToken = this.verificationTokenGenerator.generateVerificationToken();
-        await this.connection.getRepository(ctx, NativeAuthenticationMethod).save(nativeAuthMethod);
-        return user;
-    }
 }

+ 1 - 0
packages/core/src/service/services/zone.service.ts

@@ -35,6 +35,7 @@ export class ZoneService {
     private zones: SelfRefreshingCache<Zone[], [RequestContext]>;
     constructor(private connection: TransactionalConnection, private configService: ConfigService) {}
 
+    /** @internal */
     async initZones() {
         this.zones = await createSelfRefreshingCache({
             name: 'ZoneService.zones',