Browse Source

refactor(core): Replace TypeORM Connection with TransactionalConnection

Michael Bromley 5 years ago
parent
commit
09e48acedd
48 changed files with 271 additions and 252 deletions
  1. 3 3
      packages/core/e2e/fixtures/test-plugins/list-query-plugin.ts
  2. 4 4
      packages/core/src/data-import/providers/importer/fast-importer.service.ts
  3. 3 4
      packages/core/src/plugin/default-search-plugin/fulltext-search.service.ts
  4. 7 4
      packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts
  5. 4 4
      packages/core/src/plugin/default-search-plugin/search-strategy/mysql-search-strategy.ts
  6. 4 3
      packages/core/src/plugin/default-search-plugin/search-strategy/postgres-search-strategy.ts
  7. 4 4
      packages/core/src/plugin/default-search-plugin/search-strategy/sqlite-search-strategy.ts
  8. 8 9
      packages/core/src/service/controllers/collection.controller.ts
  9. 2 3
      packages/core/src/service/controllers/tax-rate.controller.ts
  10. 8 9
      packages/core/src/service/helpers/external-authentication/external-authentication.service.ts
  11. 8 7
      packages/core/src/service/helpers/list-query-builder/list-query-builder.ts
  12. 2 3
      packages/core/src/service/helpers/order-calculator/order-calculator.ts
  13. 2 3
      packages/core/src/service/helpers/order-state-machine/order-state-machine.ts
  14. 2 3
      packages/core/src/service/helpers/slug-validator/slug-validator.ts
  15. 8 7
      packages/core/src/service/helpers/translatable-saver/translatable-saver.ts
  16. 10 7
      packages/core/src/service/helpers/translatable-saver/translation-differ.ts
  17. 9 8
      packages/core/src/service/helpers/utils/channel-aware-orm-utils.ts
  18. 5 4
      packages/core/src/service/helpers/utils/get-entity-or-throw.ts
  19. 9 9
      packages/core/src/service/services/administrator.service.ts
  20. 3 4
      packages/core/src/service/services/asset.service.ts
  21. 4 5
      packages/core/src/service/services/auth.service.ts
  22. 9 10
      packages/core/src/service/services/channel.service.ts
  23. 9 3
      packages/core/src/service/services/collection.service.ts
  24. 2 3
      packages/core/src/service/services/country.service.ts
  25. 5 6
      packages/core/src/service/services/customer-group.service.ts
  26. 5 6
      packages/core/src/service/services/customer.service.ts
  27. 8 7
      packages/core/src/service/services/facet-value.service.ts
  28. 5 5
      packages/core/src/service/services/facet.service.ts
  29. 2 3
      packages/core/src/service/services/global-settings.service.ts
  30. 2 3
      packages/core/src/service/services/history.service.ts
  31. 2 1
      packages/core/src/service/services/order-testing.service.ts
  32. 4 4
      packages/core/src/service/services/order.service.ts
  33. 3 2
      packages/core/src/service/services/payment-method.service.ts
  34. 24 15
      packages/core/src/service/services/product-option-group.service.ts
  35. 11 13
      packages/core/src/service/services/product-option.service.ts
  36. 3 4
      packages/core/src/service/services/product-variant.service.ts
  37. 4 5
      packages/core/src/service/services/product.service.ts
  38. 4 5
      packages/core/src/service/services/promotion.service.ts
  39. 5 6
      packages/core/src/service/services/role.service.ts
  40. 3 2
      packages/core/src/service/services/session.service.ts
  41. 4 5
      packages/core/src/service/services/shipping-method.service.ts
  42. 8 7
      packages/core/src/service/services/stock-movement.service.ts
  43. 2 3
      packages/core/src/service/services/tax-category.service.ts
  44. 3 4
      packages/core/src/service/services/tax-rate.service.ts
  45. 20 17
      packages/core/src/service/services/user.service.ts
  46. 2 3
      packages/core/src/service/services/zone.service.ts
  47. 12 2
      packages/core/src/service/transaction/transactional-connection.ts
  48. 1 1
      packages/core/src/service/transaction/unit-of-work.ts

+ 3 - 3
packages/core/e2e/fixtures/test-plugins/list-query-plugin.ts

@@ -1,14 +1,14 @@
 import { Args, Query, Resolver } from '@nestjs/graphql';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     ListQueryBuilder,
     OnVendureBootstrap,
     PluginCommonModule,
+    TransactionalConnection,
     VendureEntity,
     VendurePlugin,
 } from '@vendure/core';
 import gql from 'graphql-tag';
