graphql-custom-fields.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. import { assertNever, getGraphQlInputName } from '@vendure/common/lib/shared-utils';
  2. import {
  3. buildSchema,
  4. extendSchema,
  5. GraphQLInputObjectType,
  6. GraphQLList,
  7. GraphQLSchema,
  8. isObjectType,
  9. parse,
  10. } from 'graphql';
  11. import {
  12. CustomFieldConfig,
  13. CustomFields,
  14. StructCustomFieldConfig,
  15. RelationCustomFieldConfig,
  16. StructFieldConfig,
  17. } from '../../config/custom-field/custom-field-types';
  18. import { Logger } from '../../config/logger/vendure-logger';
  19. import { getCustomFieldsConfigWithoutInterfaces } from './get-custom-fields-config-without-interfaces';
  20. /**
  21. * Given a CustomFields config object, generates an SDL string extending the built-in
  22. * types with a customFields property for all entities, translations and inputs for which
  23. * custom fields are defined.
  24. */
  25. export function addGraphQLCustomFields(
  26. typeDefsOrSchema: string | GraphQLSchema,
  27. customFieldConfig: CustomFields,
  28. publicOnly: boolean,
  29. ): GraphQLSchema {
  30. const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
  31. let customFieldTypeDefs = '';
  32. if (!schema.getType('JSON')) {
  33. customFieldTypeDefs += `
  34. scalar JSON
  35. `;
  36. }
  37. if (!schema.getType('DateTime')) {
  38. customFieldTypeDefs += `
  39. scalar DateTime
  40. `;
  41. }
  42. const customFieldsConfig = getCustomFieldsConfigWithoutInterfaces(customFieldConfig, schema);
  43. for (const [entityName, customFields] of customFieldsConfig) {
  44. const gqlType = schema.getType(entityName);
  45. if (isObjectType(gqlType) && gqlType.getFields().customFields) {
  46. Logger.warn(
  47. `The entity type "${entityName}" already has a "customFields" field defined. Skipping automatic custom field extension.`,
  48. );
  49. continue;
  50. }
  51. const customEntityFields = customFields.filter(config => {
  52. return !config.internal && (publicOnly === true ? config.public !== false : true);
  53. });
  54. for (const fieldDef of customEntityFields) {
  55. if (fieldDef.type === 'relation') {
  56. const graphQlTypeName = fieldDef.graphQLType || fieldDef.entity.name;
  57. if (!schema.getType(graphQlTypeName)) {
  58. const customFieldPath = `${entityName}.${fieldDef.name}`;
  59. const errorMessage = `The GraphQL type "${
  60. graphQlTypeName ?? '(unknown)'
  61. }" specified by the ${customFieldPath} custom field does not exist in the ${publicOnly ? 'Shop API' : 'Admin API'} schema.`;
  62. Logger.warn(errorMessage);
  63. if (publicOnly) {
  64. Logger.warn(
  65. [
  66. `This can be resolved by either:`,
  67. ` - setting \`public: false\` in the ${customFieldPath} custom field config`,
  68. ` - defining the "${graphQlTypeName}" type in the Shop API schema`,
  69. ].join('\n'),
  70. );
  71. }
  72. throw new Error(errorMessage);
  73. }
  74. }
  75. }
  76. const localizedFields = customEntityFields.filter(
  77. field => field.type === 'localeString' || field.type === 'localeText',
  78. );
  79. const nonLocalizedFields = customEntityFields.filter(
  80. field => field.type !== 'localeString' && field.type !== 'localeText',
  81. );
  82. const writeableLocalizedFields = localizedFields.filter(field => !field.readonly);
  83. const writeableNonLocalizedFields = nonLocalizedFields.filter(field => !field.readonly);
  84. const sortableFields = customEntityFields.filter(
  85. field => field.list !== true && field.type !== 'struct',
  86. );
  87. const filterableFields = customEntityFields.filter(
  88. field => field.type !== 'relation' && field.type !== 'struct',
  89. );
  90. const structCustomFields = customEntityFields.filter(
  91. (f): f is StructCustomFieldConfig => f.type === 'struct',
  92. );
  93. if (schema.getType(entityName)) {
  94. if (customEntityFields.length) {
  95. for (const structCustomField of structCustomFields) {
  96. customFieldTypeDefs += `
  97. type ${getStructTypeName(entityName, structCustomField)} {
  98. ${mapToStructFields(structCustomField.fields, wrapListType(getGraphQlTypeForStructField))}
  99. }
  100. `;
  101. }
  102. customFieldTypeDefs += `
  103. type ${entityName}CustomFields {
  104. ${mapToFields(customEntityFields, wrapListType(getGraphQlType(entityName)))}
  105. }
  106. extend type ${entityName} {
  107. customFields: ${entityName}CustomFields
  108. }
  109. `;
  110. } else {
  111. customFieldTypeDefs += `
  112. extend type ${entityName} {
  113. customFields: JSON
  114. }
  115. `;
  116. }
  117. }
  118. if (localizedFields.length && schema.getType(`${entityName}Translation`)) {
  119. customFieldTypeDefs += `
  120. type ${entityName}TranslationCustomFields {
  121. ${mapToFields(localizedFields, wrapListType(getGraphQlType(entityName)))}
  122. }
  123. extend type ${entityName}Translation {
  124. customFields: ${entityName}TranslationCustomFields
  125. }
  126. `;
  127. }
  128. const hasCreateInputType = schema.getType(`Create${entityName}Input`);
  129. const hasUpdateInputType = schema.getType(`Update${entityName}Input`);
  130. if ((hasCreateInputType || hasUpdateInputType) && writeableNonLocalizedFields.length) {
  131. // Define any Struct input types that are required by
  132. // the create and/or update input types.
  133. for (const structCustomField of structCustomFields) {
  134. customFieldTypeDefs += `
  135. input ${getStructInputName(entityName, structCustomField)} {
  136. ${mapToStructFields(structCustomField.fields, wrapListType(getGraphQlInputType(entityName)))}
  137. }
  138. `;
  139. }
  140. }
  141. if (hasCreateInputType) {
  142. if (writeableNonLocalizedFields.length) {
  143. customFieldTypeDefs += `
  144. input Create${entityName}CustomFieldsInput {
  145. ${mapToFields(
  146. writeableNonLocalizedFields,
  147. wrapListType(getGraphQlInputType(entityName)),
  148. getGraphQlInputName,
  149. )}
  150. }
  151. extend input Create${entityName}Input {
  152. customFields: Create${entityName}CustomFieldsInput
  153. }
  154. `;
  155. } else {
  156. customFieldTypeDefs += `
  157. extend input Create${entityName}Input {
  158. customFields: JSON
  159. }
  160. `;
  161. }
  162. }
  163. if (hasUpdateInputType) {
  164. if (writeableNonLocalizedFields.length) {
  165. customFieldTypeDefs += `
  166. input Update${entityName}CustomFieldsInput {
  167. ${mapToFields(
  168. writeableNonLocalizedFields,
  169. wrapListType(getGraphQlInputType(entityName)),
  170. getGraphQlInputName,
  171. )}
  172. }
  173. extend input Update${entityName}Input {
  174. customFields: Update${entityName}CustomFieldsInput
  175. }
  176. `;
  177. } else {
  178. customFieldTypeDefs += `
  179. extend input Update${entityName}Input {
  180. customFields: JSON
  181. }
  182. `;
  183. }
  184. }
  185. if (sortableFields.length && schema.getType(`${entityName}SortParameter`)) {
  186. // Sorting list fields makes no sense, so we only add "sort" fields
  187. // to non-list fields.
  188. customFieldTypeDefs += `
  189. extend input ${entityName}SortParameter {
  190. ${mapToFields(sortableFields, () => 'SortOrder')}
  191. }
  192. `;
  193. }
  194. if (filterableFields.length && schema.getType(`${entityName}FilterParameter`)) {
  195. customFieldTypeDefs += `
  196. extend input ${entityName}FilterParameter {
  197. ${mapToFields(filterableFields, getFilterOperator)}
  198. }
  199. `;
  200. }
  201. if (writeableLocalizedFields) {
  202. const translationInputs = [
  203. `${entityName}TranslationInput`,
  204. `Create${entityName}TranslationInput`,
  205. `Update${entityName}TranslationInput`,
  206. ];
  207. for (const inputName of translationInputs) {
  208. if (schema.getType(inputName)) {
  209. if (writeableLocalizedFields.length) {
  210. customFieldTypeDefs += `
  211. input ${inputName}CustomFields {
  212. ${mapToFields(writeableLocalizedFields, wrapListType(getGraphQlType(entityName)))}
  213. }
  214. extend input ${inputName} {
  215. customFields: ${inputName}CustomFields
  216. }
  217. `;
  218. } else {
  219. customFieldTypeDefs += `
  220. extend input ${inputName} {
  221. customFields: JSON
  222. }
  223. `;
  224. }
  225. }
  226. }
  227. }
  228. }
  229. const publicAddressFields = customFieldConfig.Address?.filter(
  230. config => !config.internal && (publicOnly === true ? config.public !== false : true),
  231. );
  232. const writeablePublicAddressFields = publicAddressFields?.filter(field => !field.readonly);
  233. if (publicAddressFields?.length) {
  234. // For custom fields on the Address entity, we also extend the OrderAddress
  235. // type (which is used to store address snapshots on Orders)
  236. if (schema.getType('OrderAddress')) {
  237. customFieldTypeDefs += `
  238. extend type OrderAddress {
  239. customFields: AddressCustomFields
  240. }
  241. `;
  242. }
  243. if (schema.getType('UpdateOrderAddressInput') && writeablePublicAddressFields?.length) {
  244. customFieldTypeDefs += `
  245. extend input UpdateOrderAddressInput {
  246. customFields: UpdateAddressCustomFieldsInput
  247. }
  248. `;
  249. }
  250. } else {
  251. if (schema.getType('OrderAddress')) {
  252. customFieldTypeDefs += `
  253. extend type OrderAddress {
  254. customFields: JSON
  255. }
  256. `;
  257. }
  258. }
  259. return extendSchema(schema, parse(customFieldTypeDefs));
  260. }
  261. export function addServerConfigCustomFields(
  262. typeDefsOrSchema: string | GraphQLSchema,
  263. customFieldConfig: CustomFields,
  264. ) {
  265. const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
  266. const customFieldTypeDefs = `
  267. """
  268. This type is deprecated in v2.2 in favor of the EntityCustomFields type,
  269. which allows custom fields to be defined on user-supplied entities.
  270. """
  271. type CustomFields {
  272. ${Object.keys(customFieldConfig).reduce(
  273. (output, name) => output + name + ': [CustomFieldConfig!]!\n',
  274. '',
  275. )}
  276. }
  277. type EntityCustomFields {
  278. entityName: String!
  279. customFields: [CustomFieldConfig!]!
  280. }
  281. extend type ServerConfig {
  282. """
  283. This field is deprecated in v2.2 in favor of the entityCustomFields field,
  284. which allows custom fields to be defined on user-supplies entities.
  285. """
  286. customFieldConfig: CustomFields!
  287. entityCustomFields: [EntityCustomFields!]!
  288. }
  289. `;
  290. return extendSchema(schema, parse(customFieldTypeDefs));
  291. }
  292. export function addActiveAdministratorCustomFields(
  293. typeDefsOrSchema: string | GraphQLSchema,
  294. administratorCustomFields: CustomFieldConfig[],
  295. ) {
  296. const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
  297. const writableCustomFields = administratorCustomFields?.filter(
  298. field => field.readonly !== true && field.internal !== true,
  299. );
  300. const extension = `
  301. extend input UpdateActiveAdministratorInput {
  302. customFields: ${
  303. 0 < writableCustomFields?.length ? 'UpdateAdministratorCustomFieldsInput' : 'JSON'
  304. }
  305. }
  306. `;
  307. return extendSchema(schema, parse(extension));
  308. }
  309. /**
  310. * If CustomFields are defined on the Customer entity, then an extra `customFields` field is added to
  311. * the `RegisterCustomerInput` so that public writable custom fields can be set when a new customer
  312. * is registered.
  313. */
  314. export function addRegisterCustomerCustomFieldsInput(
  315. typeDefsOrSchema: string | GraphQLSchema,
  316. customerCustomFields: CustomFieldConfig[],
  317. ): GraphQLSchema {
  318. const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
  319. if (!customerCustomFields || customerCustomFields.length === 0) {
  320. return schema;
  321. }
  322. const publicWritableCustomFields = customerCustomFields.filter(fieldDef => {
  323. return fieldDef.public !== false && !fieldDef.readonly && !fieldDef.internal;
  324. });
  325. if (publicWritableCustomFields.length < 1) {
  326. return schema;
  327. }
  328. const customFieldTypeDefs = `
  329. input RegisterCustomerCustomFieldsInput {
  330. ${mapToFields(publicWritableCustomFields, wrapListType(getGraphQlInputType('Customer')), getGraphQlInputName)}
  331. }
  332. extend input RegisterCustomerInput {
  333. customFields: RegisterCustomerCustomFieldsInput
  334. }
  335. `;
  336. return extendSchema(schema, parse(customFieldTypeDefs));
  337. }
  338. /**
  339. * If CustomFields are defined on the Order entity, we add a `customFields` field to the ModifyOrderInput
  340. * type.
  341. */
  342. export function addModifyOrderCustomFields(
  343. typeDefsOrSchema: string | GraphQLSchema,
  344. orderCustomFields: CustomFieldConfig[],
  345. ): GraphQLSchema {
  346. const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
  347. if (!orderCustomFields || orderCustomFields.length === 0) {
  348. return schema;
  349. }
  350. if (schema.getType('ModifyOrderInput') && schema.getType('UpdateOrderCustomFieldsInput')) {
  351. const customFieldTypeDefs = `
  352. extend input ModifyOrderInput {
  353. customFields: UpdateOrderCustomFieldsInput
  354. }
  355. `;
  356. return extendSchema(schema, parse(customFieldTypeDefs));
  357. }
  358. return schema;
  359. }
  360. /**
  361. * If CustomFields are defined on the OrderLine entity, then an extra `customFields` argument
  362. * must be added to the `addItemToOrder` and `adjustOrderLine` mutations, as well as the related
  363. * fields in the `ModifyOrderInput` type.
  364. */
  365. export function addOrderLineCustomFieldsInput(
  366. typeDefsOrSchema: string | GraphQLSchema,
  367. orderLineCustomFields: CustomFieldConfig[],
  368. publicOnly: boolean,
  369. ): GraphQLSchema {
  370. const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
  371. orderLineCustomFields = orderLineCustomFields.filter(f => f.internal !== true);
  372. const publicCustomFields = orderLineCustomFields.filter(f => f.public !== false);
  373. const customFields = publicOnly ? publicCustomFields : orderLineCustomFields;
  374. if (!customFields || customFields.length === 0) {
  375. return schema;
  376. }
  377. const schemaConfig = schema.toConfig();
  378. const mutationType = schemaConfig.mutation;
  379. if (!mutationType) {
  380. return schema;
  381. }
  382. const structFields = orderLineCustomFields.filter(
  383. (f): f is StructCustomFieldConfig => f.type === 'struct',
  384. );
  385. const structInputTypes: GraphQLInputObjectType[] = [];
  386. if (0 < structFields.length) {
  387. for (const structField of structFields) {
  388. const structInputName = getStructInputName('OrderLine', structField);
  389. structInputTypes.push(
  390. new GraphQLInputObjectType({
  391. name: structInputName,
  392. fields: structField.fields.reduce((fields, field) => {
  393. const name = getGraphQlInputName(field);
  394. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  395. const primitiveType = schema.getType(getGraphQlInputType('OrderLine')(field))!;
  396. const type = field.list === true ? new GraphQLList(primitiveType) : primitiveType;
  397. return { ...fields, [name]: { type } };
  398. }, {}),
  399. }),
  400. );
  401. }
  402. }
  403. const input = new GraphQLInputObjectType({
  404. name: 'OrderLineCustomFieldsInput',
  405. fields: customFields.reduce((fields, field) => {
  406. const name = getGraphQlInputName(field);
  407. const inputTypeName = getGraphQlInputType('OrderLine')(field);
  408. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  409. const inputType =
  410. schema.getType(getGraphQlInputType('OrderLine')(field)) ??
  411. structInputTypes.find(t => t.name === inputTypeName);
  412. if (!inputType) {
  413. throw new Error(`Could not find input type for field ${field.name}`);
  414. }
  415. const type = field.list === true ? new GraphQLList(inputType) : inputType;
  416. return { ...fields, [name]: { type } };
  417. }, {}),
  418. });
  419. schemaConfig.types = [...schemaConfig.types, ...structInputTypes, input];
  420. const addItemToOrderMutation = mutationType.getFields().addItemToOrder;
  421. const adjustOrderLineMutation = mutationType.getFields().adjustOrderLine;
  422. if (addItemToOrderMutation) {
  423. addItemToOrderMutation.args = [
  424. ...addItemToOrderMutation.args,
  425. {
  426. name: 'customFields',
  427. type: input,
  428. description: null,
  429. defaultValue: null,
  430. extensions: {},
  431. astNode: null,
  432. deprecationReason: null,
  433. },
  434. ];
  435. }
  436. if (adjustOrderLineMutation) {
  437. adjustOrderLineMutation.args = [
  438. ...adjustOrderLineMutation.args,
  439. {
  440. name: 'customFields',
  441. type: input,
  442. description: null,
  443. defaultValue: null,
  444. extensions: {},
  445. astNode: null,
  446. deprecationReason: null,
  447. },
  448. ];
  449. }
  450. let extendedSchema = new GraphQLSchema(schemaConfig);
  451. if (schema.getType('AddItemInput')) {
  452. const customFieldTypeDefs = `
  453. extend input AddItemInput {
  454. customFields: OrderLineCustomFieldsInput
  455. }
  456. `;
  457. extendedSchema = extendSchema(extendedSchema, parse(customFieldTypeDefs));
  458. }
  459. if (schema.getType('OrderLineInput')) {
  460. const customFieldTypeDefs = `
  461. extend input OrderLineInput {
  462. customFields: OrderLineCustomFieldsInput
  463. }
  464. `;
  465. extendedSchema = extendSchema(extendedSchema, parse(customFieldTypeDefs));
  466. }
  467. if (schema.getType('AddItemToDraftOrderInput')) {
  468. const customFieldTypeDefs = `
  469. extend input AddItemToDraftOrderInput {
  470. customFields: OrderLineCustomFieldsInput
  471. }
  472. `;
  473. extendedSchema = extendSchema(extendedSchema, parse(customFieldTypeDefs));
  474. }
  475. if (schema.getType('AdjustDraftOrderLineInput')) {
  476. const customFieldTypeDefs = `
  477. extend input AdjustDraftOrderLineInput {
  478. customFields: OrderLineCustomFieldsInput
  479. }
  480. `;
  481. extendedSchema = extendSchema(extendedSchema, parse(customFieldTypeDefs));
  482. }
  483. return extendedSchema;
  484. }
  485. export function addShippingMethodQuoteCustomFields(
  486. typeDefsOrSchema: string | GraphQLSchema,
  487. shippingMethodCustomFields: CustomFieldConfig[],
  488. ) {
  489. const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
  490. let customFieldTypeDefs = '';
  491. const publicCustomFields = shippingMethodCustomFields.filter(f => f.public !== false);
  492. if (0 < publicCustomFields.length) {
  493. customFieldTypeDefs = `
  494. extend type ShippingMethodQuote {
  495. customFields: ShippingMethodCustomFields
  496. }
  497. `;
  498. } else {
  499. customFieldTypeDefs = `
  500. extend type ShippingMethodQuote {
  501. customFields: JSON
  502. }
  503. `;
  504. }
  505. return extendSchema(schema, parse(customFieldTypeDefs));
  506. }
  507. export function addPaymentMethodQuoteCustomFields(
  508. typeDefsOrSchema: string | GraphQLSchema,
  509. paymentMethodCustomFields: CustomFieldConfig[],
  510. ) {
  511. const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
  512. let customFieldTypeDefs = '';
  513. const publicCustomFields = paymentMethodCustomFields.filter(f => f.public !== false);
  514. if (0 < publicCustomFields.length) {
  515. customFieldTypeDefs = `
  516. extend type PaymentMethodQuote {
  517. customFields: PaymentMethodCustomFields
  518. }
  519. `;
  520. } else {
  521. customFieldTypeDefs = `
  522. extend type PaymentMethodQuote {
  523. customFields: JSON
  524. }
  525. `;
  526. }
  527. return extendSchema(schema, parse(customFieldTypeDefs));
  528. }
  529. /**
  530. * Maps an array of CustomFieldConfig objects into a string of SDL fields.
  531. */
  532. function mapToFields(
  533. fieldDefs: CustomFieldConfig[],
  534. typeFn: (def: CustomFieldConfig) => string | undefined,
  535. nameFn?: (def: Pick<CustomFieldConfig, 'name' | 'type' | 'list'>) => string,
  536. ): string {
  537. const res = fieldDefs
  538. .map(field => {
  539. const type = typeFn(field);
  540. if (!type) {
  541. return;
  542. }
  543. const name = nameFn ? nameFn(field) : field.name;
  544. return `${name}: ${type}`;
  545. })
  546. .filter(x => x != null);
  547. return res.join('\n');
  548. }
  549. /**
  550. * Maps an array of CustomFieldConfig objects into a string of SDL fields.
  551. */
  552. function mapToStructFields(
  553. fieldDefs: StructFieldConfig[],
  554. typeFn: (def: StructFieldConfig) => string | undefined,
  555. nameFn?: (def: Pick<StructFieldConfig, 'name' | 'type' | 'list'>) => string,
  556. ): string {
  557. const res = fieldDefs
  558. .map(field => {
  559. const type = typeFn(field);
  560. if (!type) {
  561. return;
  562. }
  563. const name = nameFn ? nameFn(field) : field.name;
  564. return `${name}: ${type}`;
  565. })
  566. .filter(x => x != null);
  567. return res.join('\n');
  568. }
  569. function getFilterOperator(config: CustomFieldConfig): string | undefined {
  570. switch (config.type) {
  571. case 'datetime':
  572. return config.list ? 'DateListOperators' : 'DateOperators';
  573. case 'string':
  574. case 'localeString':
  575. case 'text':
  576. case 'localeText':
  577. return config.list ? 'StringListOperators' : 'StringOperators';
  578. case 'boolean':
  579. return config.list ? 'BooleanListOperators' : 'BooleanOperators';
  580. case 'int':
  581. case 'float':
  582. return config.list ? 'NumberListOperators' : 'NumberOperators';
  583. case 'relation':
  584. case 'struct':
  585. return undefined;
  586. default:
  587. assertNever(config);
  588. }
  589. return 'String';
  590. }
  591. function getGraphQlInputType(entityName: string) {
  592. return (config: CustomFieldConfig): string => {
  593. switch (config.type) {
  594. case 'relation':
  595. return 'ID';
  596. case 'struct':
  597. return getStructInputName(entityName, config);
  598. default:
  599. return getGraphQlType(entityName)(config);
  600. }
  601. };
  602. }
  603. function wrapListType<T extends CustomFieldConfig | StructFieldConfig>(
  604. getTypeFn: (def: T) => string | undefined,
  605. ): (def: T) => string | undefined {
  606. return (def: T) => {
  607. const type = getTypeFn(def);
  608. if (!type) {
  609. return;
  610. }
  611. return def.list ? `[${type}!]` : type;
  612. };
  613. }
  614. function getGraphQlType(entityName: string) {
  615. return (config: CustomFieldConfig): string => {
  616. switch (config.type) {
  617. case 'string':
  618. case 'localeString':
  619. case 'text':
  620. case 'localeText':
  621. return 'String';
  622. case 'datetime':
  623. return 'DateTime';
  624. case 'boolean':
  625. return 'Boolean';
  626. case 'int':
  627. return 'Int';
  628. case 'float':
  629. return 'Float';
  630. case 'relation':
  631. return config.graphQLType || config.entity.name;
  632. case 'struct':
  633. return getStructTypeName(entityName, config);
  634. default:
  635. assertNever(config);
  636. }
  637. return 'String';
  638. };
  639. }
  640. function getGraphQlTypeForStructField(config: StructFieldConfig): string {
  641. switch (config.type) {
  642. case 'string':
  643. case 'text':
  644. return 'String';
  645. case 'datetime':
  646. return 'DateTime';
  647. case 'boolean':
  648. return 'Boolean';
  649. case 'int':
  650. return 'Int';
  651. case 'float':
  652. return 'Float';
  653. default:
  654. assertNever(config);
  655. }
  656. return 'String';
  657. }
  658. function getStructTypeName(entityName: string, fieldDef: StructCustomFieldConfig): string {
  659. return `${entityName}${pascalCase(fieldDef.name)}Struct`;
  660. }
  661. function getStructInputName(entityName: string, fieldDef: StructCustomFieldConfig): string {
  662. return `${entityName}${pascalCase(fieldDef.name)}StructInput`;
  663. }
  664. function pascalCase(input: string) {
  665. return input.charAt(0).toUpperCase() + input.slice(1);
  666. }