opusdev/vector-similarity-api
1
1import util from 'util';2import {Readable} from 'stream';3import utils from "../utils.js";4import readBlob from "./readBlob.js";5import platform from "../platform/index.js";6 7const BOUNDARY_ALPHABET = platform.ALPHABET.ALPHA_DIGIT + '-_';8 9const textEncoder = typeof TextEncoder === 'function' ? new TextEncoder() : new util.TextEncoder();10 11const CRLF = '\r\n';12const CRLF_BYTES = textEncoder.encode(CRLF);13const CRLF_BYTES_COUNT = 2;14 15class FormDataPart {16 constructor(name, value) {17 const {escapeName} = this.constructor;18 const isStringValue = utils.isString(value);19 20 let headers = `Content-Disposition: form-data; name="${escapeName(name)}"${21 !isStringValue && value.name ? `; filename="${escapeName(value.name)}"` : ''22 }${CRLF}`;23 24 if (isStringValue) {25 value = textEncoder.encode(String(value).replace(/\r?\n|\r\n?/g, CRLF));26 } else {27 headers += `Content-Type: ${value.type || "application/octet-stream"}${CRLF}`28 }29 30 this.headers = textEncoder.encode(headers + CRLF);31 32 this.contentLength = isStringValue ? value.byteLength : value.size;33 34 this.size = this.headers.byteLength + this.contentLength + CRLF_BYTES_COUNT;35 36 this.name = name;37 this.value = value;38 }39 40 async *encode(){41 yield this.headers;42 43 const {value} = this;44 45 if(utils.isTypedArray(value)) {46 yield value;47 } else {48 yield* readBlob(value);49 }50 51 yield CRLF_BYTES;52 }53 54 static escapeName(name) {55 return String(name).replace(/[\r\n"]/g, (match) => ({56 '\r' : '%0D',57 '\n' : '%0A',58 '"' : '%22',59 }[match]));60 }61}62 63const formDataToStream = (form, headersHandler, options) => {64 const {65 tag = 'form-data-boundary',66 size = 25,67 boundary = tag + '-' + platform.generateString(size, BOUNDARY_ALPHABET)68 } = options || {};69 70 if(!utils.isFormData(form)) {71 throw TypeError('FormData instance required');72 }73 74 if (boundary.length < 1 || boundary.length > 70) {75 throw Error('boundary must be 10-70 characters long')76 }77 78 const boundaryBytes = textEncoder.encode('--' + boundary + CRLF);79 const footerBytes = textEncoder.encode('--' + boundary + '--' + CRLF);80 let contentLength = footerBytes.byteLength;81 82 const parts = Array.from(form.entries()).map(([name, value]) => {83 const part = new FormDataPart(name, value);84 contentLength += part.size;85 return part;86 });87 88 contentLength += boundaryBytes.byteLength * parts.length;89 90 contentLength = utils.toFiniteNumber(contentLength);91 92 const computedHeaders = {93 'Content-Type': `multipart/form-data; boundary=${boundary}`94 }95 96 if (Number.isFinite(contentLength)) {97 computedHeaders['Content-Length'] = contentLength;98 }99 100 headersHandler && headersHandler(computedHeaders);101 102 return Readable.from((async function *() {103 for(const part of parts) {104 yield boundaryBytes;105 yield* part.encode();106 }107 108 yield footerBytes;109 })());110};111 112export default formDataToStream;113 