Sfoglia il codice sorgente

chore: Get all core e2e tests passing after TypeORM update

Michael Bromley 2 anni fa
parent
commit
d06ac0b8c9
33 ha cambiato i file con 160 aggiunte e 185 eliminazioni
  1. 4 1
      packages/core/e2e/default-search-plugin.e2e-spec.ts
  2. 1 1
      packages/core/e2e/fixtures/test-payment-methods.ts
  3. 3 2
      packages/core/e2e/fixtures/test-plugins/hydration-test-plugin.ts
  4. 1 1
      packages/core/e2e/fixtures/test-plugins/issue-1636-1664/issue-1636-1664-plugin.ts
  5. 1 1
      packages/core/e2e/fixtures/test-plugins/slow-mutation-plugin.ts
  6. 5 5
      packages/core/e2e/fixtures/test-plugins/transaction-test-plugin.ts
  7. 18 41
      packages/core/src/connection/transactional-connection.ts
  8. 0 3
      packages/core/src/entity/order-line/order-line.entity.ts
  9. 1 1
      packages/core/src/plugin/default-job-queue-plugin/sql-job-queue-strategy.ts
  10. 11 12
      packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts
  11. 9 6
      packages/core/src/plugin/default-search-plugin/search-strategy/search-strategy-utils.ts
  12. 17 27
      packages/core/src/service/helpers/list-query-builder/list-query-builder.ts
  13. 3 9
      packages/core/src/service/services/administrator.service.ts
  14. 5 9
      packages/core/src/service/services/collection.service.ts
  15. 2 2
      packages/core/src/service/services/customer.service.ts
  16. 3 1
      packages/core/src/service/services/facet.service.ts
  17. 2 1
      packages/core/src/service/services/fulfillment.service.ts
  18. 5 5
      packages/core/src/service/services/global-settings.service.ts
  19. 9 20
      packages/core/src/service/services/order.service.ts
  20. 2 1
      packages/core/src/service/services/payment.service.ts
  21. 7 5
      packages/core/src/service/services/product-variant.service.ts
  22. 11 8
      packages/core/src/service/services/product.service.ts
  23. 4 2
      packages/core/src/service/services/promotion.service.ts
  24. 1 1
      packages/core/src/service/services/shipping-method.service.ts
  25. 1 1
      packages/core/src/service/services/stock-movement.service.ts
  26. 1 0
      packages/core/src/service/services/user.service.ts
  27. 2 1
      packages/core/src/service/services/zone.service.ts
  28. 2 1
      packages/dev-server/test-plugins/entity-hydrate-plugin.ts
  29. 1 1
      packages/dev-server/test-plugins/issue-1664/issue-1664.ts
  30. 6 1
      packages/dev-server/test-plugins/multivendor-plugin/multivendor.plugin.ts
  31. 12 9
      packages/elasticsearch-plugin/src/indexing/indexer.controller.ts
  32. 2 1
      packages/payments-plugin/src/stripe/stripe.service.ts
  33. 8 5
      packages/testing/src/utils/get-default-channel-token.ts

+ 4 - 1
packages/core/e2e/default-search-plugin.e2e-spec.ts