-import { Column, Connection, Entity } from 'typeorm';
+import { Column, Entity } from 'typeorm';
 
 @Entity()
 export class TestEntity extends VendureEntity {
@@ -70,7 +70,7 @@ const adminApiExtensions = gql`
     },
 })
 export class ListQueryPlugin implements OnVendureBootstrap {
-    constructor(@InjectConnection() private connection: Connection) {}
+    constructor(private connection: TransactionalConnection) {}
 
     async onVendureBootstrap() {
         const count = await this.connection.getRepository(TestEntity).count();

+ 4 - 4
packages/core/src/data-import/providers/importer/fast-importer.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateProductInput,
     CreateProductOptionGroupInput,
@@ -7,7 +6,6 @@ import {
     CreateProductVariantInput,
 } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { Channel } from '../../../entity/channel/channel.entity';
 import { ProductOptionGroupTranslation } from '../../../entity/product-option-group/product-option-group-translation.entity';
@@ -24,6 +22,7 @@ import { Product } from '../../../entity/product/product.entity';
 import { TranslatableSaver } from '../../../service/helpers/translatable-saver/translatable-saver';
 import { ChannelService } from '../../../service/services/channel.service';
 import { StockMovementService } from '../../../service/services/stock-movement.service';
+import { TransactionalConnection } from '../../../service/transaction/transactional-connection';
 
 /**
  * A service to import entities into the database. This replaces the regular `create` methods of the service layer with faster
@@ -35,7 +34,7 @@ import { StockMovementService } from '../../../service/services/stock-movement.s
 export class FastImporterService {
     private defaultChannel: Channel;
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private channelService: ChannelService,
         private stockMovementService: StockMovementService,
         private translatableSaver: TranslatableSaver,
@@ -95,8 +94,9 @@ export class FastImporterService {
 
     async addOptionGroupToProduct(productId: ID, optionGroupId: ID) {
         await this.connection
+            .getRepository(Product)
             .createQueryBuilder()
-            .relation(Product, 'optionGroups')
+            .relation('optionGroups')
             .of(productId)
             .add(optionGroupId);
     }

+ 3 - 4
packages/core/src/plugin/default-search-plugin/fulltext-search.service.ts

@@ -1,8 +1,6 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { SearchInput, SearchResponse } from '@vendure/common/lib/generated-types';
 import { Omit } from '@vendure/common/lib/omit';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { InternalServerError } from '../../common/error/errors';
@@ -12,6 +10,7 @@ import { Job } from '../../job-queue/job';
 import { FacetValueService } from '../../service/services/facet-value.service';
 import { ProductVariantService } from '../../service/services/product-variant.service';
 import { SearchService } from '../../service/services/search.service';
+import { TransactionalConnection } from '../../service/transaction/transactional-connection';
 
 import { SearchIndexService } from './indexer/search-index.service';
 import { MysqlSearchStrategy } from './search-strategy/mysql-search-strategy';
@@ -29,7 +28,7 @@ export class FulltextSearchService {
     private readonly minTermLength = 2;
 
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private eventBus: EventBus,
         private facetValueService: FacetValueService,
         private productVariantService: ProductVariantService,
@@ -89,7 +88,7 @@ export class FulltextSearchService {
      * Sets the SearchStrategy appropriate to th configured database type.
      */
     private setSearchStrategy() {
-        switch (this.connection.options.type) {
+        switch (this.connection.rawConnection.options.type) {
             case 'mysql':
             case 'mariadb':
                 this.searchStrategy = new MysqlSearchStrategy(this.connection);

+ 7 - 4
packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts

@@ -1,11 +1,9 @@
 import { Controller } from '@nestjs/common';
 import { MessagePattern } from '@nestjs/microservices';
-import { InjectConnection } from '@nestjs/typeorm';
 import { LanguageCode } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
 import { unique } from '@vendure/common/lib/unique';
 import { Observable } from 'rxjs';
-import { Connection } from 'typeorm';
 import { FindOptionsUtils } from 'typeorm/find-options/FindOptionsUtils';
 
 import { RequestContext } from '../../../api/common/request-context';
@@ -16,6 +14,7 @@ import { ProductVariant } from '../../../entity/product-variant/product-variant.
 import { Product } from '../../../entity/product/product.entity';
 import { translateDeep } from '../../../service/helpers/utils/translate-entity';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
+import { TransactionalConnection } from '../../../service/transaction/transactional-connection';
 import { asyncObservable } from '../../../worker/async-observable';
 import { SearchIndexItem } from '../search-index-item.entity';
 import {
@@ -52,7 +51,7 @@ export class IndexerController {
     private queue = new AsyncQueue('search-index');
 
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private productVariantService: ProductVariantService,
     ) {}
 
@@ -295,7 +294,11 @@ export class IndexerController {
         FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
             relations: variantRelations,
         });
-        FindOptionsUtils.joinEagerRelations(qb, qb.alias, this.connection.getMetadata(ProductVariant));
+        FindOptionsUtils.joinEagerRelations(
+            qb,
+            qb.alias,
+            this.connection.rawConnection.getMetadata(ProductVariant),
+        );
         qb.leftJoin('variants.product', 'product')
             .leftJoin('product.channels', 'channel')
             .where('channel.id = :channelId', { channelId })

+ 4 - 4
packages/core/src/plugin/default-search-plugin/search-strategy/mysql-search-strategy.ts

@@ -1,9 +1,9 @@
 import { LogicalOperator, SearchInput, SearchResult } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
-import { unique } from '@vendure/common/lib/unique';
-import { Brackets, Connection, SelectQueryBuilder } from 'typeorm';
+import { Brackets, SelectQueryBuilder } from 'typeorm';
 
 import { RequestContext } from '../../../api/common/request-context';
+import { TransactionalConnection } from '../../../service/transaction/transactional-connection';
 import { SearchIndexItem } from '../search-index-item.entity';
 
 import { SearchStrategy } from './search-strategy';
@@ -16,7 +16,7 @@ import { createFacetIdCountMap, mapToSearchResult } from './search-strategy-util
 export class MysqlSearchStrategy implements SearchStrategy {
     private readonly minTermLength = 2;
 
-    constructor(private connection: Connection) {}
+    constructor(private connection: TransactionalConnection) {}
 
     async getFacetValueIds(
         ctx: RequestContext,
@@ -95,7 +95,7 @@ export class MysqlSearchStrategy implements SearchStrategy {
             innerQb.andWhere('si.enabled = :enabled', { enabled: true });
         }
 
-        const totalItemsQb = this.connection
+        const totalItemsQb = this.connection.rawConnection
             .createQueryBuilder()
             .select('COUNT(*) as total')
             .from(`(${innerQb.getQuery()})`, 'inner')

+ 4 - 3
packages/core/src/plugin/default-search-plugin/search-strategy/postgres-search-strategy.ts

@@ -1,8 +1,9 @@
 import { LogicalOperator, SearchInput, SearchResult } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
-import { Brackets, Connection, SelectQueryBuilder } from 'typeorm';
+import { Brackets, SelectQueryBuilder } from 'typeorm';
 
 import { RequestContext } from '../../../api/common/request-context';
+import { TransactionalConnection } from '../../../service/transaction/transactional-connection';
 import { SearchIndexItem } from '../search-index-item.entity';
 
 import { SearchStrategy } from './search-strategy';
@@ -15,7 +16,7 @@ import { createFacetIdCountMap, mapToSearchResult } from './search-strategy-util
 export class PostgresSearchStrategy implements SearchStrategy {
     private readonly minTermLength = 2;
 
-    constructor(private connection: Connection) {}
+    constructor(private connection: TransactionalConnection) {}
 
     async getFacetValueIds(
         ctx: RequestContext,
@@ -96,7 +97,7 @@ export class PostgresSearchStrategy implements SearchStrategy {
         if (enabledOnly) {
             innerQb.andWhere('"si"."enabled" = :enabled', { enabled: true });
         }
-        const totalItemsQb = this.connection
+        const totalItemsQb = this.connection.rawConnection
             .createQueryBuilder()
             .select('COUNT(*) as total')
             .from(`(${innerQb.getQuery()})`, 'inner')

+ 4 - 4
packages/core/src/plugin/default-search-plugin/search-strategy/sqlite-search-strategy.ts

@@ -1,9 +1,9 @@
 import { LogicalOperator, SearchInput, SearchResult } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
-import { unique } from '@vendure/common/lib/unique';
-import { Brackets, Connection, SelectQueryBuilder } from 'typeorm';
+import { Brackets, SelectQueryBuilder } from 'typeorm';
 
 import { RequestContext } from '../../../api/common/request-context';
+import { TransactionalConnection } from '../../../service/transaction/transactional-connection';
 import { SearchIndexItem } from '../search-index-item.entity';
 
 import { SearchStrategy } from './search-strategy';
@@ -16,7 +16,7 @@ import { createFacetIdCountMap, mapToSearchResult } from './search-strategy-util
 export class SqliteSearchStrategy implements SearchStrategy {
     private readonly minTermLength = 2;
 
-    constructor(private connection: Connection) {}
+    constructor(private connection: TransactionalConnection) {}
 
     async getFacetValueIds(
         ctx: RequestContext,
@@ -92,7 +92,7 @@ export class SqliteSearchStrategy implements SearchStrategy {
             innerQb.andWhere('si.enabled = :enabled', { enabled: true });
         }
 
-        const totalItemsQb = this.connection
+        const totalItemsQb = this.connection.rawConnection
             .createQueryBuilder()
             .select('COUNT(*) as total')
             .from(`(${innerQb.getQuery()})`, 'inner')

+ 8 - 9
packages/core/src/service/controllers/collection.controller.ts

@@ -1,11 +1,9 @@
 import { Controller } from '@nestjs/common';
 import { MessagePattern } from '@nestjs/microservices';
-import { InjectConnection } from '@nestjs/typeorm';
 import { ConfigurableOperation } from '@vendure/common/lib/generated-types';
 import { pick } from '@vendure/common/lib/pick';
 import { ID } from '@vendure/common/lib/shared-types';
 import { Observable } from 'rxjs';
-import { Connection } from 'typeorm';
 
 import { ConfigService } from '../../config/config.service';
 import { Logger } from '../../config/logger/vendure-logger';
@@ -13,6 +11,7 @@ import { Collection } from '../../entity/collection/collection.entity';
 import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
 import { asyncObservable } from '../../worker/async-observable';
 import { CollectionService } from '../services/collection.service';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 import { ApplyCollectionFiltersMessage } from '../types/collection-messages';
 
 /**
@@ -22,7 +21,7 @@ import { ApplyCollectionFiltersMessage } from '../types/collection-messages';
 @Controller()
 export class CollectionController {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private collectionService: CollectionService,
         private configService: ConfigService,
     ) {}
@@ -31,7 +30,7 @@ export class CollectionController {
     applyCollectionFilters({
         collectionIds,
     }: ApplyCollectionFiltersMessage['data']): Observable<ApplyCollectionFiltersMessage['response']> {
-        return asyncObservable(async (observer) => {
+        return asyncObservable(async observer => {
             Logger.verbose(`Processing ${collectionIds.length} Collections`);
             const timeStart = Date.now();
             const collections = await this.connection.getRepository(Collection).findByIds(collectionIds, {
@@ -58,7 +57,7 @@ export class CollectionController {
     private async applyCollectionFiltersInternal(collection: Collection): Promise<ID[]> {
         const ancestorFilters = await this.collectionService
             .getAncestors(collection.id)
-            .then((ancestors) =>
+            .then(ancestors =>
                 ancestors.reduce(
                     (filters, c) => [...filters, ...(c.filters || [])],
                     [] as ConfigurableOperation[],
@@ -69,7 +68,7 @@ export class CollectionController {
             ...ancestorFilters,
             ...(collection.filters || []),
         ]);
-        const postIds = collection.productVariants.map((v) => v.id);
+        const postIds = collection.productVariants.map(v => v.id);
         try {
             await this.connection
                 .getRepository(Collection)
@@ -88,8 +87,8 @@ export class CollectionController {
         const preIdsSet = new Set(preIds);
         const postIdsSet = new Set(postIds);
         const difference = [
-            ...preIds.filter((id) => !postIdsSet.has(id)),
-            ...postIds.filter((id) => !preIdsSet.has(id)),
+            ...preIds.filter(id => !postIdsSet.has(id)),
+            ...postIds.filter(id => !preIdsSet.has(id)),
         ];
         return difference;
     }
@@ -105,7 +104,7 @@ export class CollectionController {
         let qb = this.connection.getRepository(ProductVariant).createQueryBuilder('productVariant');
 
         for (const filterType of collectionFilters) {
-            const filtersOfType = filters.filter((f) => f.code === filterType.code);
+            const filtersOfType = filters.filter(f => f.code === filterType.code);
             if (filtersOfType.length) {
                 for (const filter of filtersOfType) {
                     qb = filterType.apply(qb, filter.args);

+ 2 - 3
packages/core/src/service/controllers/tax-rate.controller.ts

@@ -1,15 +1,14 @@
 import { Controller } from '@nestjs/common';
 import { MessagePattern } from '@nestjs/microservices';
-import { InjectConnection } from '@nestjs/typeorm';
 import { from, Observable } from 'rxjs';
-import { Connection } from 'typeorm';
 
 import { TaxRateService } from '../services/tax-rate.service';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 import { TaxRateUpdatedMessage } from '../types/tax-rate-messages';
 
 @Controller()
 export class TaxRateController {
-    constructor(@InjectConnection() private connection: Connection, private taxRateService: TaxRateService) {}
+    constructor(private connection: TransactionalConnection, private taxRateService: TaxRateService) {}
 
     /**
      * When a TaxRate is updated on the main process, this will update the activeTaxRates

+ 8 - 9
packages/core/src/service/helpers/external-authentication/external-authentication.service.ts

@@ -1,7 +1,5 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { HistoryEntryType } from '@vendure/common/lib/generated-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { Administrator } from '../../../entity/administrator/administrator.entity';
@@ -13,6 +11,7 @@ import { AdministratorService } from '../../services/administrator.service';
 import { CustomerService } from '../../services/customer.service';
 import { HistoryService } from '../../services/history.service';
 import { RoleService } from '../../services/role.service';
+import { TransactionalConnection } from '../../transaction/transactional-connection';
 
 /**
  * @description
@@ -24,7 +23,7 @@ import { RoleService } from '../../services/role.service';
 @Injectable()
 export class ExternalAuthenticationService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private roleService: RoleService,
         private historyService: HistoryService,
         private customerService: CustomerService,
@@ -89,7 +88,7 @@ export class ExternalAuthenticationService {
             verified: config.verified || false,
         });
 
-        const authMethod = await this.connection.manager.save(
+        const authMethod = await this.connection.getRepository(ExternalAuthenticationMethod).save(
             new ExternalAuthenticationMethod({
                 externalIdentifier: config.externalIdentifier,
                 strategy: config.strategy,
@@ -97,9 +96,9 @@ export class ExternalAuthenticationService {
         );
 
         newUser.authenticationMethods = [authMethod];
-        const savedUser = await this.connection.manager.save(newUser);
+        const savedUser = await this.connection.getRepository(User).save(newUser);
 
-        const customer = await this.connection.manager.save(
+        const customer = await this.connection.getRepository(Customer).save(
             new Customer({
                 emailAddress: config.emailAddress,
                 firstName: config.firstName,
@@ -155,7 +154,7 @@ export class ExternalAuthenticationService {
             verified: true,
         });
 
-        const authMethod = await this.connection.manager.save(
+        const authMethod = await this.connection.getRepository(ExternalAuthenticationMethod).save(
             new ExternalAuthenticationMethod({
                 externalIdentifier: config.externalIdentifier,
                 strategy: config.strategy,
@@ -163,9 +162,9 @@ export class ExternalAuthenticationService {
         );
 
         newUser.authenticationMethods = [authMethod];
-        const savedUser = await this.connection.manager.save(newUser);
+        const savedUser = await this.connection.getRepository(User).save(newUser);
 
-        const administrator = await this.connection.manager.save(
+        const administrator = await this.connection.getRepository(Administrator).save(
             new Administrator({
                 emailAddress: config.emailAddress,
                 firstName: config.firstName,

+ 8 - 7
packages/core/src/service/helpers/list-query-builder/list-query-builder.ts

@@ -1,11 +1,11 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { ID, Type } from '@vendure/common/lib/shared-types';
-import { Connection, FindConditions, FindManyOptions, FindOneOptions, SelectQueryBuilder } from 'typeorm';
+import { FindConditions, FindManyOptions, FindOneOptions, SelectQueryBuilder } from 'typeorm';
 import { FindOptionsUtils } from 'typeorm/find-options/FindOptionsUtils';
 
 import { ListQueryOptions } from '../../../common/types/common-types';
 import { VendureEntity } from '../../../entity/base/base.entity';
+import { TransactionalConnection } from '../../transaction/transactional-connection';
 
 import { parseChannelParam } from './parse-channel-param';
 import { parseFilterParams } from './parse-filter-params';
@@ -20,7 +20,7 @@ export type ExtendedListQueryOptions<T extends VendureEntity> = {
 
 @Injectable()
 export class ListQueryBuilder {
-    constructor(@InjectConnection() private connection: Connection) {}
+    constructor(private connection: TransactionalConnection) {}
 
     /**
      * Creates and configures a SelectQueryBuilder for queries that return paginated lists of entities.
@@ -31,18 +31,19 @@ export class ListQueryBuilder {
         extendedOptions: ExtendedListQueryOptions<T> = {},
     ): SelectQueryBuilder<T> {
         const skip = options.skip;
+        const rawConnection = this.connection.rawConnection;
         let take = options.take;
         if (options.skip !== undefined && options.take === undefined) {
             take = Number.MAX_SAFE_INTEGER;
         }
         const sort = parseSortParams(
-            this.connection,
+            rawConnection,
             entity,
             Object.assign({}, options.sort, extendedOptions.orderBy),
         );
-        const filter = parseFilterParams(this.connection, entity, options.filter);
+        const filter = parseFilterParams(rawConnection, entity, options.filter);
 
-        const qb = this.connection.createQueryBuilder<T>(entity, entity.name.toLowerCase());
+        const qb = this.connection.getRepository(entity).createQueryBuilder(entity.name.toLowerCase());
         FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
             relations: extendedOptions.relations,
             take,
@@ -57,7 +58,7 @@ export class ListQueryBuilder {
         });
 
         if (extendedOptions.channelId) {
-            const channelFilter = parseChannelParam(this.connection, entity, extendedOptions.channelId);
+            const channelFilter = parseChannelParam(rawConnection, entity, extendedOptions.channelId);
             if (channelFilter) {
                 qb.andWhere(channelFilter.clause, channelFilter.parameters);
             }

+ 2 - 3
packages/core/src/service/helpers/order-calculator/order-calculator.ts

@@ -1,8 +1,6 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { filterAsync } from '@vendure/common/lib/filter-async';
 import { AdjustmentType } from '@vendure/common/lib/generated-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { InternalServerError } from '../../../common/error/errors';
@@ -16,13 +14,14 @@ import { ShippingMethod } from '../../../entity/shipping-method/shipping-method.
 import { Zone } from '../../../entity/zone/zone.entity';
 import { TaxRateService } from '../../services/tax-rate.service';
 import { ZoneService } from '../../services/zone.service';
+import { TransactionalConnection } from '../../transaction/transactional-connection';
 import { ShippingCalculator } from '../shipping-calculator/shipping-calculator';
 import { TaxCalculator } from '../tax-calculator/tax-calculator';
 
 @Injectable()
 export class OrderCalculator {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private zoneService: ZoneService,
         private taxRateService: TaxRateService,

+ 2 - 3
packages/core/src/service/helpers/order-state-machine/order-state-machine.ts

@@ -1,7 +1,5 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { HistoryEntryType } from '@vendure/common/lib/generated-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { IllegalOperationError } from '../../../common/error/errors';
@@ -15,6 +13,7 @@ import { Order } from '../../../entity/order/order.entity';
 import { HistoryService } from '../../services/history.service';
 import { PromotionService } from '../../services/promotion.service';
 import { StockMovementService } from '../../services/stock-movement.service';
+import { TransactionalConnection } from '../../transaction/transactional-connection';
 import { getEntityOrThrow } from '../utils/get-entity-or-throw';
 import {
     orderItemsAreAllCancelled,
@@ -31,7 +30,7 @@ export class OrderStateMachine {
     private readonly initialState: OrderState = 'AddingItems';
 
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private stockMovementService: StockMovementService,
         private historyService: HistoryService,

+ 2 - 3
packages/core/src/service/helpers/slug-validator/slug-validator.ts

@@ -1,11 +1,10 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { LanguageCode } from '@vendure/common/lib/generated-types';
 import { normalizeString } from '@vendure/common/lib/normalize-string';
 import { ID, Type } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { VendureEntity } from '../../../entity/base/base.entity';
+import { TransactionalConnection } from '../../transaction/transactional-connection';
 
 export type InputWithSlug = {
     id?: ID | null;
@@ -24,7 +23,7 @@ export type TranslationEntity = VendureEntity & {
 
 @Injectable()
 export class SlugValidator {
-    constructor(@InjectConnection() private connection: Connection) {}
+    constructor(private connection: TransactionalConnection) {}
 
     /**
      * Normalizes the slug to be URL-safe, and ensures it is unique for the given languageCode.

+ 8 - 7
packages/core/src/service/helpers/translatable-saver/translatable-saver.ts

@@ -1,11 +1,10 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { omit } from '@vendure/common/lib/omit';
 import { ID, Type } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { Translatable, TranslatedInput, Translation } from '../../../common/types/locale-types';
 import { VendureEntity } from '../../../entity/base/base.entity';
+import { TransactionalConnection } from '../../transaction/transactional-connection';
 import { patchEntity } from '../utils/patch-entity';
 
 import { TranslationDiffer } from './translation-differ';
@@ -27,7 +26,7 @@ export interface UpdateTranslatableOptions<T extends Translatable> extends Creat
  */
 @Injectable()
 export class TranslatableSaver {
-    constructor(@InjectConnection() private connection: Connection) {}
+    constructor(private connection: TransactionalConnection) {}
 
     /**
      * Create a translatable entity, including creating any translation entities according
@@ -43,7 +42,7 @@ export class TranslatableSaver {
             for (const translationInput of input.translations) {
                 const translation = new translationType(translationInput);
                 translations.push(translation);
-                await this.connection.manager.save(translation);
+                await this.connection.getRepository(translationType).save(translation as any);
             }
         }
 
@@ -51,7 +50,9 @@ export class TranslatableSaver {
         if (typeof beforeSave === 'function') {
             await beforeSave(entity);
         }
-        return await this.connection.manager.save(entity, { data: typeOrmSubscriberData });
+        return await this.connection
+            .getRepository(entityType)
+            .save(entity as any, { data: typeOrmSubscriberData });
     }
 
     /**
@@ -65,7 +66,7 @@ export class TranslatableSaver {
             relations: ['base'],
         });
 
-        const differ = new TranslationDiffer(translationType, this.connection.manager);
+        const differ = new TranslationDiffer(translationType, this.connection);
         const diff = differ.diff(existingTranslations, input.translations);
         const entity = await differ.applyDiff(
             new entityType({ ...input, translations: existingTranslations }),
@@ -75,6 +76,6 @@ export class TranslatableSaver {
         if (typeof beforeSave === 'function') {
             await beforeSave(entity);
         }
-        return this.connection.manager.save(updatedEntity, { data: typeOrmSubscriberData });
+        return this.connection.getRepository(entityType).save(updatedEntity, { data: typeOrmSubscriberData });
     }
 }

+ 10 - 7
packages/core/src/service/helpers/translatable-saver/translation-differ.ts

@@ -1,13 +1,13 @@
 import { DeepPartial } from '@vendure/common/lib/shared-types';
-import { EntityManager } from 'typeorm';
 
 import { EntityNotFoundError } from '../../../common/error/errors';
 import { Translatable, Translation, TranslationInput } from '../../../common/types/locale-types';
 import { foundIn, not } from '../../../common/utils';
+import { TransactionalConnection } from '../../transaction/transactional-connection';
 
-export interface TranslationContructor<T> {
-    new (input?: DeepPartial<TranslationInput<T>> | DeepPartial<Translation<T>>): Translation<T>;
-}
+export type TranslationContructor<T> = new (
+    input?: DeepPartial<TranslationInput<T>> | DeepPartial<Translation<T>>,
+) => Translation<T>;
 
 export interface TranslationDiff<T> {
     toUpdate: Array<Translation<T>>;
@@ -18,7 +18,10 @@ export interface TranslationDiff<T> {
  * This class is to be used when performing an update on a Translatable entity.
  */
 export class TranslationDiffer<Entity extends Translatable> {
-    constructor(private translationCtor: TranslationContructor<Entity>, private manager: EntityManager) {}
+    constructor(
+        private translationCtor: TranslationContructor<Entity>,
+        private connection: TransactionalConnection,
+    ) {}
 
     /**
      * Compares the existing translations with the updated translations and produces a diff of
@@ -46,7 +49,7 @@ export class TranslationDiffer<Entity extends Translatable> {
         if (toUpdate.length) {
             for (const translation of toUpdate) {
                 // any cast below is required due to TS issue: https://github.com/Microsoft/TypeScript/issues/21592
-                const updated = await this.manager
+                const updated = await this.connection
                     .getRepository(this.translationCtor)
                     .save(translation as any);
                 const index = entity.translations.findIndex(t => t.languageCode === updated.languageCode);
@@ -59,7 +62,7 @@ export class TranslationDiffer<Entity extends Translatable> {
                 translation.base = entity;
                 let newTranslation: any;
                 try {
-                    newTranslation = await this.manager
+                    newTranslation = await this.connection
                         .getRepository(this.translationCtor)
                         .save(translation as any);
                 } catch (err) {

+ 9 - 8
packages/core/src/service/helpers/utils/channel-aware-orm-utils.ts

@@ -1,27 +1,28 @@
 import { ID, Type } from '@vendure/common/lib/shared-types';
-import { Connection, FindManyOptions, FindOptionsUtils } from 'typeorm';
+import { FindManyOptions, FindOptionsUtils } from 'typeorm';
 
 import { ChannelAware } from '../../../common/types/common-types';
 import { VendureEntity } from '../../../entity';
+import { TransactionalConnection } from '../../transaction/transactional-connection';
 
 /**
  * Like the TypeOrm `Repository.findByIds()` method, but limits the results to
  * the given Channel.
  */
 export function findByIdsInChannel<T extends ChannelAware | VendureEntity>(
-    connection: Connection,
+    connection: TransactionalConnection,
     entity: Type<T>,
     ids: ID[],
     channelId: ID,
     findOptions?: FindManyOptions<T>,
     eager = true,
 ) {
-    //the syntax described in https://github.com/typeorm/typeorm/issues/1239#issuecomment-366955628
-    //breaks if the array is empty
-    if(ids.length === 0){
+    // the syntax described in https://github.com/typeorm/typeorm/issues/1239#issuecomment-366955628
+    // breaks if the array is empty
+    if (ids.length === 0) {
         return Promise.resolve([]);
     }
-    
+
     const qb = connection.getRepository(entity).createQueryBuilder('product');
     FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, findOptions);
     if (eager) {
@@ -30,7 +31,7 @@ export function findByIdsInChannel<T extends ChannelAware | VendureEntity>(
     }
     return qb
         .leftJoin('product.channels', 'channel')
-        .andWhere("product.id IN (:...ids)", { ids })
+        .andWhere('product.id IN (:...ids)', { ids })
         .andWhere('channel.id = :channelId', { channelId })
         .getMany();
 }
@@ -40,7 +41,7 @@ export function findByIdsInChannel<T extends ChannelAware | VendureEntity>(
  * the given Channel.
  */
 export function findOneInChannel<T extends ChannelAware | VendureEntity>(
-    connection: Connection,
+    connection: TransactionalConnection,
     entity: Type<T>,
     id: ID,
     channelId: ID,

+ 5 - 4
packages/core/src/service/helpers/utils/get-entity-or-throw.ts

@@ -1,9 +1,10 @@
 import { ID, Type } from '@vendure/common/lib/shared-types';
-import { Connection, FindOneOptions } from 'typeorm';
+import { FindOneOptions } from 'typeorm';
 
 import { EntityNotFoundError } from '../../../common/error/errors';
 import { ChannelAware, SoftDeletable } from '../../../common/types/common-types';
 import { VendureEntity } from '../../../entity/base/base.entity';
+import { TransactionalConnection } from '../../transaction/transactional-connection';
 
 import { findOneInChannel } from './channel-aware-orm-utils';
 
@@ -13,13 +14,13 @@ import { findOneInChannel } from './channel-aware-orm-utils';
  * the function will "fail" by resolving to the `never` type.
  */
 export async function getEntityOrThrow<T extends VendureEntity>(
-    connection: Connection,
+    connection: TransactionalConnection,
     entityType: Type<T>,
     id: ID,
     findOptions?: FindOneOptions<T>,
 ): Promise<T extends ChannelAware ? never : T>;
 export async function getEntityOrThrow<T extends VendureEntity | ChannelAware>(
-    connection: Connection,
+    connection: TransactionalConnection,
     entityType: Type<T>,
     id: ID,
     channelId: ID,
@@ -27,7 +28,7 @@ export async function getEntityOrThrow<T extends VendureEntity | ChannelAware>(
     eager?: boolean,
 ): Promise<T>;
 export async function getEntityOrThrow<T extends VendureEntity>(
-    connection: Connection,
+    connection: TransactionalConnection,
     entityType: Type<T>,
     id: ID,
     findOptionsOrChannelId?: FindOneOptions<T> | ID,

+ 9 - 9
packages/core/src/service/services/administrator.service.ts

@@ -1,22 +1,22 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateAdministratorInput,
     DeletionResult,
     UpdateAdministratorInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { EntityNotFoundError } from '../../common/error/errors';
 import { ListQueryOptions } from '../../common/types/common-types';
 import { ConfigService } from '../../config';
 import { Administrator } from '../../entity/administrator/administrator.entity';
+import { NativeAuthenticationMethod } from '../../entity/authentication-method/native-authentication-method.entity';
 import { User } from '../../entity/user/user.entity';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { PasswordCiper } from '../helpers/password-cipher/password-ciper';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { RoleService } from './role.service';
 import { UserService } from './user.service';
@@ -24,7 +24,7 @@ import { UserService } from './user.service';
 @Injectable()
 export class AdministratorService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private listQueryBuilder: ListQueryBuilder,
         private passwordCipher: PasswordCiper,
@@ -47,7 +47,7 @@ export class AdministratorService {
     }
 
     findOne(administratorId: ID): Promise<Administrator | undefined> {
-        return this.connection.manager.findOne(Administrator, administratorId, {
+        return this.connection.getRepository(Administrator).findOne(administratorId, {
             relations: ['user', 'user.roles'],
             where: {
                 deletedAt: null,
@@ -67,7 +67,7 @@ export class AdministratorService {
     async create(input: CreateAdministratorInput): Promise<Administrator> {
         const administrator = new Administrator(input);
         administrator.user = await this.userService.createAdminUser(input.emailAddress, input.password);
-        let createdAdministrator = await this.connection.manager.save(administrator);
+        let createdAdministrator = await this.connection.getRepository(Administrator).save(administrator);
         for (const roleId of input.roleIds) {
             createdAdministrator = await this.assignRole(createdAdministrator.id, roleId);
         }
@@ -80,19 +80,19 @@ export class AdministratorService {
             throw new EntityNotFoundError('Administrator', input.id);
         }
         let updatedAdministrator = patchEntity(administrator, input);
-        await this.connection.manager.save(administrator, { reload: false });
+        await this.connection.getRepository(Administrator).save(administrator, { reload: false });
 
         if (input.password) {
             const user = await this.userService.getUserById(administrator.user.id);
             if (user) {
                 const nativeAuthMethod = user.getNativeAuthenticationMethod();
                 nativeAuthMethod.passwordHash = await this.passwordCipher.hash(input.password);
-                await this.connection.manager.save(nativeAuthMethod);
+                await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
             }
         }
         if (input.roleIds) {
             administrator.user.roles = [];
-            await this.connection.manager.save(administrator.user, { reload: false });
+            await this.connection.getRepository(User).save(administrator.user, { reload: false });
             for (const roleId of input.roleIds) {
                 updatedAdministrator = await this.assignRole(administrator.id, roleId);
             }
@@ -113,7 +113,7 @@ export class AdministratorService {
             throw new EntityNotFoundError('Role', roleId);
         }
         administrator.user.roles.push(role);
-        await this.connection.manager.save(administrator.user, { reload: false });
+        await this.connection.getRepository(User).save(administrator.user, { reload: false });
         return administrator;
     }
 

+ 3 - 4
packages/core/src/service/services/asset.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     AssetType,
     CreateAssetInput,
@@ -13,7 +12,6 @@ import { ReadStream } from 'fs-extra';
 import mime from 'mime-types';
 import path from 'path';
 import { Stream } from 'stream';
-import { Connection, Like } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { InternalServerError, UserInputError } from '../../common/error/errors';
@@ -32,6 +30,7 @@ import { AssetEvent } from '../../event-bus/events/asset-event';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 // tslint:disable-next-line:no-var-requires
 const sizeOf = require('image-size');
 
@@ -50,7 +49,7 @@ export class AssetService {
     private permittedMimeTypes: Array<{ type: string; subtype: string }> = [];
 
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private listQueryBuilder: ListQueryBuilder,
         private eventBus: EventBus,
@@ -267,7 +266,7 @@ export class AssetService {
             preview: previewFileIdentifier,
             focalPoint: null,
         });
-        return this.connection.manager.save(asset);
+        return this.connection.getRepository(Asset).save(asset);
     }
 
     private async getSourceFileName(fileName: string): Promise<string> {

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

@@ -1,7 +1,5 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { ID } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { ApiType } from '../../api/common/get-api-type';
 import { RequestContext } from '../../api/common/request-context';
@@ -19,6 +17,7 @@ import { EventBus } from '../../event-bus/event-bus';
 import { AttemptedLoginEvent } from '../../event-bus/events/attempted-login-event';
 import { LoginEvent } from '../../event-bus/events/login-event';
 import { LogoutEvent } from '../../event-bus/events/logout-event';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { SessionService } from './session.service';
 
@@ -28,7 +27,7 @@ import { SessionService } from './session.service';
 @Injectable()
 export class AuthService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private sessionService: SessionService,
         private eventBus: EventBus,
@@ -83,7 +82,7 @@ export class AuthService {
             await this.sessionService.deleteSessionsByActiveOrderId(ctx.session.activeOrderId);
         }
         user.lastLogin = new Date();
-        await this.connection.manager.save(user, { reload: false });
+        await this.connection.getRepository(User).save(user, { reload: false });
         const session = await this.sessionService.createNewAuthenticatedSession(
             ctx,
             user,
@@ -141,7 +140,7 @@ export class AuthService {
             apiType === 'admin'
                 ? authOptions.adminAuthenticationStrategy
                 : authOptions.shopAuthenticationStrategy;
-        const match = strategies.find((s) => s.name === method);
+        const match = strategies.find(s => s.name === method);
         if (!match) {
             throw new InternalServerError('error.unrecognized-authentication-strategy', { name: method });
         }

+ 9 - 10
packages/core/src/service/services/channel.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateChannelInput,
     CurrencyCode,
@@ -10,7 +9,6 @@ import {
 import { DEFAULT_CHANNEL_CODE } from '@vendure/common/lib/shared-constants';
 import { ID, Type } from '@vendure/common/lib/shared-types';
 import { unique } from '@vendure/common/lib/unique';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import {
@@ -28,6 +26,7 @@ import { ProductVariantPrice } from '../../entity/product-variant/product-varian
 import { Zone } from '../../entity/zone/zone.entity';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { GlobalSettingsService } from './global-settings.service';
 
@@ -36,7 +35,7 @@ export class ChannelService {
     private allChannels: Channel[] = [];
 
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private globalSettingsService: GlobalSettingsService,
     ) {}
@@ -56,7 +55,7 @@ export class ChannelService {
      */
     assignToCurrentChannel<T extends ChannelAware>(entity: T, ctx: RequestContext): T {
         const channelIds = unique([ctx.channelId, this.getDefaultChannel().id]);
-        entity.channels = channelIds.map((id) => ({ id })) as any;
+        entity.channels = channelIds.map(id => ({ id })) as any;
         return entity;
     }
 
@@ -91,7 +90,7 @@ export class ChannelService {
             relations: ['channels'],
         });
         for (const id of channelIds) {
-            entity.channels = entity.channels.filter((c) => !idsAreEqual(c.id, id));
+            entity.channels = entity.channels.filter(c => !idsAreEqual(c.id, id));
         }
         await this.connection.getRepository(entityType).save(entity as any, { reload: false });
         return entity;
@@ -105,7 +104,7 @@ export class ChannelService {
             // there is only the default channel, so return it
             return this.getDefaultChannel();
         }
-        const channel = this.allChannels.find((c) => c.token === token);
+        const channel = this.allChannels.find(c => c.token === token);
         if (!channel) {
             throw new ChannelNotFoundError(token);
         }
@@ -116,7 +115,7 @@ export class ChannelService {
      * Returns the default Channel.
      */
     getDefaultChannel(): Channel {
-        const defaultChannel = this.allChannels.find((channel) => channel.code === DEFAULT_CHANNEL_CODE);
+        const defaultChannel = this.allChannels.find(channel => channel.code === DEFAULT_CHANNEL_CODE);
 
         if (!defaultChannel) {
             throw new InternalServerError(`error.default-channel-not-found`);
@@ -161,7 +160,7 @@ export class ChannelService {
         if (input.defaultLanguageCode) {
             const availableLanguageCodes = await this.globalSettingsService
                 .getSettings()
-                .then((s) => s.availableLanguages);
+                .then(s => s.availableLanguages);
             if (!availableLanguageCodes.includes(input.defaultLanguageCode)) {
                 throw new UserInputError('error.language-not-available-in-global-settings', {
                     code: input.defaultLanguageCode,
@@ -219,10 +218,10 @@ export class ChannelService {
                 currencyCode: CurrencyCode.USD,
                 token: defaultChannelToken,
             });
-            await this.connection.manager.save(newDefaultChannel, { reload: false });
+            await this.connection.getRepository(Channel).save(newDefaultChannel, { reload: false });
         } else if (defaultChannelToken && defaultChannel.token !== defaultChannelToken) {
             defaultChannel.token = defaultChannelToken;
-            await this.connection.manager.save(defaultChannel, { reload: false });
+            await this.connection.getRepository(Channel).save(defaultChannel, { reload: false });
         }
     }
 

+ 9 - 3
packages/core/src/service/services/collection.service.ts

@@ -1,4 +1,4 @@
-import { OnModuleInit } from '@nestjs/common';
+import { OnModuleInit, Optional } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
 import {
     ConfigurableOperation,
@@ -42,6 +42,7 @@ import { findOneInChannel } from '../helpers/utils/channel-aware-orm-utils';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { moveToIndex } from '../helpers/utils/move-to-index';
 import { translateDeep } from '../helpers/utils/translate-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 import { ApplyCollectionFiltersJobData, ApplyCollectionFiltersMessage } from '../types/collection-messages';
 
 import { AssetService } from './asset.service';
@@ -53,7 +54,10 @@ export class CollectionService implements OnModuleInit {
     private applyFiltersQueue: JobQueue<ApplyCollectionFiltersJobData>;
 
     constructor(
-        @InjectConnection() private connection: Connection,
+        // Optional() allows the onModuleInit() hook to run with injected
+        // providers despite the request-scoped TransactionalConnection
+        // not yet having been created
+        @Optional() private connection: TransactionalConnection,
         private channelService: ChannelService,
         private assetService: AssetService,
         private facetValueService: FacetValueService,
@@ -149,7 +153,9 @@ export class CollectionService implements OnModuleInit {
 
     async getParent(ctx: RequestContext, collectionId: ID): Promise<Collection | undefined> {
         const parentIdSelect =
-            this.connection.options.type === 'postgres' ? '"child"."parentId"' : 'child.parentId';
+            this.connection.rawConnection.options.type === 'postgres'
+                ? '"child"."parentId"'
+                : 'child.parentId';
         const parent = await this.connection
             .getRepository(Collection)
             .createQueryBuilder('collection')

+ 2 - 3
packages/core/src/service/services/country.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateCountryInput,
     DeletionResponse,
@@ -7,7 +6,6 @@ import {
     UpdateCountryInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { UserInputError } from '../../common/error/errors';
@@ -21,13 +19,14 @@ import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-build
 import { TranslatableSaver } from '../helpers/translatable-saver/translatable-saver';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { translateDeep } from '../helpers/utils/translate-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { ZoneService } from './zone.service';
 
 @Injectable()
 export class CountryService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private listQueryBuilder: ListQueryBuilder,
         private translatableSaver: TranslatableSaver,
         private zoneService: ZoneService,

+ 5 - 6
packages/core/src/service/services/customer-group.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateCustomerGroupInput,
     CustomerGroupListOptions,
@@ -12,7 +11,6 @@ import {
     UpdateCustomerGroupInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { UserInputError } from '../../common/error/errors';
@@ -22,13 +20,14 @@ import { Customer } from '../../entity/customer/customer.entity';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { HistoryService } from './history.service';
 
 @Injectable()
 export class CustomerGroupService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private listQueryBuilder: ListQueryBuilder,
         private historyService: HistoryService,
     ) {}
@@ -104,7 +103,7 @@ export class CustomerGroupService {
         const customers = await this.getCustomersFromIds(input.customerIds);
         const group = await getEntityOrThrow(this.connection, CustomerGroup, input.customerGroupId);
         for (const customer of customers) {
-            if (!customer.groups.map((g) => g.id).includes(input.customerGroupId)) {
+            if (!customer.groups.map(g => g.id).includes(input.customerGroupId)) {
                 customer.groups.push(group);
                 await this.historyService.createHistoryEntryForCustomer({
                     ctx,
@@ -128,10 +127,10 @@ export class CustomerGroupService {
         const customers = await this.getCustomersFromIds(input.customerIds);
         const group = await getEntityOrThrow(this.connection, CustomerGroup, input.customerGroupId);
         for (const customer of customers) {
-            if (!customer.groups.map((g) => g.id).includes(input.customerGroupId)) {
+            if (!customer.groups.map(g => g.id).includes(input.customerGroupId)) {
                 throw new UserInputError('error.customer-does-not-belong-to-customer-group');
             }
-            customer.groups = customer.groups.filter((g) => !idsAreEqual(g.id, group.id));
+            customer.groups = customer.groups.filter(g => !idsAreEqual(g.id, group.id));
             await this.historyService.createHistoryEntryForCustomer({
                 ctx,
                 customerId: customer.id,

+ 5 - 6
packages/core/src/service/services/customer.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { RegisterCustomerInput } from '@vendure/common/lib/generated-shop-types';
 import {
     AddNoteToCustomerInput,
@@ -13,7 +12,6 @@ import {
     UpdateCustomerNoteInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import {
@@ -42,6 +40,7 @@ import { addressToLine } from '../helpers/utils/address-to-line';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
 import { translateDeep } from '../helpers/utils/translate-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { CountryService } from './country.service';
 import { HistoryService } from './history.service';
@@ -50,7 +49,7 @@ import { UserService } from './user.service';
 @Injectable()
 export class CustomerService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private userService: UserService,
         private countryService: CountryService,
@@ -414,7 +413,7 @@ export class CustomerService {
     }
 
     async createAddress(ctx: RequestContext, customerId: ID, input: CreateAddressInput): Promise<Address> {
-        const customer = await this.connection.manager.findOne(Customer, customerId, {
+        const customer = await this.connection.getRepository(Customer).findOne(customerId, {
             where: { deletedAt: null },
             relations: ['addresses'],
         });
@@ -427,9 +426,9 @@ export class CustomerService {
             ...input,
             country,
         });
-        const createdAddress = await this.connection.manager.getRepository(Address).save(address);
+        const createdAddress = await this.connection.getRepository(Address).save(address);
         customer.addresses.push(createdAddress);
-        await this.connection.manager.save(customer, { reload: false });
+        await this.connection.getRepository(Customer).save(customer, { reload: false });
         await this.enforceSingleDefaultAddress(createdAddress.id, input);
         await this.historyService.createHistoryEntryForCustomer({
             customerId: customer.id,

+ 8 - 7
packages/core/src/service/services/facet-value.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateFacetValueInput,
     CreateFacetValueWithFacetInput,
@@ -9,7 +8,6 @@ import {
     UpdateFacetValueInput,
 } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { Translated } from '../../common/types/locale-types';
@@ -22,26 +20,29 @@ import { Facet } from '../../entity/facet/facet.entity';
 import { TranslatableSaver } from '../helpers/translatable-saver/translatable-saver';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { translateDeep } from '../helpers/utils/translate-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 @Injectable()
 export class FacetValueService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private translatableSaver: TranslatableSaver,
         private configService: ConfigService,
     ) {}
 
     findAll(lang: LanguageCode): Promise<Array<Translated<FacetValue>>> {
-        return this.connection.manager
-            .find(FacetValue, {
+        return this.connection
+            .getRepository(FacetValue)
+            .find({
                 relations: ['facet'],
             })
             .then(facetValues => facetValues.map(facetValue => translateDeep(facetValue, lang, ['facet'])));
     }
 
     findOne(id: ID, lang: LanguageCode): Promise<Translated<FacetValue> | undefined> {
-        return this.connection.manager
-            .findOne(FacetValue, id, {
+        return this.connection
+            .getRepository(FacetValue)
+            .findOne(id, {
                 relations: ['facet'],
             })
             .then(facetValue => facetValue && translateDeep(facetValue, lang, ['facet']));

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

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateFacetInput,
     DeletionResponse,
@@ -8,7 +7,6 @@ import {
     UpdateFacetInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { ListQueryOptions } from '../../common/types/common-types';
@@ -21,13 +19,14 @@ import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-build
 import { TranslatableSaver } from '../helpers/translatable-saver/translatable-saver';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { translateDeep } from '../helpers/utils/translate-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { FacetValueService } from './facet-value.service';
 
 @Injectable()
 export class FacetService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private facetValueService: FacetValueService,
         private translatableSaver: TranslatableSaver,
         private listQueryBuilder: ListQueryBuilder,
@@ -57,8 +56,9 @@ export class FacetService {
     findOne(facetId: ID, lang: LanguageCode): Promise<Translated<Facet> | undefined> {
         const relations = ['values', 'values.facet'];
 
-        return this.connection.manager
-            .findOne(Facet, facetId, { relations })
+        return this.connection
+            .getRepository(Facet)
+            .findOne(facetId, { relations })
             .then(facet => facet && translateDeep(facet, lang, ['values', ['values', 'facet']]));
     }
 

+ 2 - 3
packages/core/src/service/services/global-settings.service.ts

@@ -1,16 +1,15 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { UpdateGlobalSettingsInput } from '@vendure/common/lib/generated-types';
-import { Connection } from 'typeorm';
 
 import { InternalServerError } from '../../common/error/errors';
 import { ConfigService } from '../../config/config.service';
 import { GlobalSettings } from '../../entity/global-settings/global-settings.entity';
 import { patchEntity } from '../helpers/utils/patch-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 @Injectable()
 export class GlobalSettingsService {
-    constructor(@InjectConnection() private connection: Connection, private configService: ConfigService) {}
+    constructor(private connection: TransactionalConnection, private configService: ConfigService) {}
 
     /**
      * Ensure there is a global settings row in the database.

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

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     HistoryEntryListOptions,
     HistoryEntryType,
@@ -7,7 +6,6 @@ import {
     UpdateCustomerInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList, Type } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { Administrator } from '../../entity/administrator/administrator.entity';
@@ -19,6 +17,7 @@ import { OrderState } from '../helpers/order-state-machine/order-state';
 import { PaymentState } from '../helpers/payment-state-machine/payment-state';
 import { RefundState } from '../helpers/refund-state-machine/refund-state';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { AdministratorService } from './administrator.service';
 
@@ -134,7 +133,7 @@ export interface UpdateCustomerHistoryEntryArgs<T extends keyof CustomerHistoryE
 @Injectable()
 export class HistoryService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private administratorService: AdministratorService,
         private listQueryBuilder: ListQueryBuilder,
     ) {}

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

@@ -20,6 +20,7 @@ import { OrderCalculator } from '../helpers/order-calculator/order-calculator';
 import { ShippingCalculator } from '../helpers/shipping-calculator/shipping-calculator';
 import { ShippingConfiguration } from '../helpers/shipping-configuration/shipping-configuration';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 /**
  * This service is responsible for creating temporary mock Orders against which tests can be run, such as
@@ -28,7 +29,7 @@ import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 @Injectable()
 export class OrderTestingService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private orderCalculator: OrderCalculator,
         private shippingCalculator: ShippingCalculator,
         private shippingConfiguration: ShippingConfiguration,

+ 4 - 4
packages/core/src/service/services/order.service.ts

@@ -1,4 +1,3 @@
-import { InjectConnection } from '@nestjs/typeorm';
 import { PaymentInput } from '@vendure/common/lib/generated-shop-types';
 import {
     AddNoteToOrderInput,
@@ -18,7 +17,6 @@ import {
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
 import { unique } from '@vendure/common/lib/unique';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import {
@@ -63,6 +61,7 @@ import {
 } from '../helpers/utils/order-utils';
 import { patchEntity } from '../helpers/utils/patch-entity';
 import { translateDeep } from '../helpers/utils/translate-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { ChannelService } from './channel.service';
 import { CountryService } from './country.service';
@@ -75,7 +74,7 @@ import { StockMovementService } from './stock-movement.service';
 
 export class OrderService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private productVariantService: ProductVariantService,
         private customerService: CustomerService,
@@ -220,7 +219,8 @@ export class OrderService {
         const customer = await this.customerService.findOneByUserId(userId);
         if (customer) {
             const activeOrder = await this.connection
-                .createQueryBuilder(Order, 'order')
+                .getRepository(Order)
+                .createQueryBuilder('order')
                 .innerJoinAndSelect('order.channels', 'channel', 'channel.id = :channelId', {
                     channelId: ctx.channelId,
                 })

+ 3 - 2
packages/core/src/service/services/payment-method.service.ts

@@ -29,11 +29,12 @@ import { PaymentStateMachine } from '../helpers/payment-state-machine/payment-st
 import { RefundStateMachine } from '../helpers/refund-state-machine/refund-state-machine';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 @Injectable()
 export class PaymentMethodService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private listQueryBuilder: ListQueryBuilder,
         private paymentStateMachine: PaymentStateMachine,
@@ -56,7 +57,7 @@ export class PaymentMethodService {
     }
 
     findOne(paymentMethodId: ID): Promise<PaymentMethod | undefined> {
-        return this.connection.manager.findOne(PaymentMethod, paymentMethodId);
+        return this.connection.getRepository(PaymentMethod).findOne(paymentMethodId);
     }
 
     async update(input: UpdatePaymentMethodInput): Promise<PaymentMethod> {

+ 24 - 15
packages/core/src/service/services/product-option-group.service.ts

@@ -1,8 +1,10 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
-import { CreateProductOptionGroupInput, UpdateProductOptionGroupInput } from '@vendure/common/lib/generated-types';
+import {
+    CreateProductOptionGroupInput,
+    UpdateProductOptionGroupInput,
+} from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
-import { Connection, FindManyOptions, Like } from 'typeorm';
+import { FindManyOptions, Like } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { Translated } from '../../common/types/locale-types';
@@ -11,13 +13,11 @@ import { ProductOptionGroupTranslation } from '../../entity/product-option-group
 import { ProductOptionGroup } from '../../entity/product-option-group/product-option-group.entity';
 import { TranslatableSaver } from '../helpers/translatable-saver/translatable-saver';
 import { translateDeep } from '../helpers/utils/translate-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 @Injectable()
 export class ProductOptionGroupService {
-    constructor(
-        @InjectConnection() private connection: Connection,
-        private translatableSaver: TranslatableSaver,
-    ) {}
+    constructor(private connection: TransactionalConnection, private translatableSaver: TranslatableSaver) {}
 
     findAll(ctx: RequestContext, filterTerm?: string): Promise<Array<Translated<ProductOptionGroup>>> {
         const findOptions: FindManyOptions = {
@@ -28,22 +28,25 @@ export class ProductOptionGroupService {
                 code: Like(`%${filterTerm}%`),
             };
         }
-        return this.connection.manager
-            .find(ProductOptionGroup, findOptions)
+        return this.connection
+            .getRepository(ProductOptionGroup)
+            .find(findOptions)
             .then(groups => groups.map(group => translateDeep(group, ctx.languageCode, ['options'])));
     }
 
     findOne(ctx: RequestContext, id: ID): Promise<Translated<ProductOptionGroup> | undefined> {
-        return this.connection.manager
-            .findOne(ProductOptionGroup, id, {
+        return this.connection
+            .getRepository(ProductOptionGroup)
+            .findOne(id, {
                 relations: ['options'],
             })
             .then(group => group && translateDeep(group, ctx.languageCode, ['options']));
     }
 
     getOptionGroupsByProductId(ctx: RequestContext, id: ID): Promise<Array<Translated<ProductOptionGroup>>> {
-        return this.connection.manager
-            .find(ProductOptionGroup, {
+        return this.connection
+            .getRepository(ProductOptionGroup)
+            .find({
                 relations: ['options'],
                 where: {
                     product: { id },
@@ -55,7 +58,10 @@ export class ProductOptionGroupService {
             .then(groups => groups.map(group => translateDeep(group, ctx.languageCode, ['options'])));
     }
 
-    async create(ctx: RequestContext, input: CreateProductOptionGroupInput): Promise<Translated<ProductOptionGroup>> {
+    async create(
+        ctx: RequestContext,
+        input: CreateProductOptionGroupInput,
+    ): Promise<Translated<ProductOptionGroup>> {
         const group = await this.translatableSaver.create({
             input,
             entityType: ProductOptionGroup,
@@ -64,7 +70,10 @@ export class ProductOptionGroupService {
         return assertFound(this.findOne(ctx, group.id));
     }
 
-    async update(ctx: RequestContext, input: UpdateProductOptionGroupInput): Promise<Translated<ProductOptionGroup>> {
+    async update(
+        ctx: RequestContext,
+        input: UpdateProductOptionGroupInput,
+    ): Promise<Translated<ProductOptionGroup>> {
         const group = await this.translatableSaver.update({
             input,
             entityType: ProductOptionGroup,

+ 11 - 13
packages/core/src/service/services/product-option.service.ts

@@ -1,12 +1,10 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateGroupOptionInput,
     CreateProductOptionInput,
     UpdateProductOptionInput,
 } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { Translated } from '../../common/types/locale-types';
@@ -17,28 +15,28 @@ import { ProductOption } from '../../entity/product-option/product-option.entity
 import { TranslatableSaver } from '../helpers/translatable-saver/translatable-saver';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { translateDeep } from '../helpers/utils/translate-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 @Injectable()
 export class ProductOptionService {
-    constructor(
-        @InjectConnection() private connection: Connection,
-        private translatableSaver: TranslatableSaver,
-    ) {}
+    constructor(private connection: TransactionalConnection, private translatableSaver: TranslatableSaver) {}
 
     findAll(ctx: RequestContext): Promise<Array<Translated<ProductOption>>> {
-        return this.connection.manager
-            .find(ProductOption, {
+        return this.connection
+            .getRepository(ProductOption)
+            .find({
                 relations: ['group'],
             })
-            .then((options) => options.map((option) => translateDeep(option, ctx.languageCode)));
+            .then(options => options.map(option => translateDeep(option, ctx.languageCode)));
     }
 
     findOne(ctx: RequestContext, id: ID): Promise<Translated<ProductOption> | undefined> {
-        return this.connection.manager
-            .findOne(ProductOption, id, {
+        return this.connection
+            .getRepository(ProductOption)
+            .findOne(id, {
                 relations: ['group'],
             })
-            .then((option) => option && translateDeep(option, ctx.languageCode));
+            .then(option => option && translateDeep(option, ctx.languageCode));
     }
 
     async create(
@@ -54,7 +52,7 @@ export class ProductOptionService {
             input,
             entityType: ProductOption,
             translationType: ProductOptionTranslation,
-            beforeSave: (po) => (po.group = productOptionGroup),
+            beforeSave: po => (po.group = productOptionGroup),
         });
         return assertFound(this.findOne(ctx, option.id));
     }

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

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateProductVariantInput,
     DeletionResponse,
@@ -7,7 +6,6 @@ import {
     UpdateProductVariantInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { InternalServerError, UserInputError } from '../../common/error/errors';
@@ -29,6 +27,7 @@ import { TranslatableSaver } from '../helpers/translatable-saver/translatable-sa
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { samplesEach } from '../helpers/utils/samples-each';
 import { translateDeep } from '../helpers/utils/translate-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { AssetService } from './asset.service';
 import { FacetValueService } from './facet-value.service';
@@ -41,7 +40,7 @@ import { ZoneService } from './zone.service';
 @Injectable()
 export class ProductVariantService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private taxCategoryService: TaxCategoryService,
         private facetValueService: FacetValueService,
@@ -71,7 +70,7 @@ export class ProductVariantService {
     }
 
     findByIds(ctx: RequestContext, ids: ID[]): Promise<Array<Translated<ProductVariant>>> {
-        return this.connection.manager
+        return this.connection
             .getRepository(ProductVariant)
             .findByIds(ids, {
                 relations: [

+ 4 - 5
packages/core/src/service/services/product.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     AssignProductsToChannelInput,
     CreateProductInput,
@@ -10,7 +9,6 @@ import {
     UpdateProductInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { EntityNotFoundError, ForbiddenError, UserInputError } from '../../common/error/errors';
@@ -31,6 +29,7 @@ import { TranslatableSaver } from '../helpers/translatable-saver/translatable-sa
 import { findByIdsInChannel, findOneInChannel } from '../helpers/utils/channel-aware-orm-utils';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { translateDeep } from '../helpers/utils/translate-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { AssetService } from './asset.service';
 import { ChannelService } from './channel.service';
@@ -45,7 +44,7 @@ export class ProductService {
     private readonly relations = ['featuredAsset', 'assets', 'channels', 'facetValues', 'facetValues.facet'];
 
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private channelService: ChannelService,
         private roleService: RoleService,
         private assetService: AssetService,
@@ -260,7 +259,7 @@ export class ProductService {
             product.optionGroups = [optionGroup];
         }
 
-        await this.connection.manager.save(product, { reload: false });
+        await this.connection.getRepository(Product).save(product, { reload: false });
         return assertFound(this.findOne(ctx, productId));
     }
 
@@ -282,7 +281,7 @@ export class ProductService {
         }
         product.optionGroups = product.optionGroups.filter(g => g.id !== optionGroupId);
 
-        await this.connection.manager.save(product, { reload: false });
+        await this.connection.getRepository(Product).save(product, { reload: false });
         return assertFound(this.findOne(ctx, productId));
     }
 

+ 4 - 5
packages/core/src/service/services/promotion.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     Adjustment,
     AdjustmentType,
@@ -14,7 +13,6 @@ import {
 import { omit } from '@vendure/common/lib/omit';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
 import { unique } from '@vendure/common/lib/unique';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import {
@@ -35,6 +33,7 @@ import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-build
 import { findOneInChannel } from '../helpers/utils/channel-aware-orm-utils';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { ChannelService } from './channel.service';
 
@@ -50,7 +49,7 @@ export class PromotionService {
     private activePromotions: Promotion[] = [];
 
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private channelService: ChannelService,
         private listQueryBuilder: ListQueryBuilder,
@@ -111,7 +110,7 @@ export class PromotionService {
         });
         this.validatePromotionConditions(promotion);
         this.channelService.assignToCurrentChannel(promotion, ctx);
-        const newPromotion = await this.connection.manager.save(promotion);
+        const newPromotion = await this.connection.getRepository(Promotion).save(promotion);
         await this.updatePromotions();
         return assertFound(this.findOne(ctx, newPromotion.id));
     }
@@ -127,7 +126,7 @@ export class PromotionService {
         }
         this.validatePromotionConditions(updatedPromotion);
         promotion.priorityScore = this.calculatePriorityScore(input);
-        await this.connection.manager.save(updatedPromotion, { reload: false });
+        await this.connection.getRepository(Promotion).save(updatedPromotion, { reload: false });
         await this.updatePromotions();
         return assertFound(this.findOne(ctx, updatedPromotion.id));
     }

+ 5 - 6
packages/core/src/service/services/role.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateRoleInput,
     DeletionResponse,
@@ -15,7 +14,6 @@ import {
 } from '@vendure/common/lib/shared-constants';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
 import { unique } from '@vendure/common/lib/unique';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import {
@@ -33,13 +31,14 @@ import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-build
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { getUserChannelsPermissions } from '../helpers/utils/get-user-channels-permissions';
 import { patchEntity } from '../helpers/utils/patch-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { ChannelService } from './channel.service';
 
 @Injectable()
 export class RoleService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private channelService: ChannelService,
         private listQueryBuilder: ListQueryBuilder,
     ) {}
@@ -60,7 +59,7 @@ export class RoleService {
     }
 
     findOne(roleId: ID): Promise<Role | undefined> {
-        return this.connection.manager.findOne(Role, roleId, {
+        return this.connection.getRepository(Role).findOne(roleId, {
             relations: ['channels'],
         });
     }
@@ -150,7 +149,7 @@ export class RoleService {
         if (input.channelIds && ctx.activeUserId) {
             updatedRole.channels = await this.getPermittedChannels(input.channelIds, ctx.activeUserId);
         }
-        await this.connection.manager.save(updatedRole, { reload: false });
+        await this.connection.getRepository(Role).save(updatedRole, { reload: false });
         return assertFound(this.findOne(role.id));
     }
 
@@ -255,6 +254,6 @@ export class RoleService {
             permissions: unique([Permission.Authenticated, ...input.permissions]),
         });
         role.channels = channels;
-        return this.connection.manager.save(role);
+        return this.connection.getRepository(Role).save(role);
     }
 }

+ 3 - 2
packages/core/src/service/services/session.service.ts

@@ -17,6 +17,7 @@ import { AuthenticatedSession } from '../../entity/session/authenticated-session
 import { Session } from '../../entity/session/session.entity';
 import { User } from '../../entity/user/user.entity';
 import { getUserChannelsPermissions } from '../helpers/utils/get-user-channels-permissions';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { OrderService } from './order.service';
 
@@ -26,7 +27,7 @@ export class SessionService implements EntitySubscriberInterface {
     private readonly sessionDurationInMs: number;
 
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private orderService: OrderService,
     ) {
@@ -34,7 +35,7 @@ export class SessionService implements EntitySubscriberInterface {
         this.sessionDurationInMs = ms(this.configService.authOptions.sessionDuration as string);
         // This allows us to register this class as a TypeORM Subscriber while also allowing
         // the injection on dependencies. See https://docs.nestjs.com/techniques/database#subscribers
-        this.connection.subscribers.push(this);
+        this.connection.rawConnection.subscribers.push(this);
     }
 
     afterInsert(event: InsertEvent<any>): Promise<any> | void {

+ 4 - 5
packages/core/src/service/services/shipping-method.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     ConfigurableOperationDefinition,
     CreateShippingMethodInput,
@@ -9,7 +8,6 @@ import {
 } from '@vendure/common/lib/generated-types';
 import { omit } from '@vendure/common/lib/omit';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { EntityNotFoundError } from '../../common/error/errors';
@@ -23,6 +21,7 @@ import { ShippingConfiguration } from '../helpers/shipping-configuration/shippin
 import { findOneInChannel } from '../helpers/utils/channel-aware-orm-utils';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { ChannelService } from './channel.service';
 
@@ -31,7 +30,7 @@ export class ShippingMethodService {
     private activeShippingMethods: ShippingMethod[];
 
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private listQueryBuilder: ListQueryBuilder,
         private channelService: ChannelService,
@@ -74,7 +73,7 @@ export class ShippingMethodService {
             calculator: this.shippingConfiguration.parseCalculatorInput(input.calculator),
         });
         this.channelService.assignToCurrentChannel(shippingMethod, ctx);
-        const newShippingMethod = await this.connection.manager.save(shippingMethod);
+        const newShippingMethod = await this.connection.getRepository(ShippingMethod).save(shippingMethod);
         await this.updateActiveShippingMethods();
         return assertFound(this.findOne(ctx, newShippingMethod.id));
     }
@@ -93,7 +92,7 @@ export class ShippingMethodService {
                 input.calculator,
             );
         }
-        await this.connection.manager.save(updatedShippingMethod, { reload: false });
+        await this.connection.getRepository(ShippingMethod).save(updatedShippingMethod, { reload: false });
         await this.updateActiveShippingMethods();
         return assertFound(this.findOne(ctx, shippingMethod.id));
     }

+ 8 - 7
packages/core/src/service/services/stock-movement.service.ts

@@ -17,6 +17,7 @@ import { Sale } from '../../entity/stock-movement/sale.entity';
 import { StockAdjustment } from '../../entity/stock-movement/stock-adjustment.entity';
 import { StockMovement } from '../../entity/stock-movement/stock-movement.entity';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 @Injectable()
 export class StockMovementService {
@@ -24,10 +25,7 @@ export class StockMovementService {
     shippingCalculators: ShippingCalculator[];
     private activeShippingMethods: ShippingMethod[];
 
-    constructor(
-        @InjectConnection() private connection: Connection,
-        private listQueryBuilder: ListQueryBuilder,
-    ) {}
+    constructor(private connection: TransactionalConnection, private listQueryBuilder: ListQueryBuilder) {}
 
     getStockMovementsByProductVariantId(
         ctx: RequestContext,
@@ -87,9 +85,12 @@ export class StockMovementService {
     }
 
     async createCancellationsForOrderItems(items: OrderItem[]): Promise<Cancellation[]> {
-        const orderItems = await this.connection.getRepository(OrderItem).findByIds(items.map(i => i.id), {
-            relations: ['line', 'line.productVariant'],
-        });
+        const orderItems = await this.connection.getRepository(OrderItem).findByIds(
+            items.map(i => i.id),
+            {
+                relations: ['line', 'line.productVariant'],
+            },
+        );
         const cancellations: Cancellation[] = [];
         const variantsMap = new Map<ID, ProductVariant>();
         for (const item of orderItems) {

+ 2 - 3
packages/core/src/service/services/tax-category.service.ts

@@ -1,5 +1,4 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateTaxCategoryInput,
     DeletionResponse,
@@ -7,7 +6,6 @@ import {
     UpdateTaxCategoryInput,
 } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { EntityNotFoundError } from '../../common/error/errors';
@@ -16,10 +14,11 @@ import { TaxCategory } from '../../entity/tax-category/tax-category.entity';
 import { TaxRate } from '../../entity/tax-rate/tax-rate.entity';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 @Injectable()
 export class TaxCategoryService {
-    constructor(@InjectConnection() private connection: Connection) {}
+    constructor(private connection: TransactionalConnection) {}
 
     findAll(): Promise<TaxCategory[]> {
         return this.connection.getRepository(TaxCategory).find();

+ 3 - 4
packages/core/src/service/services/tax-rate.service.ts

@@ -1,4 +1,3 @@
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateTaxRateInput,
     DeletionResponse,
@@ -6,7 +5,6 @@ import {
     UpdateTaxRateInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { EntityNotFoundError } from '../../common/error/errors';
@@ -22,6 +20,7 @@ import { WorkerService } from '../../worker/worker.service';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 import { TaxRateUpdatedMessage } from '../types/tax-rate-messages';
 
 export class TaxRateService {
@@ -38,7 +37,7 @@ export class TaxRateService {
     });
 
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private eventBus: EventBus,
         private listQueryBuilder: ListQueryBuilder,
         private workerService: WorkerService,
@@ -59,7 +58,7 @@ export class TaxRateService {
     }
 
     findOne(taxRateId: ID): Promise<TaxRate | undefined> {
-        return this.connection.manager.findOne(TaxRate, taxRateId, {
+        return this.connection.getRepository(TaxRate).findOne(taxRateId, {
             relations: ['category', 'zone', 'customerGroup'],
         });
     }

+ 20 - 17
packages/core/src/service/services/user.service.ts

@@ -1,7 +1,5 @@
 import { Injectable } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import { ID } from '@vendure/common/lib/shared-types';
-import { Connection } from 'typeorm';
 
 import {
     IdentifierChangeTokenError,
@@ -18,13 +16,14 @@ import { User } from '../../entity/user/user.entity';
 import { PasswordCiper } from '../helpers/password-cipher/password-ciper';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { VerificationTokenGenerator } from '../helpers/verification-token-generator/verification-token-generator';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 import { RoleService } from './role.service';
 
 @Injectable()
 export class UserService {
     constructor(
-        @InjectConnection() private connection: Connection,
+        private connection: TransactionalConnection,
         private configService: ConfigService,
         private roleService: RoleService,
         private passwordCipher: PasswordCiper,
@@ -52,9 +51,9 @@ export class UserService {
         user.identifier = identifier;
         const customerRole = await this.roleService.getCustomerRole();
         user.roles = [customerRole];
-        return this.connection.manager.save(
-            await this.addNativeAuthenticationMethod(user, identifier, password),
-        );
+        return this.connection
+            .getRepository(User)
+            .save(await this.addNativeAuthenticationMethod(user, identifier, password));
     }
 
     async addNativeAuthenticationMethod(user: User, identifier: string, password?: string): Promise<User> {
@@ -71,7 +70,7 @@ export class UserService {
             authenticationMethod.passwordHash = '';
         }
         authenticationMethod.identifier = identifier;
-        await this.connection.manager.save(authenticationMethod);
+        await this.connection.getRepository(NativeAuthenticationMethod).save(authenticationMethod);
         user.authenticationMethods = [...(user.authenticationMethods ?? []), authenticationMethod];
         return user;
     }
@@ -81,14 +80,14 @@ export class UserService {
             identifier,
             verified: true,
         });
-        const authenticationMethod = await this.connection.manager.save(
+        const authenticationMethod = await this.connection.getRepository(NativeAuthenticationMethod).save(
             new NativeAuthenticationMethod({
                 identifier,
                 passwordHash: await this.passwordCipher.hash(password),
             }),
         );
         user.authenticationMethods = [authenticationMethod];
-        return this.connection.manager.save(user);
+        return this.connection.getRepository(User).save(user);
     }
 
     async softDelete(userId: ID) {
@@ -100,8 +99,8 @@ export class UserService {
         const nativeAuthMethod = user.getNativeAuthenticationMethod();
         nativeAuthMethod.verificationToken = this.verificationTokenGenerator.generateVerificationToken();
         user.verified = false;
-        await this.connection.manager.save(nativeAuthMethod);
-        return this.connection.manager.save(user);
+        await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
+        return this.connection.getRepository(User).save(user);
     }
 
     async verifyUserByToken(verificationToken: string, password?: string): Promise<User | undefined> {
@@ -127,7 +126,7 @@ export class UserService {
                 }
                 nativeAuthMethod.verificationToken = null;
                 user.verified = true;
-                await this.connection.manager.save(nativeAuthMethod);
+                await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
                 return this.connection.getRepository(User).save(user);
             } else {
                 throw new VerificationTokenExpiredError();
@@ -142,7 +141,7 @@ export class UserService {
         }
         const nativeAuthMethod = user.getNativeAuthenticationMethod();
         nativeAuthMethod.passwordResetToken = await this.verificationTokenGenerator.generateVerificationToken();
-        await this.connection.manager.save(nativeAuthMethod);
+        await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
         return user;
     }
 
@@ -158,7 +157,7 @@ export class UserService {
                 const nativeAuthMethod = user.getNativeAuthenticationMethod();
                 nativeAuthMethod.passwordHash = await this.passwordCipher.hash(password);
                 nativeAuthMethod.passwordResetToken = null;
-                await this.connection.manager.save(nativeAuthMethod);
+                await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
                 return this.connection.getRepository(User).save(user);
             } else {
                 throw new PasswordResetTokenExpiredError();
@@ -191,7 +190,9 @@ export class UserService {
         nativeAuthMethod.identifier = pendingIdentifier;
         nativeAuthMethod.identifierChangeToken = null;
         nativeAuthMethod.pendingIdentifier = null;
-        await this.connection.manager.save(nativeAuthMethod, { reload: false });
+        await this.connection
+            .getRepository(NativeAuthenticationMethod)
+            .save(nativeAuthMethod, { reload: false });
         await this.connection.getRepository(User).save(user, { reload: false });
         return { user, oldIdentifier };
     }
@@ -213,14 +214,16 @@ export class UserService {
             throw new UnauthorizedError();
         }
         nativeAuthMethod.passwordHash = await this.passwordCipher.hash(newPassword);
-        await this.connection.manager.save(nativeAuthMethod, { reload: false });
+        await this.connection
+            .getRepository(NativeAuthenticationMethod)
+            .save(nativeAuthMethod, { reload: false });
         return true;
     }
 
     async setIdentifierChangeToken(user: User): Promise<User> {
         const nativeAuthMethod = user.getNativeAuthenticationMethod();
         nativeAuthMethod.identifierChangeToken = this.verificationTokenGenerator.generateVerificationToken();
-        await this.connection.manager.save(nativeAuthMethod);
+        await this.connection.getRepository(NativeAuthenticationMethod).save(nativeAuthMethod);
         return user;
     }
 }

+ 2 - 3
packages/core/src/service/services/zone.service.ts

@@ -1,5 +1,4 @@
 import { Injectable, OnModuleInit } from '@nestjs/common';
-import { InjectConnection } from '@nestjs/typeorm';
 import {
     CreateZoneInput,
     DeletionResponse,
@@ -10,7 +9,6 @@ import {
 } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
 import { unique } from '@vendure/common/lib/unique';
-import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { assertFound } from '../../common/utils';
@@ -20,6 +18,7 @@ import { Zone } from '../../entity/zone/zone.entity';
 import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
 import { patchEntity } from '../helpers/utils/patch-entity';
 import { translateDeep } from '../helpers/utils/translate-entity';
+import { TransactionalConnection } from '../transaction/transactional-connection';
 
 @Injectable()
 export class ZoneService implements OnModuleInit {
@@ -27,7 +26,7 @@ export class ZoneService implements OnModuleInit {
      * We cache all Zones to avoid hitting the DB many times per request.
      */
     private zones: Zone[] = [];
-    constructor(@InjectConnection() private connection: Connection) {}
+    constructor(private connection: TransactionalConnection) {}
 
     onModuleInit() {
         return this.updateZonesCache();

+ 12 - 2
packages/core/src/service/transaction/transactional-connection.ts

@@ -1,5 +1,5 @@
 import { Injectable, Scope } from '@nestjs/common';
-import { EntitySchema, getRepository, ObjectType, Repository } from 'typeorm';
+import { Connection, ConnectionOptions, EntitySchema, getRepository, ObjectType, Repository } from 'typeorm';
 import { RepositoryFactory } from 'typeorm/repository/RepositoryFactory';
 
 import { UnitOfWork } from './unit-of-work';
@@ -19,10 +19,20 @@ import { UnitOfWork } from './unit-of-work';
  *
  * @docsCategory data-access
  */
-@Injectable({ scope: Scope.REQUEST })
+@Injectable()
 export class TransactionalConnection {
     constructor(private uow: UnitOfWork) {}
 
+    /**
+     * @description
+     * The plain TypeORM Connection object. Should be used carefully as any operations
+     * performed with this connection will not be performed within any outer
+     * transactions.
+     */
+    get rawConnection(): Connection {
+        return this.uow.getConnection();
+    }
+
     /**
      * @description
      * Gets a repository bound to the current transaction manager

+ 1 - 1
packages/core/src/service/transaction/unit-of-work.ts

@@ -10,7 +10,7 @@ import { Connection, EntityManager } from 'typeorm';
  *
  * @docsCategory data-access
  */
-@Injectable({ scope: Scope.REQUEST })
+@Injectable()
 export class UnitOfWork {
     private transactionManager: EntityManager | null;
     constructor(@InjectConnection() private connection: Connection) {}