remove-custom-fields-with-eager-relations.ts 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import { SelectQueryBuilder } from 'typeorm';
  2. import { Logger } from '../config/logger/vendure-logger';
  3. /**
  4. * This is a work-around for this issue: https://github.com/vendure-ecommerce/vendure/issues/1664
  5. *
  6. * Explanation:
  7. * When calling `FindOptionsUtils.joinEagerRelations()`, there appears to be a bug in TypeORM whereby
  8. * it will throw the following error *if* the `options.relations` array contains any customField relations
  9. * where the related entity itself has eagerly-loaded relations.
  10. *
  11. * For example, let's say we define a custom field on the Product entity like this:
  12. * ```
  13. * Product: [{
  14. * name: 'featuredFacet',
  15. * type: 'relation',
  16. * entity: Facet,
  17. * }],
  18. * ```
  19. * and then we pass into `TransactionalConnection.findOneInChannel()` an options array of:
  20. *
  21. * ```
  22. * { relations: ['customFields.featuredFacet'] }
  23. * ```
  24. * it will throw an error because the `Facet` entity itself has eager relations (namely the `translations` property).
  25. * This will cause TypeORM to throw the error:
  26. * ```
  27. * TypeORMError: "entity__customFields" alias was not found. Maybe you forgot to join it?
  28. * ```
  29. *
  30. * So this method introspects the QueryBuilder metadata and checks for any custom field relations which
  31. * themselves have eager relations. If found, it removes those items from the `options.relations` array.
  32. *
  33. * TODO: Ideally create a minimal reproduction case and report in the TypeORM repo for an upstream fix.
  34. */
  35. export function removeCustomFieldsWithEagerRelations<T extends string>(
  36. qb: SelectQueryBuilder<any>,
  37. relations: T[] = [],
  38. ): T[] {
  39. let resultingRelations = relations;
  40. const mainAlias = qb.expressionMap.mainAlias;
  41. const customFieldsMetadata = mainAlias?.metadata.embeddeds.find(
  42. metadata => metadata.propertyName === 'customFields',
  43. );
  44. if (customFieldsMetadata) {
  45. const customFieldRelationsWithEagerRelations = customFieldsMetadata.relations.filter(relation => {
  46. return (
  47. !!relation.inverseEntityMetadata.ownRelations.find(or => or.isEager === true) ||
  48. relation.inverseEntityMetadata.embeddeds.find(
  49. em => em.propertyName === 'customFields' && em.relations.find(emr => emr.isEager),
  50. )
  51. );
  52. });
  53. for (const relation of customFieldRelationsWithEagerRelations) {
  54. const propertyName = relation.propertyName;
  55. const relationsToRemove = relations.filter(r => r.startsWith(`customFields.${propertyName}`));
  56. if (relationsToRemove.length) {
  57. Logger.debug(
  58. `TransactionalConnection.findOneInChannel cannot automatically join relation [${
  59. mainAlias?.metadata.name ?? '(unknown)'
  60. }.customFields.${propertyName}]`,
  61. );
  62. resultingRelations = relations.filter(r => !r.startsWith(`customFields.${propertyName}`));
  63. }
  64. }
  65. }
  66. return resultingRelations;
  67. }