create-upload-post-data.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { DocumentNode, Kind, OperationDefinitionNode, print } from 'graphql';
  2. export interface UploadPostData<V = any> {
  3. /**
  4. * Data from a GraphQL document that takes the Upload type as input
  5. */
  6. operations: {
  7. operationName: string;
  8. variables: V;
  9. query: string;
  10. };
  11. /**
  12. * A map from index values to variable paths. Maps files in the `filePaths`
  13. * array to fields with the Upload type in the GraphQL mutation input.
  14. *
  15. * If this was the GraphQL mutation input type:
  16. * ```graphql
  17. * input ImageReceivingInput {
  18. * bannerImage: Upload!
  19. * logo: Upload!
  20. * }
  21. * ```
  22. *
  23. * And this was the GraphQL mutation:
  24. * ```graphql
  25. * addSellerImages(input: ImageReceivingInput!): Seller
  26. * ```
  27. *
  28. * Then this would be the value for `map`:
  29. * ```js
  30. * {
  31. * 0: 'variables.input.bannerImage',
  32. * 1: 'variables.input.logo'
  33. * }
  34. * ```
  35. */
  36. map: {
  37. [index: number]: string;
  38. };
  39. /**
  40. * Array of file paths. Mapped to a GraphQL mutation input variable by
  41. * `map`.
  42. */
  43. filePaths: Array<{
  44. /**
  45. * Index of the file path as a string.
  46. */
  47. name: string;
  48. /**
  49. * The actual file path
  50. */
  51. file: string;
  52. }>;
  53. }
  54. /**
  55. * Creates a data structure which can be used to make a POST request to upload
  56. * files to a mutation using the Upload type.
  57. *
  58. * @param mutation - The GraphQL document for a mutation that takes an Upload
  59. * type as an input
  60. * @param filePaths - Either a single path or an array of paths to the files
  61. * that should be uploaded
  62. * @param mapVariables - A function that will receive `filePaths` and return an
  63. * object containing the input variables for the mutation, where every field
  64. * with the Upload type has the value `null`.
  65. * @returns an UploadPostData object.
  66. */
  67. export function createUploadPostData<P extends string[] | string, V>(
  68. mutation: DocumentNode,
  69. filePaths: P,
  70. mapVariables: (filePaths: P) => V,
  71. ): UploadPostData<V> {
  72. const operationDef = mutation.definitions.find(
  73. d => d.kind === Kind.OPERATION_DEFINITION,
  74. ) as OperationDefinitionNode;
  75. const filePathsArray: string[] = Array.isArray(filePaths) ? filePaths : [filePaths];
  76. const variables = mapVariables(filePaths);
  77. const postData: UploadPostData = {
  78. operations: {
  79. operationName: operationDef.name ? operationDef.name.value : 'AnonymousMutation',
  80. variables,
  81. query: print(mutation),
  82. },
  83. map: objectPath(variables).reduce((acc, path, i) => ({ ...acc, [i.toString()]: path }), {}),
  84. filePaths: filePathsArray.map((filePath, i) => ({
  85. name: i.toString(),
  86. file: filePath,
  87. })),
  88. };
  89. return postData;
  90. }
  91. /**
  92. * This function visits each property in the `variables` object, including
  93. * nested ones, and returns the path of each null value, in order.
  94. *
  95. * @example
  96. * // variables:
  97. * {
  98. * input: {
  99. * name: "George's Pots and Pans",
  100. * logo: null,
  101. * user: {
  102. * profilePicture: null
  103. * }
  104. * }
  105. * }
  106. * // return value:
  107. * ['variables.input.logo', 'variables.input.user.profilePicture']
  108. */
  109. function objectPath(variables: any): string[] {
  110. const pathsToNulls: string[] = [];
  111. const checkValue = (pathSoFar: string, value: any) => {
  112. if (value === null) {
  113. pathsToNulls.push(pathSoFar);
  114. } else if (typeof value === 'object') {
  115. for (const key of Object.getOwnPropertyNames(value)) {
  116. checkValue(`${pathSoFar}.${key}`, value[key]);
  117. }
  118. }
  119. };
  120. checkValue('variables', variables);
  121. return pathsToNulls;
  122. }