Browse Source

fix(core): Work-around for nested custom field relations issue

Relates to #1664
Michael Bromley 3 years ago
parent
commit
651710a4c3

+ 37 - 0
packages/core/e2e/custom-field-relations.e2e-spec.ts

@@ -79,6 +79,7 @@ class Vendor extends VendureEntity {
     constructor() {
         super();
     }
+
     @OneToOne(type => Product, { eager: true })
     @JoinColumn()
     featuredProduct: Product;
@@ -112,6 +113,16 @@ customFieldConfig.Product?.push(
         public: true,
     },
 );
+customFieldConfig.User?.push({
+    name: 'cfVendor',
+    type: 'relation',
+    entity: Vendor,
+    graphQLType: 'Vendor',
+    list: false,
+    eager: true,
+    internal: false,
+    public: true,
+});
 
 const testResolverSpy = jest.fn();
 
@@ -138,6 +149,7 @@ class TestResolver1636 {
                 getAssetTest(id: ID!): Boolean!
             }
             type Vendor {
+                id: ID
                 featuredProduct: Product
             }
         `,
@@ -146,6 +158,7 @@ class TestResolver1636 {
     adminApiExtensions: {
         schema: gql`
             type Vendor {
+                id: ID
                 featuredProduct: Product
             }
         `,
@@ -819,6 +832,30 @@ describe('Custom field relations', () => {
                 expect(product).toBeDefined();
             });
 
+            // https://github.com/vendure-ecommerce/vendure/issues/1664
+            it('successfully gets product by id with nested eager-loading custom field relation', async () => {
+                const { customer } = await adminClient.query(gql`
+                    query {
+                        customer(id: "T_1") {
+                            id
+                            firstName
+                            lastName
+                            emailAddress
+                            phoneNumber
+                            user {
+                                customFields {
+                                    cfVendor {
+                                        id
+                                    }
+                                }
+                            }
+                        }
+                    }
+                `);
+
+                expect(customer).toBeDefined();
+            });
+
             it('successfully gets product by slug with eager-loading custom field relation', async () => {
                 const { product } = await shopClient.query(gql`
                     query {

+ 24 - 3
packages/core/src/connection/transactional-connection.ts

@@ -264,10 +264,31 @@ export class TransactionalConnection {
         channelId: ID,
         options: FindOneOptions = {},
     ) {
-        const qb = this.getRepository(ctx, entity).createQueryBuilder('entity');
+        let qb = this.getRepository(ctx, entity).createQueryBuilder('entity');
         options.relations = removeCustomFieldsWithEagerRelations(qb, options.relations);
-        FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, options);
-        if (options.loadEagerRelations !== false) {
+        let skipEagerRelations = false;
+        try {
+            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');
+            FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
+                ...options,
+                relations: [],
+                loadEagerRelations: false,
+            });
+            skipEagerRelations = true;
+        }
+        if (options.loadEagerRelations !== false && !skipEagerRelations) {
             // tslint:disable-next-line:no-non-null-assertion
             FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);
         }

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

@@ -1,4 +1,5 @@
 import {
+    Facet,
     LanguageCode,
     LocaleString,
     PluginCommonModule,
@@ -75,6 +76,27 @@ const schema = gql`
                 component: 'cp-product-vendor-selector',
             },
         });
+        config.customFields.User.push({
+            name: 'vendor',
+            label: [{ languageCode: LanguageCode.en_AU, value: 'Vendor' }],
+            type: 'relation',
+            entity: Vendor,
+            eager: true,
+            nullable: false,
+            defaultValue: null,
+            ui: {
+                component: 'cp-product-vendor-selector',
+            },
+        });
+        config.customFields.User.push({
+            name: 'facet',
+            label: [{ languageCode: LanguageCode.en_AU, value: 'Facet' }],
+            type: 'relation',
+            entity: Facet,
+            eager: true,
+            nullable: false,
+            defaultValue: null,
+        });
 
         config.customFields.Product.push({
             name: 'shopifyId',