graphql-custom-fields.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import { CustomFieldConfig, CustomFields, CustomFieldType } from '@vendure/common/lib/shared-types';
  2. import { assertNever } from '@vendure/common/lib/shared-utils';
  3. import { buildSchema, extendSchema, GraphQLArgument, GraphQLInputObjectType, GraphQLSchema, parse } from 'graphql';
  4. /**
  5. * Given a CustomFields config object, generates an SDL string extending the built-in
  6. * types with a customFields property for all entities, translations and inputs for which
  7. * custom fields are defined.
  8. */
  9. export function addGraphQLCustomFields(
  10. typeDefsOrSchema: string | GraphQLSchema,
  11. customFieldConfig: CustomFields,
  12. ): GraphQLSchema {
  13. const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
  14. let customFieldTypeDefs = '';
  15. if (!schema.getType('JSON')) {
  16. customFieldTypeDefs += `
  17. scalar JSON
  18. `;
  19. }
  20. for (const entityName of Object.keys(customFieldConfig)) {
  21. const customEntityFields = customFieldConfig[entityName as keyof CustomFields] || [];
  22. const localeStringFields = customEntityFields.filter(field => field.type === 'localeString');
  23. const nonLocaleStringFields = customEntityFields.filter(field => field.type !== 'localeString');
  24. if (schema.getType(entityName)) {
  25. if (customEntityFields.length) {
  26. customFieldTypeDefs += `
  27. type ${entityName}CustomFields {
  28. ${mapToFields(customEntityFields, getGraphQlType)}
  29. }
  30. extend type ${entityName} {
  31. customFields: ${entityName}CustomFields
  32. }
  33. `;
  34. } else {
  35. customFieldTypeDefs += `
  36. extend type ${entityName} {
  37. customFields: JSON
  38. }
  39. `;
  40. }
  41. }
  42. if (localeStringFields.length && schema.getType(`${entityName}Translation`)) {
  43. customFieldTypeDefs += `
  44. type ${entityName}TranslationCustomFields {
  45. ${mapToFields(localeStringFields, getGraphQlType)}
  46. }
  47. extend type ${entityName}Translation {
  48. customFields: ${entityName}TranslationCustomFields
  49. }
  50. `;
  51. }
  52. if (schema.getType(`Create${entityName}Input`)) {
  53. if (nonLocaleStringFields.length) {
  54. customFieldTypeDefs += `
  55. input Create${entityName}CustomFieldsInput {
  56. ${mapToFields(nonLocaleStringFields, getGraphQlType)}
  57. }
  58. extend input Create${entityName}Input {
  59. customFields: Create${entityName}CustomFieldsInput
  60. }
  61. `;
  62. } else {
  63. customFieldTypeDefs += `
  64. extend input Create${entityName}Input {
  65. customFields: JSON
  66. }
  67. `;
  68. }
  69. }
  70. if (schema.getType(`Update${entityName}Input`)) {
  71. if (nonLocaleStringFields.length) {
  72. customFieldTypeDefs += `
  73. input Update${entityName}CustomFieldsInput {
  74. ${mapToFields(nonLocaleStringFields, getGraphQlType)}
  75. }
  76. extend input Update${entityName}Input {
  77. customFields: Update${entityName}CustomFieldsInput
  78. }
  79. `;
  80. } else {
  81. customFieldTypeDefs += `
  82. extend input Update${entityName}Input {
  83. customFields: JSON
  84. }
  85. `;
  86. }
  87. }
  88. if (customEntityFields.length && schema.getType(`${entityName}SortParameter`)) {
  89. customFieldTypeDefs += `
  90. extend input ${entityName}SortParameter {
  91. ${mapToFields(customEntityFields, () => 'SortOrder')}
  92. }
  93. `;
  94. }
  95. if (customEntityFields.length && schema.getType(`${entityName}FilterParameter`)) {
  96. customFieldTypeDefs += `
  97. extend input ${entityName}FilterParameter {
  98. ${mapToFields(customEntityFields, getFilterOperator)}
  99. }
  100. `;
  101. }
  102. if (localeStringFields && schema.getType(`${entityName}TranslationInput`)) {
  103. if (localeStringFields.length) {
  104. customFieldTypeDefs += `
  105. input ${entityName}TranslationCustomFieldsInput {
  106. ${mapToFields(localeStringFields, getGraphQlType)}
  107. }
  108. extend input ${entityName}TranslationInput {
  109. customFields: ${entityName}TranslationCustomFieldsInput
  110. }
  111. `;
  112. } else {
  113. customFieldTypeDefs += `
  114. extend input ${entityName}TranslationInput {
  115. customFields: JSON
  116. }
  117. `;
  118. }
  119. }
  120. }
  121. return extendSchema(schema, parse(customFieldTypeDefs));
  122. }
  123. /**
  124. * If CustomFields are defined on the OrderLine entity, then an extra `customFields` argument
  125. * must be added to the `addItemToOrder` and `adjustOrderLine` mutations.
  126. */
  127. export function addOrderLineCustomFieldsInput(typeDefsOrSchema: string | GraphQLSchema, orderLineCustomFields: CustomFieldConfig[]): GraphQLSchema {
  128. const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
  129. if (!orderLineCustomFields || orderLineCustomFields.length === 0) {
  130. return schema;
  131. }
  132. const schemaConfig = schema.toConfig();
  133. const mutationType = schemaConfig.mutation;
  134. if (!mutationType) {
  135. return schema;
  136. }
  137. const addItemToOrderMutation = mutationType.getFields().addItemToOrder;
  138. const adjustOrderLineMutation = mutationType.getFields().adjustOrderLine;
  139. if (!addItemToOrderMutation || !adjustOrderLineMutation) {
  140. return schema;
  141. }
  142. const input = new GraphQLInputObjectType({
  143. name: 'OrderLineCustomFieldsInput',
  144. fields: orderLineCustomFields.reduce((fields, field) => {
  145. return { ...fields, [field.name]: { type: schema.getType(getGraphQlType(field.type)) } };
  146. }, {}),
  147. });
  148. schemaConfig.types.push(input);
  149. addItemToOrderMutation.args.push({
  150. name: 'customFields',
  151. type: input,
  152. });
  153. adjustOrderLineMutation.args.push({
  154. name: 'customFields',
  155. type: input,
  156. });
  157. return new GraphQLSchema(schemaConfig);
  158. }
  159. type GraphQLFieldType = 'String' | 'Int' | 'Float' | 'Boolean' | 'ID';
  160. /**
  161. * Maps an array of CustomFieldConfig objects into a string of SDL fields.
  162. */
  163. function mapToFields(fieldDefs: CustomFieldConfig[], typeFn: (fieldType: CustomFieldType) => string): string {
  164. return fieldDefs.map(field => `${field.name}: ${typeFn(field.type)}`).join('\n');
  165. }
  166. function getFilterOperator(type: CustomFieldType): string {
  167. switch (type) {
  168. case 'datetime':
  169. return 'DateOperators';
  170. case 'string':
  171. case 'localeString':
  172. return 'StringOperators';
  173. case 'boolean':
  174. return 'BooleanOperators';
  175. case 'int':
  176. case 'float':
  177. return 'NumberOperators';
  178. default:
  179. assertNever(type);
  180. }
  181. return 'String';
  182. }
  183. function getGraphQlType(type: CustomFieldType): GraphQLFieldType {
  184. switch (type) {
  185. case 'string':
  186. case 'datetime':
  187. case 'localeString':
  188. return 'String';
  189. case 'boolean':
  190. return 'Boolean';
  191. case 'int':
  192. return 'Int';
  193. case 'float':
  194. return 'Float';
  195. default:
  196. assertNever(type);
  197. }
  198. return 'String';
  199. }