| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- import { ReadableStreamFrom } from "./shims.mjs";
- export 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`."
- : ''));
- }
- };
- /**
- * Construct a `File` instance. This is used to ensure a helpful error is thrown
- * for environments that don't define a global `File` yet.
- */
- export function makeFile(fileBits, fileName, options) {
- checkFileSupport();
- return new File(fileBits, fileName ?? 'unknown_file', options);
- }
- export function getName(value, stripPath) {
- const val = (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)))) ||
- '';
- return stripPath ? val.split(/[\\/]/).pop() || undefined : val;
- }
- export const isAsyncIterable = (value) => value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function';
- /**
- * 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.
- */
- export const maybeMultipartFormRequestOptions = async (opts, fetch) => {
- if (!hasUploadableValue(opts.body))
- return opts;
- return { ...opts, body: await createForm(opts.body, fetch) };
- };
- export const multipartFormRequestOptions = async (opts, fetch, stripFilenames = true) => {
- return { ...opts, body: await createForm(opts.body, fetch, stripFilenames) };
- };
- 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;
- }
- export const createForm = async (body, fetch, stripFilenames = true) => {
- 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, stripFilenames)));
- return form;
- };
- // 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 || 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, stripFilenames) => {
- 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) {
- let options = {};
- const contentType = value.headers.get('Content-Type');
- if (contentType) {
- options = { type: contentType };
- }
- form.append(key, makeFile([await value.blob()], getName(value, stripFilenames), options));
- }
- else if (isAsyncIterable(value)) {
- form.append(key, makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value, stripFilenames)));
- }
- else if (isNamedBlob(value)) {
- form.append(key, makeFile([value], getName(value, stripFilenames), { type: value.type }));
- }
- else if (Array.isArray(value)) {
- await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry, stripFilenames)));
- }
- else if (typeof value === 'object') {
- await Promise.all(Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop, stripFilenames)));
- }
- 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.mjs.map
|