fix
This commit is contained in:
393
book/node_modules/form-data-encoder/lib/index.cjs
generated
vendored
Normal file
393
book/node_modules/form-data-encoder/lib/index.cjs
generated
vendored
Normal file
@@ -0,0 +1,393 @@
|
||||
"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
|
||||
});
|
||||
277
book/node_modules/form-data-encoder/lib/index.d.cts
generated
vendored
Normal file
277
book/node_modules/form-data-encoder/lib/index.d.cts
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
interface RawHeaders {
|
||||
"Content-Type": string;
|
||||
"Content-Length"?: string;
|
||||
}
|
||||
interface FormDataEncoderHeaders extends RawHeaders {
|
||||
"content-type": string;
|
||||
"content-length"?: string;
|
||||
}
|
||||
|
||||
interface FileLike {
|
||||
/**
|
||||
* Name of the file referenced by the File object.
|
||||
*/
|
||||
readonly name: string;
|
||||
/**
|
||||
* Returns the media type ([`MIME`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)) of the file represented by a `File` object.
|
||||
*/
|
||||
readonly type: string;
|
||||
/**
|
||||
* Size of the file parts in bytes
|
||||
*/
|
||||
readonly size: number;
|
||||
/**
|
||||
* The last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight). Files without a known last modified date return the current date.
|
||||
*/
|
||||
readonly lastModified: number;
|
||||
/**
|
||||
* Returns a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) which upon reading returns the data contained within the [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File).
|
||||
*/
|
||||
stream(): ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;
|
||||
readonly [Symbol.toStringTag]?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `string` or `File` that represents a single value from a set of `FormData` key-value pairs.
|
||||
*/
|
||||
type FormDataEntryValue = string | FileLike;
|
||||
/**
|
||||
* This interface reflects minimal shape of the FormData
|
||||
*/
|
||||
interface FormDataLike {
|
||||
/**
|
||||
* Appends a new value onto an existing key inside a FormData object,
|
||||
* or adds the key if it does not already exist.
|
||||
*
|
||||
* The difference between `set()` and `append()` is that if the specified key already exists, `set()` will overwrite all existing values with the new one, whereas `append()` will append the new value onto the end of the existing set of values.
|
||||
*
|
||||
* @param name The name of the field whose data is contained in `value`.
|
||||
* @param value The field's value. This can be [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
|
||||
or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File). If none of these are specified the value is converted to a string.
|
||||
* @param fileName The filename reported to the server, when a Blob or File is passed as the second parameter. The default filename for Blob objects is "blob". The default filename for File objects is the file's filename.
|
||||
*/
|
||||
append(name: string, value: unknown, fileName?: string): void;
|
||||
/**
|
||||
* Returns all the values associated with a given key from within a `FormData` object.
|
||||
*
|
||||
* @param {string} name A name of the value you want to retrieve.
|
||||
*
|
||||
* @returns An array of `FormDataEntryValue` whose key matches the value passed in the `name` parameter. If the key doesn't exist, the method returns an empty list.
|
||||
*/
|
||||
getAll(name: string): FormDataEntryValue[];
|
||||
/**
|
||||
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through the `FormData` key/value pairs.
|
||||
* The key of each pair is a string; the value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).
|
||||
*/
|
||||
entries(): IterableIterator<[string, FormDataEntryValue]>;
|
||||
/**
|
||||
* An alias for FormDataLike#entries()
|
||||
*/
|
||||
[Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>;
|
||||
readonly [Symbol.toStringTag]?: string;
|
||||
}
|
||||
|
||||
interface FormDataEncoderOptions {
|
||||
/**
|
||||
* When enabled, the encoder will emit additional per part headers, such as `Content-Length`.
|
||||
*
|
||||
* Please note that the web clients do not include these, so when enabled this option might cause an error if `multipart/form-data` does not consider additional headers.
|
||||
*
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
enableAdditionalHeaders?: boolean;
|
||||
}
|
||||
/**
|
||||
* Implements [`multipart/form-data` encoding algorithm](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm),
|
||||
* allowing to add support for spec-comliant [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) to an HTTP client.
|
||||
*/
|
||||
declare class FormDataEncoder {
|
||||
#private;
|
||||
/**
|
||||
* Returns boundary string
|
||||
*/
|
||||
readonly boundary: string;
|
||||
/**
|
||||
* Returns Content-Type header
|
||||
*/
|
||||
readonly contentType: string;
|
||||
/**
|
||||
* Returns Content-Length header
|
||||
*/
|
||||
readonly contentLength: string | undefined;
|
||||
/**
|
||||
* Returns headers object with Content-Type and Content-Length header
|
||||
*/
|
||||
readonly headers: Readonly<FormDataEncoderHeaders>;
|
||||
/**
|
||||
* Creates a multipart/form-data encoder.
|
||||
*
|
||||
* @param form FormData object to encode. This object must be a spec-compatible FormData implementation.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* 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)
|
||||
* }
|
||||
*
|
||||
* const response = await fetch("https://httpbin.org/post", options)
|
||||
*
|
||||
* console.log(await response.json())
|
||||
* ```
|
||||
*/
|
||||
constructor(form: FormDataLike);
|
||||
/**
|
||||
* Creates multipart/form-data encoder with custom boundary string.
|
||||
*
|
||||
* @param form FormData object to encode. This object must be a spec-compatible FormData implementation.
|
||||
* @param boundary An optional boundary string that will be used by the encoder. If there's no boundary string is present, Encoder will generate it automatically.
|
||||
*/
|
||||
constructor(form: FormDataLike, boundary: string);
|
||||
/**
|
||||
* Creates multipart/form-data encoder with additional options.
|
||||
*
|
||||
* @param form FormData object to encode. This object must be a spec-compatible FormData implementation.
|
||||
* @param options Additional options
|
||||
*/
|
||||
constructor(form: FormDataLike, options: FormDataEncoderOptions);
|
||||
constructor(form: FormDataLike, boundary: string, options?: FormDataEncoderOptions);
|
||||
/**
|
||||
* 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(): Generator<Uint8Array | FileLike, void, undefined>;
|
||||
/**
|
||||
* 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())
|
||||
* ```
|
||||
*/
|
||||
encode(): AsyncGenerator<Uint8Array, void, undefined>;
|
||||
/**
|
||||
* Creates an iterator allowing to read through the encoder data using for...of loops
|
||||
*/
|
||||
[Symbol.iterator](): Generator<Uint8Array | FileLike, void, undefined>;
|
||||
/**
|
||||
* Creates an **async** iterator allowing to read through the encoder data using for-await...of loops
|
||||
*/
|
||||
[Symbol.asyncIterator](): AsyncGenerator<Uint8Array, void, undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given object is `File`.
|
||||
*
|
||||
* Note that this function will return `false` for Blob, because the FormDataEncoder expects FormData to return File when a value is binary data.
|
||||
*
|
||||
* @param value an object to test
|
||||
*
|
||||
* @api public
|
||||
*
|
||||
* This function will return `true` for FileAPI compatible `File` objects:
|
||||
*
|
||||
* ```ts
|
||||
* import {createReadStream} from "node:fs"
|
||||
*
|
||||
* import {isFile} from "form-data-encoder"
|
||||
*
|
||||
* isFile(new File(["Content"], "file.txt")) // -> true
|
||||
* ```
|
||||
*
|
||||
* However, if you pass a Node.js `Buffer`, or `Blob`, or `ReadStream`, it will return `false`:
|
||||
*
|
||||
* ```js
|
||||
* import {isFile} from "form-data-encoder"
|
||||
*
|
||||
* isFile(Buffer.from("Content")) // -> false
|
||||
* isFile(new Blob(["Content"])) // -> false
|
||||
* isFile(createReadStream("path/to/a/file.txt")) // -> false
|
||||
* ```
|
||||
*/
|
||||
declare const isFile: (value: unknown) => value is FileLike;
|
||||
|
||||
/**
|
||||
* Check if given object is FormData
|
||||
*
|
||||
* @param value an object to test
|
||||
*/
|
||||
declare const isFormData: (value: unknown) => value is FormDataLike;
|
||||
|
||||
export { FileLike, FormDataEncoder, FormDataEncoderOptions, FormDataEntryValue, FormDataLike, isFile, isFormData };
|
||||
277
book/node_modules/form-data-encoder/lib/index.d.ts
generated
vendored
Normal file
277
book/node_modules/form-data-encoder/lib/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
interface RawHeaders {
|
||||
"Content-Type": string;
|
||||
"Content-Length"?: string;
|
||||
}
|
||||
interface FormDataEncoderHeaders extends RawHeaders {
|
||||
"content-type": string;
|
||||
"content-length"?: string;
|
||||
}
|
||||
|
||||
interface FileLike {
|
||||
/**
|
||||
* Name of the file referenced by the File object.
|
||||
*/
|
||||
readonly name: string;
|
||||
/**
|
||||
* Returns the media type ([`MIME`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)) of the file represented by a `File` object.
|
||||
*/
|
||||
readonly type: string;
|
||||
/**
|
||||
* Size of the file parts in bytes
|
||||
*/
|
||||
readonly size: number;
|
||||
/**
|
||||
* The last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight). Files without a known last modified date return the current date.
|
||||
*/
|
||||
readonly lastModified: number;
|
||||
/**
|
||||
* Returns a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) which upon reading returns the data contained within the [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File).
|
||||
*/
|
||||
stream(): ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>;
|
||||
readonly [Symbol.toStringTag]?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `string` or `File` that represents a single value from a set of `FormData` key-value pairs.
|
||||
*/
|
||||
type FormDataEntryValue = string | FileLike;
|
||||
/**
|
||||
* This interface reflects minimal shape of the FormData
|
||||
*/
|
||||
interface FormDataLike {
|
||||
/**
|
||||
* Appends a new value onto an existing key inside a FormData object,
|
||||
* or adds the key if it does not already exist.
|
||||
*
|
||||
* The difference between `set()` and `append()` is that if the specified key already exists, `set()` will overwrite all existing values with the new one, whereas `append()` will append the new value onto the end of the existing set of values.
|
||||
*
|
||||
* @param name The name of the field whose data is contained in `value`.
|
||||
* @param value The field's value. This can be [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
|
||||
or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File). If none of these are specified the value is converted to a string.
|
||||
* @param fileName The filename reported to the server, when a Blob or File is passed as the second parameter. The default filename for Blob objects is "blob". The default filename for File objects is the file's filename.
|
||||
*/
|
||||
append(name: string, value: unknown, fileName?: string): void;
|
||||
/**
|
||||
* Returns all the values associated with a given key from within a `FormData` object.
|
||||
*
|
||||
* @param {string} name A name of the value you want to retrieve.
|
||||
*
|
||||
* @returns An array of `FormDataEntryValue` whose key matches the value passed in the `name` parameter. If the key doesn't exist, the method returns an empty list.
|
||||
*/
|
||||
getAll(name: string): FormDataEntryValue[];
|
||||
/**
|
||||
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through the `FormData` key/value pairs.
|
||||
* The key of each pair is a string; the value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).
|
||||
*/
|
||||
entries(): IterableIterator<[string, FormDataEntryValue]>;
|
||||
/**
|
||||
* An alias for FormDataLike#entries()
|
||||
*/
|
||||
[Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>;
|
||||
readonly [Symbol.toStringTag]?: string;
|
||||
}
|
||||
|
||||
interface FormDataEncoderOptions {
|
||||
/**
|
||||
* When enabled, the encoder will emit additional per part headers, such as `Content-Length`.
|
||||
*
|
||||
* Please note that the web clients do not include these, so when enabled this option might cause an error if `multipart/form-data` does not consider additional headers.
|
||||
*
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
enableAdditionalHeaders?: boolean;
|
||||
}
|
||||
/**
|
||||
* Implements [`multipart/form-data` encoding algorithm](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm),
|
||||
* allowing to add support for spec-comliant [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) to an HTTP client.
|
||||
*/
|
||||
declare class FormDataEncoder {
|
||||
#private;
|
||||
/**
|
||||
* Returns boundary string
|
||||
*/
|
||||
readonly boundary: string;
|
||||
/**
|
||||
* Returns Content-Type header
|
||||
*/
|
||||
readonly contentType: string;
|
||||
/**
|
||||
* Returns Content-Length header
|
||||
*/
|
||||
readonly contentLength: string | undefined;
|
||||
/**
|
||||
* Returns headers object with Content-Type and Content-Length header
|
||||
*/
|
||||
readonly headers: Readonly<FormDataEncoderHeaders>;
|
||||
/**
|
||||
* Creates a multipart/form-data encoder.
|
||||
*
|
||||
* @param form FormData object to encode. This object must be a spec-compatible FormData implementation.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* 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)
|
||||
* }
|
||||
*
|
||||
* const response = await fetch("https://httpbin.org/post", options)
|
||||
*
|
||||
* console.log(await response.json())
|
||||
* ```
|
||||
*/
|
||||
constructor(form: FormDataLike);
|
||||
/**
|
||||
* Creates multipart/form-data encoder with custom boundary string.
|
||||
*
|
||||
* @param form FormData object to encode. This object must be a spec-compatible FormData implementation.
|
||||
* @param boundary An optional boundary string that will be used by the encoder. If there's no boundary string is present, Encoder will generate it automatically.
|
||||
*/
|
||||
constructor(form: FormDataLike, boundary: string);
|
||||
/**
|
||||
* Creates multipart/form-data encoder with additional options.
|
||||
*
|
||||
* @param form FormData object to encode. This object must be a spec-compatible FormData implementation.
|
||||
* @param options Additional options
|
||||
*/
|
||||
constructor(form: FormDataLike, options: FormDataEncoderOptions);
|
||||
constructor(form: FormDataLike, boundary: string, options?: FormDataEncoderOptions);
|
||||
/**
|
||||
* 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(): Generator<Uint8Array | FileLike, void, undefined>;
|
||||
/**
|
||||
* 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())
|
||||
* ```
|
||||
*/
|
||||
encode(): AsyncGenerator<Uint8Array, void, undefined>;
|
||||
/**
|
||||
* Creates an iterator allowing to read through the encoder data using for...of loops
|
||||
*/
|
||||
[Symbol.iterator](): Generator<Uint8Array | FileLike, void, undefined>;
|
||||
/**
|
||||
* Creates an **async** iterator allowing to read through the encoder data using for-await...of loops
|
||||
*/
|
||||
[Symbol.asyncIterator](): AsyncGenerator<Uint8Array, void, undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given object is `File`.
|
||||
*
|
||||
* Note that this function will return `false` for Blob, because the FormDataEncoder expects FormData to return File when a value is binary data.
|
||||
*
|
||||
* @param value an object to test
|
||||
*
|
||||
* @api public
|
||||
*
|
||||
* This function will return `true` for FileAPI compatible `File` objects:
|
||||
*
|
||||
* ```ts
|
||||
* import {createReadStream} from "node:fs"
|
||||
*
|
||||
* import {isFile} from "form-data-encoder"
|
||||
*
|
||||
* isFile(new File(["Content"], "file.txt")) // -> true
|
||||
* ```
|
||||
*
|
||||
* However, if you pass a Node.js `Buffer`, or `Blob`, or `ReadStream`, it will return `false`:
|
||||
*
|
||||
* ```js
|
||||
* import {isFile} from "form-data-encoder"
|
||||
*
|
||||
* isFile(Buffer.from("Content")) // -> false
|
||||
* isFile(new Blob(["Content"])) // -> false
|
||||
* isFile(createReadStream("path/to/a/file.txt")) // -> false
|
||||
* ```
|
||||
*/
|
||||
declare const isFile: (value: unknown) => value is FileLike;
|
||||
|
||||
/**
|
||||
* Check if given object is FormData
|
||||
*
|
||||
* @param value an object to test
|
||||
*/
|
||||
declare const isFormData: (value: unknown) => value is FormDataLike;
|
||||
|
||||
export { FileLike, FormDataEncoder, FormDataEncoderOptions, FormDataEntryValue, FormDataLike, isFile, isFormData };
|
||||
365
book/node_modules/form-data-encoder/lib/index.js
generated
vendored
Normal file
365
book/node_modules/form-data-encoder/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,365 @@
|
||||
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/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);
|
||||
};
|
||||
export {
|
||||
FormDataEncoder,
|
||||
isFile,
|
||||
isFormData
|
||||
};
|
||||
21
book/node_modules/form-data-encoder/license
generated
vendored
Normal file
21
book/node_modules/form-data-encoder/license
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021-present Nick K.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
84
book/node_modules/form-data-encoder/package.json
generated
vendored
Normal file
84
book/node_modules/form-data-encoder/package.json
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "form-data-encoder",
|
||||
"description": "Encode FormData content into the multipart/form-data format",
|
||||
"version": "4.0.2",
|
||||
"author": "Nick K.",
|
||||
"license": "MIT",
|
||||
"repository": "octet-stream/form-data-encoder",
|
||||
"sideEffects": false,
|
||||
"packageManager": "pnpm@8.5.1",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"keywords": [
|
||||
"form-data",
|
||||
"encoder",
|
||||
"multipart",
|
||||
"files-upload",
|
||||
"async-iterator",
|
||||
"spec-compatible",
|
||||
"form"
|
||||
],
|
||||
"main": "./lib/index.js",
|
||||
"module": "./lib/index.js",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./lib/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./lib/index.d.cts",
|
||||
"default": "./lib/index.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"types": "./lib/index.d.ts",
|
||||
"devDependencies": {
|
||||
"@changesets/changelog-github": "0.4.8",
|
||||
"@changesets/cli": "2.26.2",
|
||||
"@octetstream/eslint-config": "7.2.1",
|
||||
"@types/mime-types": "2.1.1",
|
||||
"@types/node": "20.2.1",
|
||||
"@types/sinon": "10.0.15",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.6",
|
||||
"@typescript-eslint/parser": "5.59.6",
|
||||
"ava": "5.2.0",
|
||||
"c8": "7.13.0",
|
||||
"cross-env": "7.0.3",
|
||||
"del-cli": "5.0.0",
|
||||
"eslint": "8.40.0",
|
||||
"eslint-config-airbnb-typescript": "17.0.0",
|
||||
"eslint-import-resolver-typescript": "3.5.5",
|
||||
"eslint-plugin-ava": "14.0.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-jsx-a11y": "6.7.1",
|
||||
"eslint-plugin-react": "7.32.2",
|
||||
"formdata-node": "6.0.2",
|
||||
"husky": "8.0.3",
|
||||
"lint-staged": "13.2.2",
|
||||
"node-fetch": "^3.3.2",
|
||||
"pinst": "3.0.0",
|
||||
"sinon": "15.0.4",
|
||||
"ts-expect": "^1.3.0",
|
||||
"ts-node": "10.9.1",
|
||||
"tsup": "7.2.0",
|
||||
"ttypescript": "1.5.15",
|
||||
"typescript": "5.0.4",
|
||||
"undici": "^5.26.5",
|
||||
"web-streams-polyfill": "4.0.0-beta.3"
|
||||
},
|
||||
"scripts": {
|
||||
"eslint": "eslint src/**/*.ts",
|
||||
"lint:types": "tsc --noEmit",
|
||||
"lint": "pnpm eslint && pnpm lint:types",
|
||||
"staged": "lint-staged",
|
||||
"coverage": "c8 pnpm test",
|
||||
"ci": "c8 pnpm test && c8 report --reporter=json",
|
||||
"build": "pnpm exec del-cli lib && pnpm exec tsup",
|
||||
"test": "cross-env NODE_OPTIONS=\"--no-warnings --experimental-fetch --loader=ts-node/esm\" ava",
|
||||
"release": "pnpm build && pnpm changeset publish"
|
||||
}
|
||||
}
|
||||
369
book/node_modules/form-data-encoder/readme.md
generated
vendored
Normal file
369
book/node_modules/form-data-encoder/readme.md
generated
vendored
Normal file
@@ -0,0 +1,369 @@
|
||||
# form-data-encoder
|
||||
|
||||
Encode `FormData` content into the `multipart/form-data` format
|
||||
|
||||
[](https://codecov.io/github/octet-stream/form-data-encoder?branch=main)
|
||||
[](https://github.com/octet-stream/form-data-encoder/actions/workflows/ci.yml)
|
||||
[](https://github.com/octet-stream/form-data-encoder/actions/workflows/eslint.yml)
|
||||
[](https://github.com/octet-stream/form-data-encoder/actions/workflows/typescript.yml)
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js v18.0.0 or higher;
|
||||
- Runtime should support `TextEncoder`, `TextDecoder`, `WeakMap`, `WeakSet` and async generator functions;
|
||||
- For TypeScript users: tsc v4.3 or higher.
|
||||
|
||||
## Installation
|
||||
|
||||
You can install this package using npm:
|
||||
|
||||
```sh
|
||||
npm install form-data-encoder
|
||||
```
|
||||
|
||||
Or yarn:
|
||||
|
||||
```sh
|
||||
yarn add form-data-encoder
|
||||
```
|
||||
|
||||
Or pnpm:
|
||||
|
||||
```sh
|
||||
pnpm add form-data-encoder
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. To start the encoding process, you need to create a new Encoder instance with the FormData you want to encode:
|
||||
|
||||
```js
|
||||
import {Readable} from "stream"
|
||||
|
||||
import {FormData, File} from "formdata-node"
|
||||
import {FormDataEncoder} from "form-data-encoder"
|
||||
|
||||
import fetch from "node-fetch"
|
||||
|
||||
const form = new FormData()
|
||||
|
||||
form.set("greeting", "Hello, World!")
|
||||
form.set("file", new File(["On Soviet Moon landscape see binoculars through YOU"], "file.txt"))
|
||||
|
||||
const encoder = new FormDataEncoder(form)
|
||||
|
||||
const options = {
|
||||
method: "post",
|
||||
|
||||
// Set request headers provided by the Encoder.
|
||||
// The `headers` property has `Content-Type` and `Content-Length` headers.
|
||||
headers: encoder.headers,
|
||||
|
||||
// Create a Readable stream from the Encoder.
|
||||
// You can omit usage of `Readable.from` for HTTP clients whose support async iterables in request body.
|
||||
// The Encoder will yield FormData content portions encoded into the multipart/form-data format as node-fetch consumes the stream.
|
||||
body: Readable.from(encoder.encode()) // or just Readable.from(encoder)
|
||||
}
|
||||
|
||||
const response = await fetch("https://httpbin.org/post", options)
|
||||
|
||||
console.log(await response.json())
|
||||
```
|
||||
|
||||
2. Encoder support different spec-compatible FormData implementations. Let's try it with [`formdata-polyfill`](https://github.com/jimmywarting/FormData):
|
||||
|
||||
```js
|
||||
import {Readable} from "stream"
|
||||
|
||||
import {FormDataEncoder} from "form-data-encoder"
|
||||
import {FormData} from "formdata-polyfill/esm-min.js"
|
||||
import {File} from "fetch-blob" // v3
|
||||
|
||||
const form = new FormData()
|
||||
|
||||
form.set("field", "Some value")
|
||||
form.set("file", new File(["File content goes here"], "file.txt"))
|
||||
|
||||
const encoder = new FormDataEncoder(form)
|
||||
|
||||
const options = {
|
||||
method: "post",
|
||||
headers: encoder.headers,
|
||||
body: Readable.from(encoder)
|
||||
}
|
||||
|
||||
await fetch("https://httpbin.org/post", options)
|
||||
```
|
||||
|
||||
3. Because the Encoder is iterable (it has both Symbol.asyncIterator and Symbol.iterator methods), you can use it with different targets. Let's say you want to convert FormData content into `Blob`, for that you can write a function like this:
|
||||
|
||||
```js
|
||||
import {Readable} from "stream"
|
||||
|
||||
import {FormDataEncoder} from "form-data-encoder"
|
||||
import {FormData, File, Blob} from "formdata-node"
|
||||
import {fileFromPath} from "formdata-node/file-from-path"
|
||||
|
||||
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"))
|
||||
|
||||
// Note 1: When using with native Blob or fetch-blob@2 you might also need to generate boundary string for your FormDataEncoder instance
|
||||
// because Blob will lowercase value of the `type` option and default boundary generator produces a string with both lower and upper cased alphabetical characters. Math.random() should be enough to fix this:
|
||||
// const encoder = new FormDataEncoder(form, String(Math.random()))
|
||||
const encoder = new FormDataEncoder(form)
|
||||
|
||||
const options = {
|
||||
method: "post",
|
||||
|
||||
// Note 2: To use this approach with fetch-blob@2 you probably gonna need to convert the encoder parts output to an array first:
|
||||
// new Blob([...encoder], {type: encoder.contentType})
|
||||
body: new Blob(encoder, {type: encoder.contentType})
|
||||
}
|
||||
|
||||
const response = await fetch("https://httpbin.org/post", options)
|
||||
|
||||
console.log(await response.json())
|
||||
```
|
||||
|
||||
4. Here's FormData to Blob conversion with async-iterator approach:
|
||||
|
||||
```js
|
||||
import {FormData} from "formdata-polyfill/esm-min.js"
|
||||
import {FormDataEncoder} from "form-data-encoder"
|
||||
import {blobFrom} from "fetch-blob/from.js"
|
||||
|
||||
import Blob from "fetch-blob"
|
||||
import fetch from "node-fetch"
|
||||
|
||||
// This approach may require much more RAM compared to the previous one, but it works too.
|
||||
async function toBlob(form) {
|
||||
const encoder = new Encoder(form)
|
||||
const chunks = []
|
||||
|
||||
for await (const chunk of encoder) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
return new Blob(chunks, {type: encoder.contentType})
|
||||
}
|
||||
|
||||
const form = new FormData()
|
||||
|
||||
form.set("name", "John Doe")
|
||||
form.set("avatar", await blobFrom("path/to/an/avatar.png"), "avatar.png")
|
||||
|
||||
const options = {
|
||||
method: "post",
|
||||
body: await toBlob(form)
|
||||
}
|
||||
|
||||
await fetch("https://httpbin.org/post", options)
|
||||
```
|
||||
|
||||
5. Another way to convert FormData parts to blob using `form-data-encoder` is making a Blob-ish class:
|
||||
|
||||
```js
|
||||
import {Readable} from "stream"
|
||||
|
||||
import {FormDataEncoder} from "form-data-encoder"
|
||||
import {FormData} from "formdata-polyfill/esm-min.js"
|
||||
import {blobFrom} from "fetch-blob/from.js"
|
||||
|
||||
import Blob from "fetch-blob"
|
||||
import fetch from "node-fetch"
|
||||
|
||||
class BlobDataItem {
|
||||
constructor(encoder) {
|
||||
this.#encoder = encoder
|
||||
this.#size = encoder.headers["Content-Length"]
|
||||
this.#type = encoder.headers["Content-Type"]
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.#type
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.#size
|
||||
}
|
||||
|
||||
stream() {
|
||||
return Readable.from(this.#encoder)
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return "Blob"
|
||||
}
|
||||
}
|
||||
|
||||
const form = new FormData()
|
||||
|
||||
form.set("name", "John Doe")
|
||||
form.set("avatar", await blobFrom("path/to/an/avatar.png"), "avatar.png")
|
||||
|
||||
const encoder = new FormDataEncoder(form)
|
||||
|
||||
// Note that node-fetch@2 performs more strictness tests for Blob objects, so you may need to do extra steps before you set up request body (like, maybe you'll need to instaniate a Blob with BlobDataItem as one of its blobPart)
|
||||
const blob = new BlobDataItem(enocoder) // or new Blob([new BlobDataItem(enocoder)], {type: encoder.contentType})
|
||||
|
||||
const options = {
|
||||
method: "post",
|
||||
body: blob
|
||||
}
|
||||
|
||||
await fetch("https://httpbin.org/post", options)
|
||||
```
|
||||
|
||||
6. In this example we will pull FormData content into the ReadableStream:
|
||||
|
||||
```js
|
||||
// This module is only necessary when you targeting Node.js or need web streams that implement Symbol.asyncIterator
|
||||
import {ReadableStream} from "web-streams-polyfill/ponyfill/es2018"
|
||||
|
||||
import {FormDataEncoder} from "form-data-encoder"
|
||||
import {FormData} from "formdata-node"
|
||||
|
||||
import fetch from "node-fetch"
|
||||
|
||||
function toReadableStream(encoder) {
|
||||
const iterator = encoder.encode()
|
||||
|
||||
return new ReadableStream({
|
||||
async pull(controller) {
|
||||
const {value, done} = await iterator.next()
|
||||
|
||||
if (done) {
|
||||
return controller.close()
|
||||
}
|
||||
|
||||
controller.enqueue(value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const form = new FormData()
|
||||
|
||||
form.set("field", "My hovercraft is full of eels")
|
||||
|
||||
const encoder = new FormDataEncoder(form)
|
||||
|
||||
const options = {
|
||||
method: "post",
|
||||
headers: encoder.headers,
|
||||
body: toReadableStream(encoder)
|
||||
}
|
||||
|
||||
// Note that this example requires `fetch` to support Symbol.asyncIterator, which node-fetch lacks of (but will support eventually)
|
||||
await fetch("https://httpbin.org/post", options)
|
||||
```
|
||||
|
||||
7. Speaking of async iterables - if HTTP client supports them, you can use encoder like this:
|
||||
|
||||
```js
|
||||
import {FormDataEncoder} from "form-data-encoder"
|
||||
import {FormData} from "formdata-node"
|
||||
|
||||
import fetch from "node-fetch"
|
||||
|
||||
const form = new FormData()
|
||||
|
||||
form.set("field", "My hovercraft is full of eels")
|
||||
|
||||
const encoder = new FormDataEncoder(form)
|
||||
|
||||
const options = {
|
||||
method: "post",
|
||||
headers: encoder.headers,
|
||||
body: encoder
|
||||
}
|
||||
|
||||
await fetch("https://httpbin.org/post", options)
|
||||
```
|
||||
|
||||
8. ...And for those client whose supporting form-data-encoder out of the box, the usage will be much, much more simpler:
|
||||
|
||||
```js
|
||||
import {FormData} from "formdata-node" // Or any other spec-compatible implementation
|
||||
|
||||
import fetch from "node-fetch"
|
||||
|
||||
const form = new FormData()
|
||||
|
||||
form.set("field", "My hovercraft is full of eels")
|
||||
|
||||
const options = {
|
||||
method: "post",
|
||||
body: form
|
||||
}
|
||||
|
||||
// Note that node-fetch does NOT support form-data-encoder
|
||||
await fetch("https://httpbin.org/post", options)
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `class FormDataEncoder`
|
||||
|
||||
##### `constructor(form[, boundary, options]) -> {FormDataEncoder}`
|
||||
|
||||
- **{FormDataLike}** form - FormData object to encode. This object must be a spec-compatible FormData implementation.
|
||||
- **{string}** [boundary] - An optional boundary string that will be used by the encoder. If there's no boundary string is present, FormDataEncoder will generate it automatically.
|
||||
- **{object}** [options] - FormDataEncoder options.
|
||||
- **{boolean}** [options.enableAdditionalHeaders = false] - When enabled, the encoder will emit additional per part headers, such as `Content-Length`. Please note that the web clients do not include these, so when enabled this option might cause an error if `multipart/form-data` does not consider additional headers.
|
||||
|
||||
Creates a `multipart/form-data` encoder.
|
||||
|
||||
#### Instance properties
|
||||
|
||||
##### `boundary -> {string}`
|
||||
|
||||
Returns boundary string.
|
||||
|
||||
##### `contentType -> {string}`
|
||||
|
||||
Returns Content-Type header.
|
||||
|
||||
##### `contentLength -> {string}`
|
||||
|
||||
Return Content-Length header.
|
||||
|
||||
##### `headers -> {object}`
|
||||
|
||||
Returns headers object with Content-Type and Content-Length header.
|
||||
|
||||
#### Instance methods
|
||||
|
||||
##### `values() -> {Generator<Uint8Array | FileLike, void, undefined>}`
|
||||
|
||||
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.
|
||||
|
||||
##### `encode() -> {AsyncGenerator<Uint8Array, void, undefined>}`
|
||||
|
||||
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).
|
||||
|
||||
##### `[Symbol.iterator]() -> {Generator<Uint8Array | FileLike, void, undefined>}`
|
||||
|
||||
An alias for `Encoder#values()` method.
|
||||
|
||||
##### `[Symbol.asyncIterator]() -> {AsyncGenerator<Uint8Array, void, undefined>}`
|
||||
|
||||
An alias for `Encoder#encode()` method.
|
||||
|
||||
### `isFile(value) -> {boolean}`
|
||||
|
||||
Check if a value is File-ish object.
|
||||
|
||||
- **{unknown}** value - a value to test
|
||||
|
||||
### `isFormData(value) -> {boolean}`
|
||||
|
||||
Check if a value is FormData-ish object.
|
||||
|
||||
- **{unknown}** value - a value to test
|
||||
Reference in New Issue
Block a user