to-file.mjs 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import { getName, makeFile, isAsyncIterable } from "./uploads.mjs";
  2. import { checkFileSupport } from "./uploads.mjs";
  3. /**
  4. * This check adds the arrayBuffer() method type because it is available and used at runtime
  5. */
  6. const isBlobLike = (value) => value != null &&
  7. typeof value === 'object' &&
  8. typeof value.size === 'number' &&
  9. typeof value.type === 'string' &&
  10. typeof value.text === 'function' &&
  11. typeof value.slice === 'function' &&
  12. typeof value.arrayBuffer === 'function';
  13. /**
  14. * This check adds the arrayBuffer() method type because it is available and used at runtime
  15. */
  16. const isFileLike = (value) => value != null &&
  17. typeof value === 'object' &&
  18. typeof value.name === 'string' &&
  19. typeof value.lastModified === 'number' &&
  20. isBlobLike(value);
  21. const isResponseLike = (value) => value != null &&
  22. typeof value === 'object' &&
  23. typeof value.url === 'string' &&
  24. typeof value.blob === 'function';
  25. /**
  26. * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats
  27. * @param value the raw content of the file. Can be an {@link Uploadable}, BlobLikePart, or AsyncIterable of BlobLikeParts
  28. * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible
  29. * @param {Object=} options additional properties
  30. * @param {string=} options.type the MIME type of the content
  31. * @param {number=} options.lastModified the last modified timestamp
  32. * @returns a {@link File} with the given properties
  33. */
  34. export async function toFile(value, name, options) {
  35. checkFileSupport();
  36. // If it's a promise, resolve it.
  37. value = await value;
  38. // If we've been given a `File` we don't need to do anything
  39. if (isFileLike(value)) {
  40. if (value instanceof File) {
  41. return value;
  42. }
  43. return makeFile([await value.arrayBuffer()], value.name);
  44. }
  45. if (isResponseLike(value)) {
  46. const blob = await value.blob();
  47. name || (name = new URL(value.url).pathname.split(/[\\/]/).pop());
  48. return makeFile(await getBytes(blob), name, options);
  49. }
  50. const parts = await getBytes(value);
  51. name || (name = getName(value));
  52. if (!options?.type) {
  53. const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type);
  54. if (typeof type === 'string') {
  55. options = { ...options, type };
  56. }
  57. }
  58. return makeFile(parts, name, options);
  59. }
  60. async function getBytes(value) {
  61. let parts = [];
  62. if (typeof value === 'string' ||
  63. ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc.
  64. value instanceof ArrayBuffer) {
  65. parts.push(value);
  66. }
  67. else if (isBlobLike(value)) {
  68. parts.push(value instanceof Blob ? value : await value.arrayBuffer());
  69. }
  70. else if (isAsyncIterable(value) // includes Readable, ReadableStream, etc.
  71. ) {
  72. for await (const chunk of value) {
  73. parts.push(...(await getBytes(chunk))); // TODO, consider validating?
  74. }
  75. }
  76. else {
  77. const constructor = value?.constructor?.name;
  78. throw new Error(`Unexpected data type: ${typeof value}${constructor ? `; constructor: ${constructor}` : ''}${propsForError(value)}`);
  79. }
  80. return parts;
  81. }
  82. function propsForError(value) {
  83. if (typeof value !== 'object' || value === null)
  84. return '';
  85. const props = Object.getOwnPropertyNames(value);
  86. return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`;
  87. }
  88. //# sourceMappingURL=to-file.mjs.map