Browse Source

WIP: Accessible entity IDs

Attempting to implement #2031 but it is proving
quite difficult to make everything work.

I added a new e2e test, but the current implementation breaks
existing tests as well as not fully passing the new test.
Michael Bromley 1 year ago
parent
commit
5eab7cecb6

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

@@ -1366,6 +1366,7 @@ describe('Custom field relations', () => {
             });
 
             it('findOne on Asset', async () => {
+                console.log(`HERE!!!`);
                 const { asset } = await adminClient.query(gql`
                     query {
                         asset(id: "T_1") {
@@ -1374,6 +1375,7 @@ describe('Custom field relations', () => {
                         }
                     }
                 `);
+                console.log(asset);
                 expect(asset.customFields.single.id).toBe('T_2');
                 expect(asset.customFields.multi.length).toEqual(2);
             });
@@ -1393,6 +1395,40 @@ describe('Custom field relations', () => {
         });
     });
 
+    it('relation Id is available without joining for non-list custom fields', async () => {
+        await adminClient.query(gql`
+            mutation {
+                updateProduct(input: { id: "T_1", customFields: { singleId: "T_2" } }) {
+                    id
+                }
+            }
+        `);
+
+        const connection = server.app.get(TransactionalConnection);
+
+        const result1 = await connection.rawConnection.getRepository(Product).findOne({
+            where: { id: 1 },
+        });
+
+        expect(result1?.customFields).toEqual({
+            owner: null,
+            primitive: 'test',
+            singleId: 2,
+        });
+
+        if (result1) {
+            result1.customFields.owner = { id: 1 };
+            await connection.rawConnection.getRepository(Product).save(result1);
+        }
+
+        const result2 = await connection.rawConnection.getRepository(Product).findOne({
+            where: { id: 1 },
+        });
+
+        expect(result2?.customFields.owner.id).toBe(1);
+        expect(result2?.customFields.ownerId).toBe(1);
+    });
+
     it('null values', async () => {
         const { updateCustomerAddress } = await adminClient.query(gql`
             mutation {

+ 1 - 1
packages/core/src/api/config/generate-resolvers.ts

@@ -343,7 +343,7 @@ function generateCustomFieldResolvers(
 
 function getCustomScalars(configService: ConfigService, apiType: 'admin' | 'shop') {
     return getPluginAPIExtensions(configService.plugins, apiType)
-        .map(e => (typeof e.scalars === 'function' ? e.scalars() : (e.scalars ?? {})))
+        .map(e => (typeof e.scalars === 'function' ? e.scalars() : e.scalars ?? {}))
         .reduce(
             (all, scalarMap) => ({
                 ...all,

+ 4 - 1
packages/core/src/api/resolvers/admin/asset.resolver.ts

@@ -32,7 +32,10 @@ export class AssetResolver {
         @Args() args: QueryAssetArgs,
         @Relations(Asset) relations: RelationPaths<Asset>,
     ): Promise<Asset | undefined> {
-        return this.assetService.findOne(ctx, args.id, relations);
+        console.log(`assetResolver findOne`, relations);
+        const asset = await this.assetService.findOne(ctx, args.id, relations);
+        console.log(`assetResolver findOne`, asset);
+        return asset;
     }
 
     @Query()

+ 2 - 1
packages/core/src/bootstrap.ts

@@ -224,6 +224,7 @@ export async function preBootstrapConfig(
     Logger.useLogger(config.logger);
     config = await runPluginConfigurations(config);
     const entityIdStrategy = config.entityOptions.entityIdStrategy ?? config.entityIdStrategy;
+    registerCustomEntityFields(config);
     setEntityIdStrategy(entityIdStrategy, entities);
     const moneyStrategy = config.entityOptions.moneyStrategy;
     setMoneyStrategy(moneyStrategy, entities);
@@ -232,7 +233,7 @@ export async function preBootstrapConfig(
         process.exitCode = 1;
         throw new Error('CustomFields config error:\n- ' + customFieldValidationResult.errors.join('\n- '));
     }
-    registerCustomEntityFields(config);
+
     await runEntityMetadataModifiers(config);
     setExposedHeaders(config);
     return config;

+ 3 - 0
packages/core/src/entity/register-custom-entity-fields.ts

@@ -19,6 +19,7 @@ import { DateUtils } from 'typeorm/util/DateUtils';
 import { CustomFieldConfig, CustomFields } from '../config/custom-field/custom-field-types';
 import { Logger } from '../config/logger/vendure-logger';
 import { VendureConfig } from '../config/vendure-config';
+import { EntityId } from './entity-id.decorator';
 
 /**
  * The maximum length of the "length" argument of a MySQL varchar column.
@@ -53,6 +54,8 @@ function registerCustomFieldsForEntity(
                             eager: customField.eager,
                         })(instance, name);
                         JoinColumn()(instance, name);
+                        // add an "id" column to the custom field entity
+                        EntityId({ nullable: true })(instance, `${name}Id`);
                     }
                 } else {
                     const options: ColumnOptions = {

+ 33 - 2
packages/core/src/entity/set-entity-id-strategy.ts

@@ -1,5 +1,6 @@
 import { Type } from '@vendure/common/lib/shared-types';
-import { Column, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
+import { Column, getMetadataArgsStorage, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
+import { EmbeddedMetadataArgs } from 'typeorm/metadata-args/EmbeddedMetadataArgs';
 
 import { EntityIdStrategy } from '../config/entity/entity-id-strategy';
 
@@ -7,7 +8,37 @@ import { getIdColumnsFor, getPrimaryGeneratedIdColumn } from './entity-id.decora
 
 export function setEntityIdStrategy(entityIdStrategy: EntityIdStrategy<any>, entities: Array<Type<any>>) {
     setBaseEntityIdType(entityIdStrategy);
-    setEntityIdColumnTypes(entityIdStrategy, entities);
+
+    // eslint-disable-next-line @typescript-eslint/ban-types
+    const customFieldEntities: Array<Type<any>> = [];
+    // get all the CustomField classes associated with the entities
+    const metadataArgsStorage = getMetadataArgsStorage();
+    for (const EntityCtor of entities) {
+        const entityName = EntityCtor.name;
+        const customFieldsMetadata = getCustomFieldsMetadata(entityName);
+        const customFieldsClass = customFieldsMetadata?.type();
+        if (customFieldsClass && typeof customFieldsClass !== 'string') {
+            customFieldEntities.push(customFieldsClass as Type<any>);
+        }
+    }
+
+    setEntityIdColumnTypes(entityIdStrategy, [...entities, ...customFieldEntities]);
+
+    // eslint-disable-next-line @typescript-eslint/ban-types
+    function getCustomFieldsMetadata(entity: Function | string): EmbeddedMetadataArgs | undefined {
+        const entityName = typeof entity === 'string' ? entity : entity.name;
+        const metadataArgs = metadataArgsStorage.embeddeds.find(item => {
+            if (item.propertyName === 'customFields') {
+                const targetName = typeof item.target === 'string' ? item.target : item.target.name;
+                return targetName === entityName;
+            }
+        });
+
+        if (!metadataArgs) {
+            // throw new Error(`Could not find embedded CustomFields property on entity "${entityName}"`);
+        }
+        return metadataArgs;
+    }
 }
 
 function setEntityIdColumnTypes(entityIdStrategy: EntityIdStrategy<any>, entities: Array<Type<any>>) {

+ 3 - 0
packages/core/src/service/helpers/custom-field-relation/custom-field-relation.service.ts

@@ -64,6 +64,9 @@ export class CustomFieldRelationService {
                             ...entity.customFields,
                             ...entityWithCustomFields?.customFields,
                             [field.name]: relations,
+                            ...(relations && !Array.isArray(relations)
+                                ? { [`${field.name}Id`]: relations.id }
+                                : {}),
                         };
                         await this.connection
                             .getRepository(ctx, entityType)