Skip to content

Commit

Permalink
feat(core): Support bi-directional relations in customFields (#2365)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexis Kobrinski <[email protected]>
  • Loading branch information
kefkalexis and Alexis Kobrinski authored Aug 29, 2023
1 parent 88430e5 commit 0313ce5
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 4 deletions.
60 changes: 59 additions & 1 deletion packages/core/e2e/custom-field-relations.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
assertFound,
Asset,
Collection,
Country,
Expand All @@ -11,22 +12,29 @@ import {
LogLevel,
manualFulfillmentHandler,
mergeConfig,
PluginCommonModule,
Product,
ProductOption,
ProductOptionGroup,
ProductVariant,
RequestContext,
ShippingMethod,
TransactionalConnection,
VendureEntity,
VendurePlugin,
} from '@vendure/core';
import { createTestEnvironment } from '@vendure/testing';
import gql from 'graphql-tag';
import path from 'path';
import { Repository } from 'typeorm';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

import { initialData } from '../../../e2e-common/e2e-initial-data';
import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';

import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
import { TestPlugin1636_1664 } from './fixtures/test-plugins/issue-1636-1664/issue-1636-1664-plugin';
import { TestCustomEntity, WithCustomEntity } from './fixtures/test-plugins/with-custom-entity';
import { AddItemToOrderMutationVariables } from './graphql/generated-e2e-shop-types';
import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
import { sortById } from './utils/test-order-utils';
Expand Down Expand Up @@ -107,7 +115,7 @@ const customConfig = mergeConfig(testConfig(), {
timezone: 'Z',
},
customFields: customFieldConfig,
plugins: [TestPlugin1636_1664],
plugins: [TestPlugin1636_1664, WithCustomEntity],
});

describe('Custom field relations', () => {
Expand Down Expand Up @@ -1180,4 +1188,54 @@ describe('Custom field relations', () => {
expect(updateCustomerAddress.customFields.single).toEqual(null);
expect(updateCustomerAddress.customFields.multi).toEqual([{ id: 'T_1' }]);
});

describe('bi-direction relations', () => {
let customEntityRepository: Repository<TestCustomEntity>;
let customEntity: TestCustomEntity;
let collectionIdInternal: number;

beforeAll(async () => {
customEntityRepository = server.app
.get(TransactionalConnection)
.getRepository(RequestContext.empty(), TestCustomEntity);

const customEntityId = (await customEntityRepository.save({})).id;

const { createCollection } = await adminClient.query(gql`
mutation {
createCollection(
input: {
translations: [
{ languageCode: en, name: "Test", description: "test", slug: "test" }
]
filters: []
customFields: { customEntityListIds: [${customEntityId}] customEntityId: ${customEntityId} }
}
) {
id
}
}
`);
collectionIdInternal = parseInt(createCollection.id.replace('T_', ''), 10);

customEntity = await assertFound(
customEntityRepository.findOne({
where: { id: customEntityId },
relations: ['customEntityListInverse', 'customEntityInverse'],
}),
);
});

it('can create inverse relation for list=false', () => {
expect(customEntity.customEntityInverse).toEqual([
expect.objectContaining({ id: collectionIdInternal }),
]);
});

it('can create inverse relation for list=true', () => {
expect(customEntity.customEntityListInverse).toEqual([
expect.objectContaining({ id: collectionIdInternal }),
]);
});
});
});
58 changes: 58 additions & 0 deletions packages/core/e2e/fixtures/test-plugins/with-custom-entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Collection, PluginCommonModule, VendureEntity, VendurePlugin } from '@vendure/core';
import gql from 'graphql-tag';
import { DeepPartial, Entity, ManyToMany, OneToMany } from 'typeorm';

@Entity()
export class TestCustomEntity extends VendureEntity {
constructor(input?: DeepPartial<TestCustomEntity>) {
super(input);
}

@ManyToMany(() => Collection, x => (x as any).customFields?.customEntityList, {
eager: true,
})
customEntityListInverse: Collection[];

@OneToMany(() => Collection, (a: Collection) => (a.customFields as any).customEntity)
customEntityInverse: Collection[];
}

@VendurePlugin({
imports: [PluginCommonModule],
entities: [TestCustomEntity],
adminApiExtensions: {
schema: gql`
type TestCustomEntity {
id: ID!
}
`,
},
configuration: config => {
config.customFields = {
...(config.customFields ?? {}),
Collection: [
...(config.customFields?.Collection ?? []),
{
name: 'customEntity',
type: 'relation',
entity: TestCustomEntity,
list: false,
public: false,
inverseSide: (t: TestCustomEntity) => t.customEntityInverse,
graphQLType: 'TestCustomEntity',
},
{
name: 'customEntityList',
type: 'relation',
entity: TestCustomEntity,
list: true,
public: false,
inverseSide: (t: TestCustomEntity) => t.customEntityListInverse,
graphQLType: 'TestCustomEntity',
},
],
};
return config;
},
})
export class WithCustomEntity {}
9 changes: 8 additions & 1 deletion packages/core/src/config/custom-field/custom-field-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ export type DateTimeCustomFieldConfig = TypedCustomFieldConfig<'datetime', Graph
export type RelationCustomFieldConfig = TypedCustomFieldConfig<
'relation',
Omit<GraphQLRelationCustomFieldConfig, 'entity' | 'scalarFields'>
> & { entity: Type<VendureEntity>; graphQLType?: string; eager?: boolean };
> & {
entity: Type<VendureEntity>;
graphQLType?: string;
eager?: boolean;
inverseSide: string | ((object: VendureEntity) => any);
};

/**
* @description
Expand Down Expand Up @@ -180,6 +185,8 @@ export type CustomFieldConfig =
*
* * `entity: VendureEntity`: The entity which this custom field is referencing
* * `eager?: boolean`: Whether to [eagerly load](https://typeorm.io/#/eager-and-lazy-relations) the relation. Defaults to false.
* * `inverseSide?: inverseSide: string | ((object: any) => any`: The inverse side for
* [bi-directional relations](https://typeorm.io/many-to-many-relations#bi-directional-relations)
* * `graphQLType?: string`: The name of the GraphQL type that corresponds to the entity.
* Can be omitted if it is the same, which is usually the case.
*
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/entity/register-custom-entity-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,14 @@ function registerCustomFieldsForEntity(
const registerColumn = () => {
if (customField.type === 'relation') {
if (customField.list) {
ManyToMany(type => customField.entity, { eager: customField.eager })(instance, name);
ManyToMany(type => customField.entity, customField.inverseSide, {
eager: customField.eager,
})(instance, name);
JoinTable()(instance, name);
} else {
ManyToOne(type => customField.entity, { eager: customField.eager })(instance, name);
ManyToOne(type => customField.entity, customField.inverseSide, {
eager: customField.eager,
})(instance, name);
JoinColumn()(instance, name);
}
} else {
Expand Down

0 comments on commit 0313ce5

Please sign in to comment.