141 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| Object.defineProperty(exports, "__esModule", { value: true });
 | |
| exports.createForm = exports.multipartFormRequestOptions = exports.maybeMultipartFormRequestOptions = exports.isAsyncIterable = exports.checkFileSupport = void 0;
 | |
| exports.makeFile = makeFile;
 | |
| exports.getName = getName;
 | |
| const shims_1 = require("./shims.js");
 | |
| const checkFileSupport = () => {
 | |
|     if (typeof File === 'undefined') {
 | |
|         const { process } = globalThis;
 | |
|         const isOldNode = typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20;
 | |
|         throw new Error('`File` is not defined as a global, which is required for file uploads.' +
 | |
|             (isOldNode ?
 | |
|                 " Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`."
 | |
|                 : ''));
 | |
|     }
 | |
| };
 | |
| exports.checkFileSupport = checkFileSupport;
 | |
| /**
 | |
|  * Construct a `File` instance. This is used to ensure a helpful error is thrown
 | |
|  * for environments that don't define a global `File` yet.
 | |
|  */
 | |
| function makeFile(fileBits, fileName, options) {
 | |
|     (0, exports.checkFileSupport)();
 | |
|     return new File(fileBits, fileName ?? 'unknown_file', options);
 | |
| }
 | |
| function getName(value) {
 | |
|     return (((typeof value === 'object' &&
 | |
|         value !== null &&
 | |
|         (('name' in value && value.name && String(value.name)) ||
 | |
|             ('url' in value && value.url && String(value.url)) ||
 | |
|             ('filename' in value && value.filename && String(value.filename)) ||
 | |
|             ('path' in value && value.path && String(value.path)))) ||
 | |
|         '')
 | |
|         .split(/[\\/]/)
 | |
|         .pop() || undefined);
 | |
| }
 | |
| const isAsyncIterable = (value) => value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function';
 | |
| exports.isAsyncIterable = isAsyncIterable;
 | |
| /**
 | |
|  * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value.
 | |
|  * Otherwise returns the request as is.
 | |
|  */
 | |
| const maybeMultipartFormRequestOptions = async (opts, fetch) => {
 | |
|     if (!hasUploadableValue(opts.body))
 | |
|         return opts;
 | |
|     return { ...opts, body: await (0, exports.createForm)(opts.body, fetch) };
 | |
| };
 | |
| exports.maybeMultipartFormRequestOptions = maybeMultipartFormRequestOptions;
 | |
| const multipartFormRequestOptions = async (opts, fetch) => {
 | |
|     return { ...opts, body: await (0, exports.createForm)(opts.body, fetch) };
 | |
| };
 | |
| exports.multipartFormRequestOptions = multipartFormRequestOptions;
 | |
| const supportsFormDataMap = /* @__PURE__ */ new WeakMap();
 | |
| /**
 | |
|  * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending
 | |
|  * properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]".
 | |
|  * This function detects if the fetch function provided supports the global FormData object to avoid
 | |
|  * confusing error messages later on.
 | |
|  */
 | |
| function supportsFormData(fetchObject) {
 | |
|     const fetch = typeof fetchObject === 'function' ? fetchObject : fetchObject.fetch;
 | |
|     const cached = supportsFormDataMap.get(fetch);
 | |
|     if (cached)
 | |
|         return cached;
 | |
|     const promise = (async () => {
 | |
|         try {
 | |
|             const FetchResponse = ('Response' in fetch ?
 | |
|                 fetch.Response
 | |
|                 : (await fetch('data:,')).constructor);
 | |
|             const data = new FormData();
 | |
|             if (data.toString() === (await new FetchResponse(data).text())) {
 | |
|                 return false;
 | |
|             }
 | |
|             return true;
 | |
|         }
 | |
|         catch {
 | |
|             // avoid false negatives
 | |
|             return true;
 | |
|         }
 | |
|     })();
 | |
|     supportsFormDataMap.set(fetch, promise);
 | |
|     return promise;
 | |
| }
 | |
| const createForm = async (body, fetch) => {
 | |
|     if (!(await supportsFormData(fetch))) {
 | |
|         throw new TypeError('The provided fetch function does not support file uploads with the current global FormData class.');
 | |
|     }
 | |
|     const form = new FormData();
 | |
|     await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value)));
 | |
|     return form;
 | |
| };
 | |
| exports.createForm = createForm;
 | |
| // We check for Blob not File because Bun.File doesn't inherit from File,
 | |
| // but they both inherit from Blob and have a `name` property at runtime.
 | |
| const isNamedBlob = (value) => value instanceof Blob && 'name' in value;
 | |
| const isUploadable = (value) => typeof value === 'object' &&
 | |
|     value !== null &&
 | |
|     (value instanceof Response || (0, exports.isAsyncIterable)(value) || isNamedBlob(value));
 | |
| const hasUploadableValue = (value) => {
 | |
|     if (isUploadable(value))
 | |
|         return true;
 | |
|     if (Array.isArray(value))
 | |
|         return value.some(hasUploadableValue);
 | |
|     if (value && typeof value === 'object') {
 | |
|         for (const k in value) {
 | |
|             if (hasUploadableValue(value[k]))
 | |
|                 return true;
 | |
|         }
 | |
|     }
 | |
|     return false;
 | |
| };
 | |
| const addFormValue = async (form, key, value) => {
 | |
|     if (value === undefined)
 | |
|         return;
 | |
|     if (value == null) {
 | |
|         throw new TypeError(`Received null for "${key}"; to pass null in FormData, you must use the string 'null'`);
 | |
|     }
 | |
|     // TODO: make nested formats configurable
 | |
|     if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
 | |
|         form.append(key, String(value));
 | |
|     }
 | |
|     else if (value instanceof Response) {
 | |
|         form.append(key, makeFile([await value.blob()], getName(value)));
 | |
|     }
 | |
|     else if ((0, exports.isAsyncIterable)(value)) {
 | |
|         form.append(key, makeFile([await new Response((0, shims_1.ReadableStreamFrom)(value)).blob()], getName(value)));
 | |
|     }
 | |
|     else if (isNamedBlob(value)) {
 | |
|         form.append(key, value, getName(value));
 | |
|     }
 | |
|     else if (Array.isArray(value)) {
 | |
|         await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry)));
 | |
|     }
 | |
|     else if (typeof value === 'object') {
 | |
|         await Promise.all(Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)));
 | |
|     }
 | |
|     else {
 | |
|         throw new TypeError(`Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`);
 | |
|     }
 | |
| };
 | |
| //# sourceMappingURL=uploads.js.map
 |