"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __accessCheck = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; var __privateMethod = (obj, member, method) => { __accessCheck(obj, member, "access private method"); return method; }; // src/index.ts var src_exports = {}; __export(src_exports, { FormDataEncoder: () => FormDataEncoder, isFile: () => isFile, isFormData: () => isFormData }); module.exports = __toCommonJS(src_exports); // src/util/isFunction.ts var isFunction = (value) => typeof value === "function"; // src/util/isAsyncIterable.ts var isAsyncIterable = (value) => isFunction(value[Symbol.asyncIterator]); // src/util/chunk.ts var MAX_CHUNK_SIZE = 65536; function* chunk(value) { if (value.byteLength <= MAX_CHUNK_SIZE) { yield value; return; } let offset = 0; while (offset < value.byteLength) { const size = Math.min(value.byteLength - offset, MAX_CHUNK_SIZE); const buffer = value.buffer.slice(offset, offset + size); offset += buffer.byteLength; yield new Uint8Array(buffer); } } // src/util/getStreamIterator.ts async function* readStream(readable) { const reader = readable.getReader(); while (true) { const { done, value } = await reader.read(); if (done) { break; } yield value; } } async function* chunkStream(stream) { for await (const value of stream) { yield* chunk(value); } } var getStreamIterator = (source) => { if (isAsyncIterable(source)) { return chunkStream(source); } if (isFunction(source.getReader)) { return chunkStream(readStream(source)); } throw new TypeError( "Unsupported data source: Expected either ReadableStream or async iterable." ); }; // src/util/createBoundary.ts var alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"; function createBoundary() { let size = 16; let res = ""; while (size--) { res += alphabet[Math.random() * alphabet.length << 0]; } return res; } // src/util/normalizeValue.ts var normalizeValue = (value) => String(value).replace(/\r|\n/g, (match, i, str) => { if (match === "\r" && str[i + 1] !== "\n" || match === "\n" && str[i - 1] !== "\r") { return "\r\n"; } return match; }); // src/util/isPlainObject.ts var getType = (value) => Object.prototype.toString.call(value).slice(8, -1).toLowerCase(); function isPlainObject(value) { if (getType(value) !== "object") { return false; } const pp = Object.getPrototypeOf(value); if (pp === null || pp === void 0) { return true; } const Ctor = pp.constructor && pp.constructor.toString(); return Ctor === Object.toString(); } // src/util/proxyHeaders.ts function getProperty(target, prop) { if (typeof prop === "string") { for (const [name, value] of Object.entries(target)) { if (prop.toLowerCase() === name.toLowerCase()) { return value; } } } return void 0; } var proxyHeaders = (object) => new Proxy( object, { get: (target, prop) => getProperty(target, prop), has: (target, prop) => getProperty(target, prop) !== void 0 } ); // src/util/isFormData.ts var isFormData = (value) => Boolean( value && isFunction(value.constructor) && value[Symbol.toStringTag] === "FormData" && isFunction(value.append) && isFunction(value.getAll) && isFunction(value.entries) && isFunction(value[Symbol.iterator]) ); // src/util/escapeName.ts var escapeName = (name) => String(name).replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/"/g, "%22"); // src/util/isFile.ts var isFile = (value) => Boolean( value && typeof value === "object" && isFunction(value.constructor) && value[Symbol.toStringTag] === "File" && isFunction(value.stream) && value.name != null ); // src/FormDataEncoder.ts var defaultOptions = { enableAdditionalHeaders: false }; var readonlyProp = { writable: false, configurable: false }; var _CRLF, _CRLF_BYTES, _CRLF_BYTES_LENGTH, _DASHES, _encoder, _footer, _form, _options, _getFieldHeader, getFieldHeader_fn, _getContentLength, getContentLength_fn; var FormDataEncoder = class { constructor(form, boundaryOrOptions, options) { __privateAdd(this, _getFieldHeader); /** * Returns form-data content length */ __privateAdd(this, _getContentLength); __privateAdd(this, _CRLF, "\r\n"); __privateAdd(this, _CRLF_BYTES, void 0); __privateAdd(this, _CRLF_BYTES_LENGTH, void 0); __privateAdd(this, _DASHES, "-".repeat(2)); /** * TextEncoder instance */ __privateAdd(this, _encoder, new TextEncoder()); /** * Returns form-data footer bytes */ __privateAdd(this, _footer, void 0); /** * FormData instance */ __privateAdd(this, _form, void 0); /** * Instance options */ __privateAdd(this, _options, void 0); if (!isFormData(form)) { throw new TypeError("Expected first argument to be a FormData instance."); } let boundary; if (isPlainObject(boundaryOrOptions)) { options = boundaryOrOptions; } else { boundary = boundaryOrOptions; } if (!boundary) { boundary = createBoundary(); } if (typeof boundary !== "string") { throw new TypeError("Expected boundary argument to be a string."); } if (options && !isPlainObject(options)) { throw new TypeError("Expected options argument to be an object."); } __privateSet(this, _form, Array.from(form.entries())); __privateSet(this, _options, { ...defaultOptions, ...options }); __privateSet(this, _CRLF_BYTES, __privateGet(this, _encoder).encode(__privateGet(this, _CRLF))); __privateSet(this, _CRLF_BYTES_LENGTH, __privateGet(this, _CRLF_BYTES).byteLength); this.boundary = `form-data-boundary-${boundary}`; this.contentType = `multipart/form-data; boundary=${this.boundary}`; __privateSet(this, _footer, __privateGet(this, _encoder).encode( `${__privateGet(this, _DASHES)}${this.boundary}${__privateGet(this, _DASHES)}${__privateGet(this, _CRLF).repeat(2)}` )); const headers = { "Content-Type": this.contentType }; const contentLength = __privateMethod(this, _getContentLength, getContentLength_fn).call(this); if (contentLength) { this.contentLength = contentLength; headers["Content-Length"] = contentLength; } this.headers = proxyHeaders(Object.freeze(headers)); Object.defineProperties(this, { boundary: readonlyProp, contentType: readonlyProp, contentLength: readonlyProp, headers: readonlyProp }); } /** * Creates an iterator allowing to go through form-data parts (with metadata). * This method **will not** read the files and **will not** split values big into smaller chunks. * * Using this method, you can convert form-data content into Blob: * * @example * * ```ts * import {Readable} from "stream" * * import {FormDataEncoder} from "form-data-encoder" * * import {FormData} from "formdata-polyfill/esm-min.js" * import {fileFrom} from "fetch-blob/form.js" * import {File} from "fetch-blob/file.js" * import {Blob} from "fetch-blob" * * import fetch from "node-fetch" * * const form = new FormData() * * form.set("field", "Just a random string") * form.set("file", new File(["Using files is class amazing"])) * form.set("fileFromPath", await fileFrom("path/to/a/file.txt")) * * const encoder = new FormDataEncoder(form) * * const options = { * method: "post", * body: new Blob(encoder, {type: encoder.contentType}) * } * * const response = await fetch("https://httpbin.org/post", options) * * console.log(await response.json()) * ``` */ *values() { for (const [name, raw] of __privateGet(this, _form)) { const value = isFile(raw) ? raw : __privateGet(this, _encoder).encode( normalizeValue(raw) ); yield __privateMethod(this, _getFieldHeader, getFieldHeader_fn).call(this, name, value); yield value; yield __privateGet(this, _CRLF_BYTES); } yield __privateGet(this, _footer); } /** * Creates an async iterator allowing to perform the encoding by portions. * This method reads through files and splits big values into smaller pieces (65536 bytes per each). * * @example * * ```ts * import {Readable} from "stream" * * import {FormData, File, fileFromPath} from "formdata-node" * import {FormDataEncoder} from "form-data-encoder" * * import fetch from "node-fetch" * * const form = new FormData() * * form.set("field", "Just a random string") * form.set("file", new File(["Using files is class amazing"], "file.txt")) * form.set("fileFromPath", await fileFromPath("path/to/a/file.txt")) * * const encoder = new FormDataEncoder(form) * * const options = { * method: "post", * headers: encoder.headers, * body: Readable.from(encoder.encode()) // or Readable.from(encoder) * } * * const response = await fetch("https://httpbin.org/post", options) * * console.log(await response.json()) * ``` */ async *encode() { for (const part of this.values()) { if (isFile(part)) { yield* getStreamIterator(part.stream()); } else { yield* chunk(part); } } } /** * Creates an iterator allowing to read through the encoder data using for...of loops */ [Symbol.iterator]() { return this.values(); } /** * Creates an **async** iterator allowing to read through the encoder data using for-await...of loops */ [Symbol.asyncIterator]() { return this.encode(); } }; _CRLF = new WeakMap(); _CRLF_BYTES = new WeakMap(); _CRLF_BYTES_LENGTH = new WeakMap(); _DASHES = new WeakMap(); _encoder = new WeakMap(); _footer = new WeakMap(); _form = new WeakMap(); _options = new WeakMap(); _getFieldHeader = new WeakSet(); getFieldHeader_fn = function(name, value) { let header = ""; header += `${__privateGet(this, _DASHES)}${this.boundary}${__privateGet(this, _CRLF)}`; header += `Content-Disposition: form-data; name="${escapeName(name)}"`; if (isFile(value)) { header += `; filename="${escapeName(value.name)}"${__privateGet(this, _CRLF)}`; header += `Content-Type: ${value.type || "application/octet-stream"}`; } if (__privateGet(this, _options).enableAdditionalHeaders === true) { const size = isFile(value) ? value.size : value.byteLength; if (size != null && !isNaN(size)) { header += `${__privateGet(this, _CRLF)}Content-Length: ${size}`; } } return __privateGet(this, _encoder).encode(`${header}${__privateGet(this, _CRLF).repeat(2)}`); }; _getContentLength = new WeakSet(); getContentLength_fn = function() { let length = 0; for (const [name, raw] of __privateGet(this, _form)) { const value = isFile(raw) ? raw : __privateGet(this, _encoder).encode( normalizeValue(raw) ); const size = isFile(value) ? value.size : value.byteLength; if (size == null || isNaN(size)) { return void 0; } length += __privateMethod(this, _getFieldHeader, getFieldHeader_fn).call(this, name, value).byteLength; length += size; length += __privateGet(this, _CRLF_BYTES_LENGTH); } return String(length + __privateGet(this, _footer).byteLength); }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { FormDataEncoder, isFile, isFormData });