@@ -844,11 +844,14 @@ describe('Default search plugin', () => {
                 await awaitRunningJobs(adminClient);
                 const { search } = await doAdminSearchQuery({ term: 'drive', groupByProduct: false });
 
+                const variantToDelete = search.items[0];
+                expect(variantToDelete.sku).toBe('IHD455T1_updated');
+
                 await adminClient.query<
                     Codegen.DeleteProductVariantMutation,
                     Codegen.DeleteProductVariantMutationVariables
                 >(DELETE_PRODUCT_VARIANT, {
-                    id: search.items[0].productVariantId,
+                    id: variantToDelete.productVariantId,
                 });
 
                 await awaitRunningJobs(adminClient);

+ 1 - 1
packages/core/e2e/fixtures/test-payment-methods.ts

@@ -132,7 +132,7 @@ export const singleStageRefundFailingPaymentMethod = new PaymentMethodHandler({
     createRefund: async (ctx, input, amount, order, payment, args) => {
         const paymentWithRefunds = await connection
             .getRepository(ctx, Payment)
-            .findOne(payment.id, { relations: ['refunds'] });
+            .findOne({ where: { id: payment.id }, relations: ['refunds'] });
         const isFirstRefundAttempt = paymentWithRefunds?.refunds.length === 0;
         const metadata = isFirstRefundAttempt ? { errorMessage: 'Service temporarily unavailable' } : {};
         return {

+ 3 - 2
packages/core/e2e/fixtures/test-plugins/hydration-test-plugin.ts

@@ -31,7 +31,8 @@ export class TestAdminPluginResolver {
 
     @Query()
     async hydrateProduct(@Ctx() ctx: RequestContext, @Args() args: { id: ID }) {
-        const product = await this.connection.getRepository(ctx, Product).findOne(args.id, {
+        const product = await this.connection.getRepository(ctx, Product).findOne({
+            where: { id: args.id },
             relations: ['facetValues'],
         });
         // tslint:disable-next-line:no-non-null-assertion
@@ -52,7 +53,7 @@ export class TestAdminPluginResolver {
     // Test case for https://github.com/vendure-ecommerce/vendure/issues/1153
     @Query()
     async hydrateProductAsset(@Ctx() ctx: RequestContext, @Args() args: { id: ID }) {
-        const product = await this.connection.getRepository(ctx, Product).findOne(args.id);
+        const product = await this.connection.getRepository(ctx, Product).findOne({ where: { id: args.id } });
         // tslint:disable-next-line:no-non-null-assertion
         await this.entityHydrator.hydrate(ctx, product!, {
             relations: ['assets'],

+ 1 - 1
packages/core/e2e/fixtures/test-plugins/issue-1636-1664/issue-1636-1664-plugin.ts

@@ -177,7 +177,7 @@ export class TestPlugin1636_1664 implements OnApplicationBootstrap {
         (channel.customFields as any).profile = profile;
         await this.connection.rawConnection.getRepository(Channel).save(channel);
 
-        const asset = await this.connection.rawConnection.getRepository(Asset).findOne(1);
+        const asset = await this.connection.rawConnection.getRepository(Asset).findOne({ where: { id: 1 } });
         if (asset) {
             const profileAsset = this.connection.rawConnection.getRepository(ProfileAsset).save({
                 asset,

+ 1 - 1
packages/core/e2e/fixtures/test-plugins/slow-mutation-plugin.ts

@@ -45,7 +45,7 @@ export class SlowMutationResolver {
     @Transaction()
     @Mutation()
     async attemptDeadlock(@Ctx() ctx: RequestContext) {
-        const product = await this.connection.getRepository(ctx, Product).findOneOrFail(1);
+        const product = await this.connection.getRepository(ctx, Product).findOneOrFail({ where: { id: 1 } });
         const asset = await this.connection.getRepository(ctx, Asset).save(
             new Asset({
                 name: 'test',

+ 5 - 5
packages/core/e2e/fixtures/test-plugins/transaction-test-plugin.ts

@@ -144,7 +144,7 @@ class TestResolver {
     async createNTestAdministrators(@Ctx() ctx: RequestContext, @Args() args: any) {
         let error: any;
 
-        const promises: Promise<any>[] = [];
+        const promises: Array<Promise<any>> = [];
         for (let i = 0; i < args.n; i++) {
             promises.push(
                 new Promise(resolve => setTimeout(resolve, i * 10))
@@ -179,7 +179,7 @@ class TestResolver {
     async createNTestAdministrators2(@Ctx() ctx: RequestContext, @Args() args: any) {
         let error: any;
 
-        const promises: Promise<any>[] = [];
+        const promises: Array<Promise<any>> = [];
         const result = await this.connection
             .withTransaction(ctx, _ctx => {
                 for (let i = 0; i < args.n; i++) {
@@ -254,8 +254,8 @@ class TestResolver {
     // Promise.allSettled polyfill
     // Same as Promise.all but waits until all promises will be fulfilled or rejected.
     private allSettled<T>(
-        promises: Promise<T>[],
-    ): Promise<({ status: 'fulfilled'; value: T } | { status: 'rejected'; reason: any })[]> {
+        promises: Array<Promise<T>>,
+    ): Promise<Array<{ status: 'fulfilled'; value: T } | { status: 'rejected'; reason: any }>> {
         return Promise.all(
             promises.map((promise, i) =>
                 promise
@@ -344,7 +344,7 @@ export class TransactionTestPlugin implements OnApplicationBootstrap {
                 // note the ctx is not passed here, so we are not inside the ongoing transaction
                 const adminRepository = this.connection.getRepository(Administrator);
                 try {
-                    await adminRepository.findOneOrFail(administrator.id);
+                    await adminRepository.findOneOrFail({ where: { id: administrator.id } });
                 } catch (e: any) {
                     TransactionTestPlugin.errorHandler(e);
                 } finally {

+ 18 - 41
packages/core/src/connection/transactional-connection.ts

@@ -9,17 +9,15 @@ import {
     FindOptionsUtils,
     ObjectType,
     Repository,
-    SelectQueryBuilder,
 } from 'typeorm';
+import { FindManyOptions } from 'typeorm/find-options/FindManyOptions';
 
 import { RequestContext } from '../api/common/request-context';
 import { TRANSACTION_MANAGER_KEY } from '../common/constants';
 import { EntityNotFoundError } from '../common/error/errors';
 import { ChannelAware, SoftDeletable } from '../common/types/common-types';
-import { Logger } from '../config/logger/vendure-logger';
 import { VendureEntity } from '../entity/base/base.entity';
 
-import { removeCustomFieldsWithEagerRelations } from './remove-custom-fields-with-eager-relations';
 import { TransactionWrapper } from './transaction-wrapper';
 import { GetEntityOrThrowOptions } from './types';
 
@@ -239,8 +237,15 @@ export class TransactionalConnection {
                 optionsWithoutChannelId,
             );
         } else {
+            const optionsWithId = {
+                ...options,
+                where: {
+                    ...(options.where || {}),
+                    id,
+                },
+            } as FindOneOptions<T>;
             entity = await this.getRepository(ctx, entityType)
-                .findOne({ where: { id }, ...options } as FindOneOptions<T>)
+                .findOne(optionsWithId)
                 .then(result => result ?? undefined);
         }
         if (
@@ -264,44 +269,18 @@ export class TransactionalConnection {
         entity: Type<T>,
         id: ID,
         channelId: ID,
-        options: FindOneOptions = {},
+        options: FindOneOptions<T> = {},
     ) {
-        let qb = this.getRepository(ctx, entity).createQueryBuilder('entity');
-        options.relations = removeCustomFieldsWithEagerRelations(qb, options.relations as string[]);
-        let skipEagerRelations = false;
-        try {
-            // TODO: replace with what?
-            // FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, options);
-        } catch (e: any) {
-            // https://github.com/vendure-ecommerce/vendure/issues/1664
-            // This is a failsafe to catch edge cases related to the TypeORM
-            // bug described in the doc block of `removeCustomFieldsWithEagerRelations`.
-            // In this case, a nested custom field relation has an eager-loaded relation,
-            // and is throwing an error. In this case we throw our hands up and say
-            // "sod it!", refuse to load _any_ relations at all, and rely on the
-            // GraphQL entity resolvers to take care of them.
-            Logger.debug(
-                `TransactionalConnection.findOneInChannel ran into issues joining nested custom field relations. Running the query without joining any relations instead.`,
-            );
-            qb = this.getRepository(ctx, entity).createQueryBuilder('entity');
-            // TODO: replace with what?
-            // FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
-            //     ...options,
-            //     relations: [],
-            //     loadEagerRelations: false,
-            // });
-            skipEagerRelations = true;
-        }
-        if (options.loadEagerRelations !== false && !skipEagerRelations) {
+        const qb = this.getRepository(ctx, entity).createQueryBuilder('entity').setFindOptions(options);
+        if (options.loadEagerRelations !== false) {
             // tslint:disable-next-line:no-non-null-assertion
             FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);
         }
-        return qb
-            .leftJoin('entity.channels', 'channel')
+        qb.leftJoin('entity.channels', '__channel')
             .andWhere('entity.id = :id', { id })
-            .andWhere('channel.id = :channelId', { channelId })
-            .getOne()
-            .then(result => result ?? undefined);
+            .andWhere('__channel.id = :channelId', { channelId });
+
+        return qb.getOne().then(result => result ?? undefined);
     }
 
     /**
@@ -314,7 +293,7 @@ export class TransactionalConnection {
         entity: Type<T>,
         ids: ID[],
         channelId: ID,
-        options: FindOneOptions,
+        options: FindManyOptions<T>,
     ) {
         // the syntax described in https://github.com/typeorm/typeorm/issues/1239#issuecomment-366955628
         // breaks if the array is empty
@@ -322,9 +301,7 @@ export class TransactionalConnection {
             return Promise.resolve([]);
         }
 
-        const qb = this.getRepository(ctx, entity).createQueryBuilder('entity');
-        // TODO: replace with what?
-        // FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, options);
+        const qb = this.getRepository(ctx, entity).createQueryBuilder('entity').setFindOptions(options);
         if (options.loadEagerRelations !== false) {
             // tslint:disable-next-line:no-non-null-assertion
             FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);

+ 0 - 3
packages/core/src/entity/order-line/order-line.entity.ts

@@ -259,9 +259,6 @@ export class OrderLine extends VendureEntity implements HasCustomFields {
      */
     @Calculated()
     get discountedLinePriceWithTax(): number {
-        // return roundMoney(
-        //     this.linePriceWithTax + this.getLineAdjustmentsTotal(true, AdjustmentType.PROMOTION),
-        // );
         return roundMoney(this._discountedUnitPriceWithTax(), this.quantity);
     }
 

+ 1 - 1
packages/core/src/plugin/default-job-queue-plugin/sql-job-queue-strategy.ts

@@ -199,7 +199,7 @@ export class SqlJobQueueStrategy extends PollingJobQueueStrategy implements Insp
         }
         return this.connection
             .getRepository(JobRecord)
-            .findByIds(ids)
+            .find({ where: { id: In(ids) } })
             .then(records => records.map(this.fromRecord));
     }
 

+ 11 - 12
packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts

@@ -158,7 +158,9 @@ export class IndexerController {
 
     async deleteVariant(data: UpdateVariantMessageData): Promise<boolean> {
         const ctx = MutableRequestContext.deserialize(data.ctx);
-        const variants = await this.connection.getRepository(ctx, ProductVariant).findByIds(data.variantIds);
+        const variants = await this.connection.getRepository(ctx, ProductVariant).find({
+            where: { id: In(data.variantIds) },
+        });
         if (variants.length) {
             const languageVariants = unique([
                 ...variants
@@ -311,17 +313,14 @@ export class IndexerController {
     }
 
     private getSearchIndexQueryBuilder(ctx: RequestContext, channelId: ID) {
-        const qb = this.connection.getRepository(ctx, ProductVariant).createQueryBuilder('variants');
-        // TODO: replace with what?
-        // FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
-        //     relations: variantRelations,
-        // });
-        FindOptionsUtils.joinEagerRelations(
-            qb,
-            qb.alias,
-            this.connection.rawConnection.getMetadata(ProductVariant),
-        );
-        qb.leftJoin('variants.product', 'product')
+        const qb = this.connection
+            .getRepository(ctx, ProductVariant)
+            .createQueryBuilder('variants')
+            .setFindOptions({
+                relations: variantRelations,
+                loadEagerRelations: true,
+            })
+            .leftJoin('variants.product', 'product')
             .leftJoin('product.channels', 'channel')
             .where('channel.id = :channelId', { channelId })
             .andWhere('product.deletedAt IS NULL')

+ 9 - 6
packages/core/src/plugin/default-search-plugin/search-strategy/search-strategy-utils.ts

@@ -129,29 +129,32 @@ export function applyLanguageConstraints(
     languageCode: LanguageCode,
     defaultLanguageCode: LanguageCode,
 ) {
+    const lcEscaped = qb.escape('languageCode');
     if (languageCode === defaultLanguageCode) {
-        qb.andWhere('si.languageCode = :languageCode', { languageCode });
+        qb.andWhere(`si.${lcEscaped} = :languageCode`, { languageCode });
     } else {
-        qb.andWhere('si.languageCode IN (:...languageCodes)', {
+        qb.andWhere(`si.${lcEscaped} IN (:...languageCodes)`, {
             languageCodes: [languageCode, defaultLanguageCode],
         });
 
-        const joinFieldConditions = identifierFields.map(field => `si.${field} = sil.${field}`).join(' AND ');
+        const joinFieldConditions = identifierFields
+            .map(field => `si.${qb.escape(field)} = sil.${qb.escape(field)}`)
+            .join(' AND ');
 
         qb.leftJoin(
             SearchIndexItem,
             'sil',
             `
             ${joinFieldConditions}
-            AND si.languageCode != sil.languageCode
-            AND sil.languageCode = :languageCode
+            AND si.${lcEscaped} != sil.${lcEscaped}
+            AND sil.${lcEscaped} = :languageCode
         `,
             {
                 languageCode,
             },
         );
 
-        qb.andWhere('sil.languageCode IS NULL');
+        qb.andWhere(`sil.${lcEscaped} IS NULL`);
     }
 
     return qb;

+ 17 - 27
packages/core/src/service/helpers/list-query-builder/list-query-builder.ts

@@ -27,7 +27,6 @@ import { VendureEntity } from '../../../entity/base/base.entity';
 
 import { getColumnMetadata, getEntityAlias } from './connection-utils';
 import { getCalculatedColumns } from './get-calculated-columns';
-import { parseChannelParam } from './parse-channel-param';
 import { parseFilterParams } from './parse-filter-params';
 import { parseSortParams } from './parse-sort-params';
 
@@ -212,16 +211,19 @@ export class ListQueryBuilder implements OnApplicationBootstrap {
         const repo = extendedOptions.ctx
             ? this.connection.getRepository(extendedOptions.ctx, entity)
             : this.connection.rawConnection.getRepository(entity);
-
-        const qb = repo.createQueryBuilder(extendedOptions.entityAlias || entity.name.toLowerCase());
+        const alias = extendedOptions.entityAlias || entity.name.toLowerCase();
         const minimumRequiredRelations = this.getMinimumRequiredRelations(repo, options, extendedOptions);
-        // TODO: what do we replace this with?
-        // FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
-        //     relations: minimumRequiredRelations,
-        //     take,
-        //     skip,
-        //     where: extendedOptions.where || {},
-        // } as FindManyOptions<T>);
+        const qb = repo.createQueryBuilder(alias).setFindOptions({
+            relations: minimumRequiredRelations,
+            take,
+            skip,
+            where: extendedOptions.where || {},
+            // We would like to be able to use this feature
+            // rather than our custom `optimizeGetManyAndCountMethod()` implementation,
+            // but at this time (TypeORM 0.3.12) it throws an error in the case of
+            // a Collection that joins its parent entity.
+            // relationLoadStrategy: 'query',
+        });
         // tslint:disable-next-line:no-non-null-assertion
         FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);
 
@@ -234,13 +236,7 @@ export class ListQueryBuilder implements OnApplicationBootstrap {
         }
         const customFieldsForType = this.configService.customFields[entity.name as keyof CustomFields];
         const sortParams = Object.assign({}, options.sort, extendedOptions.orderBy);
-        this.applyTranslationConditions(
-            qb,
-            entity,
-            sortParams,
-            extendedOptions.ctx,
-            extendedOptions.entityAlias,
-        );
+        this.applyTranslationConditions(qb, entity, sortParams, extendedOptions.ctx, alias);
         const sort = parseSortParams(
             rawConnection,
             entity,
@@ -275,15 +271,9 @@ export class ListQueryBuilder implements OnApplicationBootstrap {
         }
 
         if (extendedOptions.channelId) {
-            const channelFilter = parseChannelParam(
-                rawConnection,
-                entity,
-                extendedOptions.channelId,
-                extendedOptions.entityAlias,
-            );
-            if (channelFilter) {
-                qb.andWhere(channelFilter.clause, channelFilter.parameters);
-            }
+            qb.leftJoin(`${alias}.channels`, 'lqb__channel').andWhere('lqb__channel.id = :channelId', {
+                channelId: extendedOptions.channelId,
+            });
         }
 
         qb.orderBy(sort);
@@ -485,7 +475,7 @@ export class ListQueryBuilder implements OnApplicationBootstrap {
                         where: { id: In(entitiesIds) },
                         select: ['id'],
                         relations: relationPaths,
-                        loadEagerRelations: false,
+                        loadEagerRelations: true,
                     } as FindManyOptions<T>)
                     .then(results =>
                         results.map(r => ({ relation: relationPaths[0] as keyof T, entity: r })),

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

@@ -10,7 +10,7 @@ import { In, IsNull } from 'typeorm';
 import { RequestContext } from '../../api/common/request-context';
 import { RelationPaths } from '../../api/index';
 import { EntityNotFoundError, InternalServerError, UserInputError } from '../../common/error/errors';
-import { convertRelationPaths, idsAreEqual } from '../../common/index';
+import { idsAreEqual } from '../../common/index';
 import { ListQueryOptions } from '../../common/types/common-types';
 import { ConfigService } from '../../config';
 import { TransactionalConnection } from '../../connection/transactional-connection';
@@ -90,13 +90,7 @@ export class AdministratorService {
         return this.connection
             .getRepository(ctx, Administrator)
             .findOne({
-                relations: relations
-                    ? convertRelationPaths(relations)
-                    : {
-                          user: {
-                              roles: true,
-                          },
-                      },
+                relations: relations ?? ['user', 'user.roles'],
                 where: {
                     id: administratorId,
                     deletedAt: IsNull(),
@@ -117,7 +111,7 @@ export class AdministratorService {
         return this.connection
             .getRepository(ctx, Administrator)
             .findOne({
-                relations: convertRelationPaths(relations),
+                relations,
                 where: {
                     user: { id: userId },
                     deletedAt: IsNull(),

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

@@ -17,7 +17,7 @@ import { ROOT_COLLECTION_NAME } from '@vendure/common/lib/shared-constants';
 import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
 import { merge } from 'rxjs';
 import { debounceTime } from 'rxjs/operators';
-import { IsNull } from 'typeorm';
+import { In, IsNull } from 'typeorm';
 
 import { RequestContext, SerializedRequestContext } from '../../api/common/request-context';
 import { RelationPaths } from '../../api/index';
@@ -238,10 +238,6 @@ export class CollectionService implements OnModuleInit {
     }
 
     async getParent(ctx: RequestContext, collectionId: ID): Promise<Collection | undefined> {
-        const parentIdSelect =
-            this.connection.rawConnection.options.type === 'postgres'
-                ? '"child"."parentId"'
-                : 'child.parentId';
         const parent = await this.connection
             .getRepository(ctx, Collection)
             .createQueryBuilder('collection')
@@ -250,7 +246,7 @@ export class CollectionService implements OnModuleInit {
                 qb =>
                     `collection.id = ${qb
                         .subQuery()
-                        .select(parentIdSelect)
+                        .select(`${qb.escape('child')}.${qb.escape('parentId')}`)
                         .from(Collection, 'child')
                         .where('child.id = :id', { id: collectionId })
                         .getQuery()}`,
@@ -375,7 +371,7 @@ export class CollectionService implements OnModuleInit {
 
         return this.connection
             .getRepository(ctx, Collection)
-            .findByIds(ancestors.map(c => c.id))
+            .find({ where: { id: In(ancestors.map(c => c.id)) } })
             .then(categories => {
                 const resultCategories: Array<Collection | Translated<Collection>> = [];
                 ancestors.forEach(a => {
@@ -795,7 +791,7 @@ export class CollectionService implements OnModuleInit {
         }
         const collectionsToAssign = await this.connection
             .getRepository(ctx, Collection)
-            .findByIds(input.collectionIds);
+            .find({ where: { id: In(input.collectionIds) } });
 
         await Promise.all(
             collectionsToAssign.map(collection =>
@@ -840,7 +836,7 @@ export class CollectionService implements OnModuleInit {
         }
         const collectionsToRemove = await this.connection
             .getRepository(ctx, Collection)
-            .findByIds(input.collectionIds);
+            .find({ where: { id: In(input.collectionIds) } });
 
         await Promise.all(
             collectionsToRemove.map(async collection => {

+ 2 - 2
packages/core/src/service/services/customer.service.ts

@@ -122,7 +122,7 @@ export class CustomerService {
         return this.connection
             .findOneInChannel(ctx, Customer, id, ctx.channelId, {
                 relations,
-                where: { deletedAt: null },
+                where: { deletedAt: IsNull() },
             })
             .then(result => result ?? undefined);
     }
@@ -180,7 +180,7 @@ export class CustomerService {
             {
                 relations: ['groups'],
                 where: {
-                    deletedAt: null,
+                    deletedAt: IsNull(),
                 },
             },
         );

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

@@ -181,7 +181,9 @@ export class FacetService {
             entityType: Facet,
             translationType: FacetTranslation,
             beforeSave: async f => {
-                f.code = await this.ensureUniqueCode(ctx, f.code, f.id);
+                if (f.code) {
+                    f.code = await this.ensureUniqueCode(ctx, f.code, f.id);
+                }
             },
         });
         await this.customFieldRelationService.updateRelations(ctx, Facet, input, facet);

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

@@ -3,6 +3,7 @@ import { ConfigurableOperationInput, OrderLineInput } from '@vendure/common/lib/
 import { ID } from '@vendure/common/lib/shared-types';
 import { isObject } from '@vendure/common/lib/shared-utils';
 import { unique } from '@vendure/common/lib/unique';
+import { In } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import {
@@ -74,7 +75,7 @@ export class FulfillmentService {
 
         const orderLines = await this.connection
             .getRepository(ctx, OrderLine)
-            .findByIds(lines.map(l => l.orderLineId));
+            .find({ where: { id: In(lines.map(l => l.orderLineId)) } });
 
         const newFulfillment = await this.connection.getRepository(ctx, Fulfillment).save(
             new Fulfillment({

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

@@ -59,11 +59,11 @@ export class GlobalSettingsService {
      */
     async getSettings(ctx: RequestContext): Promise<GlobalSettings> {
         const settings = await this.requestCache.get(ctx, 'globalSettings', () =>
-            this.connection.getRepository(ctx, GlobalSettings).findOne({
-                order: {
-                    createdAt: 'ASC',
-                },
-            }),
+            this.connection
+                .getRepository(ctx, GlobalSettings)
+                .createQueryBuilder('global_settings')
+                .orderBy(this.connection.rawConnection.driver.escape('createdAt'), 'ASC')
+                .getOne(),
         );
         if (!settings) {
             throw new InternalServerError(`error.global-settings-not-found`);

+ 9 - 20
packages/core/src/service/services/order.service.ts

@@ -47,8 +47,6 @@ import { RequestContextCacheService } from '../../cache/request-context-cache.se
 import { ErrorResultUnion, isGraphQlErrorResult } from '../../common/error/error-result';
 import { EntityNotFoundError, InternalServerError, UserInputError } from '../../common/error/errors';
 import {
-    AlreadyRefundedError,
-    CancelActiveOrderError,
     CancelPaymentError,
     EmptyOrderLineSelectionError,
     FulfillmentStateTransitionError,
@@ -58,7 +56,6 @@ import {
     MultipleOrderError,
     NothingToRefundError,
     PaymentOrderMismatchError,
-    QuantityTooGreatError,
     RefundOrderStateError,
     SettlePaymentError,
 } from '../../common/error/generated-graphql-admin-errors';
@@ -74,17 +71,15 @@ import {
     PaymentFailedError,
 } from '../../common/error/generated-graphql-shop-errors';
 import { grossPriceOf, netPriceOf } from '../../common/tax-utils';
-import { ListQueryOptions, PaymentMetadata } from '../../common/types/common-types';
+import { ListQueryOptions } from '../../common/types/common-types';
 import { assertFound, idsAreEqual } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
-import { removeCustomFieldsWithEagerRelations } from '../../connection/remove-custom-fields-with-eager-relations';
 import { TransactionalConnection } from '../../connection/transactional-connection';
 import { Channel } from '../../entity/channel/channel.entity';
 import { Customer } from '../../entity/customer/customer.entity';
 import { Fulfillment } from '../../entity/fulfillment/fulfillment.entity';
 import { HistoryEntry } from '../../entity/history-entry/history-entry.entity';
 import { FulfillmentLine } from '../../entity/order-line-reference/fulfillment-line.entity';
-import { RefundLine } from '../../entity/order-line-reference/refund-line.entity';
 import { OrderLine } from '../../entity/order-line/order-line.entity';
 import { OrderModification } from '../../entity/order-modification/order-modification.entity';
 import { Order } from '../../entity/order/order.entity';
@@ -94,7 +89,6 @@ import { Promotion } from '../../entity/promotion/promotion.entity';
 import { Refund } from '../../entity/refund/refund.entity';
 import { Session } from '../../entity/session/session.entity';
 import { ShippingLine } from '../../entity/shipping-line/shipping-line.entity';
-import { Allocation } from '../../entity/stock-movement/allocation.entity';
 import { Surcharge } from '../../entity/surcharge/surcharge.entity';
 import { User } from '../../entity/user/user.entity';
 import { EventBus } from '../../event-bus/event-bus';
@@ -118,11 +112,7 @@ import { PaymentStateMachine } from '../helpers/payment-state-machine/payment-st
 import { RefundStateMachine } from '../helpers/refund-state-machine/refund-state-machine';
 import { ShippingCalculator } from '../helpers/shipping-calculator/shipping-calculator';
 import { TranslatorService } from '../helpers/translator/translator.service';
-import {
-    getOrdersFromLines,
-    orderLinesAreAllCancelled,
-    totalCoveredByPayments,
-} from '../helpers/utils/order-utils';
+import { getOrdersFromLines, totalCoveredByPayments } from '../helpers/utils/order-utils';
 import { patchEntity } from '../helpers/utils/patch-entity';
 
 import { ChannelService } from './channel.service';
@@ -223,7 +213,7 @@ export class OrderService {
         relations?: RelationPaths<Order>,
     ): Promise<Order | undefined> {
         const qb = this.connection.getRepository(ctx, Order).createQueryBuilder('order');
-        let effectiveRelations = relations ?? [
+        const effectiveRelations = relations ?? [
             'channels',
             'customer',
             'customer.user',
@@ -244,16 +234,15 @@ export class OrderService {
         ) {
             effectiveRelations.push('lines.productVariant.taxCategory');
         }
-        effectiveRelations = removeCustomFieldsWithEagerRelations(qb, effectiveRelations);
-        // TODO: What's the replacement?
-        // FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
-        //     relations: effectiveRelations,
-        // });
-        qb.leftJoin('order.channels', 'channel')
+        qb.setFindOptions({ relations: effectiveRelations })
+            .leftJoin('order.channels', 'channel')
             .where('order.id = :orderId', { orderId })
             .andWhere('channel.id = :channelId', { channelId: ctx.channelId });
         if (effectiveRelations.includes('lines')) {
-            qb.addOrderBy('order__lines.createdAt', 'ASC').addOrderBy('order__lines.productVariantId', 'ASC');
+            qb.addOrderBy(`order__order_lines.${qb.escape('createdAt')}`, 'ASC').addOrderBy(
+                `order__order_lines.${qb.escape('productVariantId')}`,
+                'ASC',
+            );
         }
 
         // tslint:disable-next-line:no-non-null-assertion

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

@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
 import { ManualPaymentInput, RefundOrderInput } from '@vendure/common/lib/generated-types';
 import { DeepPartial, ID } from '@vendure/common/lib/shared-types';
 import { summate } from '@vendure/common/lib/shared-utils';
+import { In } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { InternalServerError } from '../../common/error/errors';
@@ -299,7 +300,7 @@ export class PaymentService {
         let refundOrderLinesTotal = 0;
         const orderLines = await this.connection
             .getRepository(ctx, OrderLine)
-            .findByIds(input.lines.map(l => l.orderLineId));
+            .find({ where: { id: In(input.lines.map(l => l.orderLineId)) } });
         for (const line of input.lines) {
             const orderLine = orderLines.find(l => idsAreEqual(l.id, line.orderLineId));
             if (orderLine && 0 < orderLine.quantity) {

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

@@ -116,7 +116,7 @@ export class ProductVariantService {
                     ...(relations || ['product', 'featuredAsset', 'product.featuredAsset']),
                     'taxCategory',
                 ],
-                where: { deletedAt: null },
+                where: { deletedAt: IsNull() },
             })
             .then(async result => {
                 if (result) {
@@ -397,7 +397,7 @@ export class ProductVariantService {
                 if (optionIds && optionIds.length) {
                     const selectedOptions = await this.connection
                         .getRepository(ctx, ProductOption)
-                        .findByIds(optionIds);
+                        .find({ where: { id: In(optionIds) } });
                     variant.options = selectedOptions;
                 }
                 if (input.facetValueIds) {
@@ -527,7 +527,9 @@ export class ProductVariantService {
 
     async softDelete(ctx: RequestContext, id: ID | ID[]): Promise<DeletionResponse> {
         const ids = Array.isArray(id) ? id : [id];
-        const variants = await this.connection.getRepository(ctx, ProductVariant).findByIds(ids);
+        const variants = await this.connection
+            .getRepository(ctx, ProductVariant)
+            .find({ where: { id: In(ids) } });
         for (const variant of variants) {
             variant.deletedAt = new Date();
         }
@@ -689,7 +691,7 @@ export class ProductVariantService {
         }
         const variants = await this.connection
             .getRepository(ctx, ProductVariant)
-            .findByIds(input.productVariantIds);
+            .find({ where: { id: In(input.productVariantIds) } });
         for (const variant of variants) {
             await this.channelService.removeFromChannels(ctx, ProductVariant, variant.id, [input.channelId]);
             await this.connection.getRepository(ctx, ProductVariantPrice).delete({
@@ -754,7 +756,7 @@ export class ProductVariantService {
         const product = await this.connection.getEntityOrThrow(ctx, Product, input.productId, {
             channelId: ctx.channelId,
             relations: ['variants', 'variants.options'],
-            loadEagerRelations: false,
+            loadEagerRelations: true,
         });
 
         const inputOptionIds = this.sortJoin(optionIds, ',');

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

@@ -111,7 +111,7 @@ export class ProductService {
         const product = await this.connection.findOneInChannel(ctx, Product, productId, ctx.channelId, {
             relations: unique(effectiveRelations),
             where: {
-                deletedAt: null,
+                deletedAt: IsNull(),
             },
         });
         if (!product) {
@@ -125,11 +125,10 @@ export class ProductService {
         productIds: ID[],
         relations?: RelationPaths<Product>,
     ): Promise<Array<Translated<Product>>> {
-        const qb = this.connection.getRepository(ctx, Product).createQueryBuilder('product');
-        // TODO: what's the replacement?
-        // FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
-        //     relations: (relations && false) || this.relations,
-        // });
+        const qb = this.connection
+            .getRepository(ctx, Product)
+            .createQueryBuilder('product')
+            .setFindOptions({ relations: (relations && false) || this.relations });
         // tslint:disable-next-line:no-non-null-assertion
         FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);
         return qb
@@ -314,7 +313,9 @@ export class ProductService {
             ([] as ID[]).concat(...productsWithVariants.map(p => p.assets.map(a => a.assetId))),
         );
         await this.assetService.assignToChannel(ctx, { channelId: input.channelId, assetIds });
-        const products = await this.connection.getRepository(ctx, Product).findByIds(input.productIds);
+        const products = await this.connection
+            .getRepository(ctx, Product)
+            .find({ where: { id: In(input.productIds) } });
         for (const product of products) {
             this.eventBus.publish(new ProductChannelEvent(ctx, product, input.channelId, 'assigned'));
         }
@@ -338,7 +339,9 @@ export class ProductService {
             ),
             channelId: input.channelId,
         });
-        const products = await this.connection.getRepository(ctx, Product).findByIds(input.productIds);
+        const products = await this.connection
+            .getRepository(ctx, Product)
+            .find({ where: { id: In(input.productIds) } });
         for (const product of products) {
             this.eventBus.publish(new ProductChannelEvent(ctx, product, input.channelId, 'removed'));
         }

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

@@ -15,7 +15,7 @@ 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 { IsNull } from 'typeorm';
+import { In, IsNull } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { RelationPaths } from '../../api/index';
@@ -332,7 +332,9 @@ export class PromotionService {
             a => AdjustmentSource.decodeSourceId(a.adjustmentSource).id,
         );
         const promotionIds = unique(allPromotionIds);
-        const promotions = await this.connection.getRepository(ctx, Promotion).findByIds(promotionIds);
+        const promotions = await this.connection
+            .getRepository(ctx, Promotion)
+            .find({ where: { id: In(promotionIds) } });
         order.promotions = promotions;
         return this.connection.getRepository(ctx, Order).save(order);
     }

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

@@ -93,7 +93,7 @@ export class ShippingMethodService {
             ctx.channelId,
             {
                 relations,
-                ...(includeDeleted === false ? { where: { deletedAt: null } } : {}),
+                ...(includeDeleted === false ? { where: { deletedAt: IsNull() } } : {}),
             },
         );
         return (shippingMethod && this.translator.translate(shippingMethod, ctx)) ?? undefined;

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

@@ -209,7 +209,7 @@ export class StockMovementService {
         const globalTrackInventory = (await this.globalSettingsService.getSettings(ctx)).trackInventory;
         const orderLines = await this.connection
             .getRepository(ctx, OrderLine)
-            .findByIds(lines.map(line => line.orderLineId));
+            .find({ where: { id: In(lines.map(line => line.orderLineId)) } });
         for (const lineRow of lines) {
             const orderLine = orderLines.find(line => idsAreEqual(line.id, lineRow.orderLineId));
             if (!orderLine) {

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

@@ -46,6 +46,7 @@ export class UserService {
         return this.connection
             .getRepository(ctx, User)
             .findOne({
+                where: { id: userId },
                 relations: {
                     roles: {
                         channels: true,

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

@@ -9,6 +9,7 @@ import {
 } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
 import { unique } from '@vendure/common/lib/unique';
+import { In } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
 import { createSelfRefreshingCache, SelfRefreshingCache } from '../../common/self-refreshing-cache';
@@ -186,7 +187,7 @@ export class ZoneService {
     }
 
     private getCountriesFromIds(ctx: RequestContext, ids: ID[]): Promise<Country[]> {
-        return this.connection.getRepository(ctx, Country).findByIds(ids);
+        return this.connection.getRepository(ctx, Country).find({ where: { id: In(ids) } });
     }
 
     /**

+ 2 - 1
packages/dev-server/test-plugins/entity-hydrate-plugin.ts

@@ -25,7 +25,8 @@ class TestResolver {
 
     @Query()
     async hydrateTest(@Ctx() ctx: RequestContext, @Args() args: { id: ID }) {
-        const product = await this.connection.getRepository(ctx, Product).findOne(args.id, {
+        const product = await this.connection.getRepository(ctx, Product).findOne({
+            where: { id: args.id },
             relations: ['featuredAsset'],
         });
         await this.entityHydrator.hydrate(ctx, product!, {

+ 1 - 1
packages/dev-server/test-plugins/issue-1664/issue-1664.ts

@@ -128,7 +128,7 @@ export class Test1664Plugin implements OnApplicationBootstrap {
         (user.customFields as any).profile = profile;
         await this.connection.rawConnection.getRepository(User).save(user);
 
-        const asset = await this.connection.rawConnection.getRepository(Asset).findOne(1);
+        const asset = await this.connection.rawConnection.getRepository(Asset).findOne({ where: { id: 1 } });
         if (asset) {
             const profileAsset = this.connection.rawConnection.getRepository(ProfileAsset).save({
                 asset,

+ 6 - 1
packages/dev-server/test-plugins/multivendor-plugin/multivendor.plugin.ts

@@ -185,12 +185,17 @@ export class MultivendorPlugin implements OnApplicationBootstrap {
             const allChannels = await this.connection.getRepository(ctx, Channel).find();
             const createdPaymentMethod = await this.paymentMethodService.create(ctx, {
                 code: CONNECTED_PAYMENT_METHOD_CODE,
-                name: 'Connected Payments',
                 enabled: true,
                 handler: {
                     code: multivendorPaymentMethodHandler.code,
                     arguments: [],
                 },
+                translations: [
+                    {
+                        languageCode: LanguageCode.en,
+                        name: 'Connected Payments',
+                    },
+                ],
             });
             await this.channelService.assignToChannels(
                 ctx,

+ 12 - 9
packages/elasticsearch-plugin/src/indexing/indexer.controller.ts

@@ -25,6 +25,7 @@ import {
     Translation,
 } from '@vendure/core';
 import { Observable } from 'rxjs';
+import { In, IsNull } from 'typeorm';
 
 import { ELASTIC_SEARCH_OPTIONS, loggerCtx, VARIANT_INDEX_NAME } from '../constants';
 import { ElasticsearchOptions } from '../options';
@@ -504,12 +505,13 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         const operations: BulkVariantOperation[] = [];
         let product: Product | undefined;
         try {
-            product = await this.connection.getRepository(Product).findOne(productId, {
-                relations: this.productRelations,
-                where: {
-                    deletedAt: null,
-                },
-            });
+            product = await this.connection
+                .getRepository(Product)
+                .findOne({
+                    where: { id: productId, deletedAt: IsNull() },
+                    relations: this.productRelations,
+                })
+                .then(result => result ?? undefined);
         } catch (e: any) {
             Logger.error(e.message, loggerCtx, e.stack);
             throw e;
@@ -520,8 +522,8 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         const updatedProductVariants = await this.connection.getRepository(ProductVariant).find({
             relations: this.variantRelations,
             where: {
-                productId,
-                deletedAt: null,
+                id: productId,
+                deletedAt: IsNull(),
             },
             order: {
                 id: 'ASC',
@@ -753,7 +755,8 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
     }
 
     private async getProductIdsByVariantIds(variantIds: ID[]): Promise<ID[]> {
-        const variants = await this.connection.getRepository(ProductVariant).findByIds(variantIds, {
+        const variants = await this.connection.getRepository(ProductVariant).find({
+            where: { id: In(variantIds) },
             relations: ['product'],
             loadEagerRelations: false,
         });

+ 2 - 1
packages/payments-plugin/src/stripe/stripe.service.ts

@@ -80,7 +80,8 @@ export class StripeService {
      */
     private async getStripeCustomerId(ctx: RequestContext, activeOrder: Order): Promise<string | undefined> {
         // Load relation with customer not available in the response from activeOrderService.getOrderFromContext()
-        const order = await this.connection.getRepository(Order).findOne(activeOrder.id, {
+        const order = await this.connection.getRepository(Order).findOne({
+            where: { id: activeOrder.id },
             relations: ['customer'],
         });
 

+ 8 - 5
packages/testing/src/utils/get-default-channel-token.ts

@@ -11,11 +11,14 @@ export async function getDefaultChannelToken(logging = true): Promise<string> {
     const connection = await getConnection();
     let defaultChannel: Channel | undefined;
     try {
-        defaultChannel = await connection.manager.getRepository(Channel).findOne({
-            where: {
-                code: DEFAULT_CHANNEL_CODE,
-            },
-        });
+        defaultChannel = await connection.manager
+            .getRepository(Channel)
+            .findOne({
+                where: {
+                    code: DEFAULT_CHANNEL_CODE,
+                },
+            })
+            .then(result => result ?? undefined);
     } catch (err: any) {
         console.log(`Error occurred when attempting to get default Channel`);
         console.log(err);