{"version":3,"sources":["../../../src/uploads/fetchAPI-multipart/processMultipart.ts"],"sourcesContent":["import type { Readable } from 'stream'\n\nimport Busboy from 'busboy'\nimport { status as httpStatus } from 'http-status'\n\nimport type { FetchAPIFileUploadOptions } from '../../config/types.js'\nimport type { FetchAPIFileUploadResponse } from './index.js'\n\nimport { APIError } from '../../errors/APIError.js'\nimport { fileFactory } from './fileFactory.js'\nimport { memHandler, tempFileHandler } from './handlers.js'\nimport { processNested } from './processNested.js'\nimport { createUploadTimer } from './uploadTimer.js'\nimport { buildFields, debugLog, isFunc, parseFileName } from './utilities.js'\n\nconst waitFlushProperty = Symbol('wait flush property symbol')\n\ndeclare global {\n  interface Request {\n    [waitFlushProperty]?: Promise<any>[]\n  }\n}\n\ntype ProcessMultipart = (args: {\n  options: FetchAPIFileUploadOptions\n  request: Request\n}) => Promise<FetchAPIFileUploadResponse>\nexport const processMultipart: ProcessMultipart = async ({ options, request }) => {\n  let parsingRequest = true\n\n  let shouldAbortProccessing = false\n  let fileCount = 0\n  let filesCompleted = 0\n  let allFilesHaveResolved: (value?: unknown) => void\n  let failedResolvingFiles: (err: Error) => void\n  let busboyFinishedResolve: () => void\n  let busboyFinishedReject: (err: Error) => void\n\n  const allFilesComplete = new Promise((res, rej) => {\n    allFilesHaveResolved = res\n    failedResolvingFiles = rej\n  })\n\n  const busboyFinished = new Promise<void>((resolve, reject) => {\n    busboyFinishedResolve = resolve\n    busboyFinishedReject = reject\n  })\n\n  const result: FetchAPIFileUploadResponse = {\n    fields: undefined!,\n    files: undefined!,\n  }\n\n  const headersObject: Record<string, string> = {}\n  request.headers.forEach((value, name) => {\n    headersObject[name] = value\n  })\n\n  const reader = request.body?.getReader()\n\n  const busboy = Busboy({ ...options, headers: headersObject })\n\n  function abortAndDestroyFile(file: Readable, err: APIError) {\n    file.destroy()\n    shouldAbortProccessing = true\n    failedResolvingFiles(err)\n  }\n\n  // Build multipart req.body fields\n  busboy.on('field', (field, val) => {\n    result.fields = buildFields(result.fields, field, val)\n  })\n\n  // Build req.files fields\n  busboy.on('file', (field, file, info) => {\n    fileCount += 1\n    // Parse file name(cutting huge names, decoding, etc..).\n    const { encoding, filename: name, mimeType: mime } = info\n    const filename = parseFileName(options, name)\n\n    const inferredMimeType =\n      (filename && filename.endsWith('.glb') && 'model/gltf-binary') ||\n      (filename && filename.endsWith('.gltf') && 'model/gltf+json') ||\n      mime\n\n    // Define methods and handlers for upload process.\n    const { cleanup, complete, dataHandler, getFilePath, getFileSize, getHash, getWritePromise } =\n      options.useTempFiles\n        ? tempFileHandler(options, field, filename) // Upload into temporary file.\n        : memHandler(options, field, filename) // Upload into RAM.\n\n    const writePromise = options.useTempFiles\n      ? getWritePromise().catch(() => {\n          busboy.end()\n          cleanup()\n        })\n      : getWritePromise()\n\n    const uploadTimer = createUploadTimer(options.uploadTimeout, () => {\n      return abortAndDestroyFile(\n        file,\n        new APIError(`Upload timeout for ${field}->${filename}, bytes:${getFileSize()}`),\n      )\n    })\n\n    file.on('limit', () => {\n      debugLog(options, `Size limit reached for ${field}->${filename}, bytes:${getFileSize()}`)\n      uploadTimer.clear()\n\n      if (isFunc(options.limitHandler)) {\n        options.limitHandler({ request, size: getFileSize() })\n      }\n\n      // Return error and cleanup files if abortOnLimit set.\n      if (options.abortOnLimit) {\n        debugLog(options, `Upload file size limit reached ${field}->${filename}.`)\n        cleanup()\n        abortAndDestroyFile(\n          file,\n          new APIError(options.responseOnLimit!, httpStatus.REQUEST_ENTITY_TOO_LARGE, {\n            size: getFileSize(),\n          }),\n        )\n      }\n    })\n\n    file.on('data', (data) => {\n      uploadTimer.set()\n      dataHandler(data)\n    })\n\n    file.on('end', () => {\n      const size = getFileSize()\n      debugLog(options, `Upload finished ${field}->${filename}, bytes:${size}`)\n      uploadTimer.clear()\n\n      if (!name && size === 0) {\n        fileCount -= 1\n        if (options.useTempFiles) {\n          cleanup()\n          debugLog(options, `Removing the empty file ${field}->${filename}`)\n        }\n        return debugLog(options, `Don't add file instance if original name and size are empty`)\n      }\n\n      filesCompleted += 1\n\n      result.files = buildFields(\n        result.files,\n        field,\n        fileFactory(\n          {\n            name: filename,\n            buffer: complete(),\n            encoding,\n            hash: getHash(),\n            mimetype: inferredMimeType,\n            size,\n            tempFilePath: getFilePath(),\n            truncated: Boolean('truncated' in file && file.truncated) || false,\n          },\n          options,\n        ),\n      )\n\n      if (!request[waitFlushProperty]) {\n        request[waitFlushProperty] = []\n      }\n      request[waitFlushProperty].push(writePromise)\n\n      if (filesCompleted === fileCount) {\n        allFilesHaveResolved()\n      }\n    })\n\n    file.on('error', (err) => {\n      uploadTimer.clear()\n      debugLog(options, `File Error: ${err.message}`)\n      cleanup()\n      failedResolvingFiles(err)\n    })\n\n    // Start upload process.\n    debugLog(options, `New upload started ${field}->${filename}, bytes:${getFileSize()}`)\n    uploadTimer.set()\n  })\n\n  busboy.on('finish', async () => {\n    debugLog(options, `Busboy finished parsing request.`)\n    if (options.parseNested) {\n      result.fields = processNested(result.fields)\n      result.files = processNested(result.files)\n    }\n\n    if (request[waitFlushProperty]) {\n      try {\n        await Promise.all(request[waitFlushProperty]).then(() => {\n          delete request[waitFlushProperty]\n        })\n      } catch (err) {\n        debugLog(options, `Error waiting for file write promises: ${err}`)\n      }\n    }\n\n    busboyFinishedResolve()\n  })\n\n  busboy.on(\n    'error',\n    (err = new APIError('Busboy error parsing multipart request', httpStatus.BAD_REQUEST)) => {\n      debugLog(options, `Busboy error`)\n      const busboyError =\n        err instanceof Error\n          ? err\n          : new APIError('Busboy error parsing multipart request', httpStatus.BAD_REQUEST)\n\n      busboyFinishedReject(busboyError)\n    },\n  )\n\n  while (parsingRequest) {\n    const { done, value } = await reader!.read()\n\n    if (done) {\n      parsingRequest = false\n      busboy.end()\n    }\n\n    if (value && !shouldAbortProccessing) {\n      busboy.write(value)\n    }\n  }\n\n  if (fileCount !== 0) {\n    await allFilesComplete.catch((e) => {\n      throw e\n    })\n  }\n\n  await busboyFinished\n\n  return result\n}\n"],"names":["Busboy","status","httpStatus","APIError","fileFactory","memHandler","tempFileHandler","processNested","createUploadTimer","buildFields","debugLog","isFunc","parseFileName","waitFlushProperty","Symbol","processMultipart","options","request","parsingRequest","shouldAbortProccessing","fileCount","filesCompleted","allFilesHaveResolved","failedResolvingFiles","busboyFinishedResolve","busboyFinishedReject","allFilesComplete","Promise","res","rej","busboyFinished","resolve","reject","result","fields","undefined","files","headersObject","headers","forEach","value","name","reader","body","getReader","busboy","abortAndDestroyFile","file","err","destroy","on","field","val","info","encoding","filename","mimeType","mime","inferredMimeType","endsWith","cleanup","complete","dataHandler","getFilePath","getFileSize","getHash","getWritePromise","useTempFiles","writePromise","catch","end","uploadTimer","uploadTimeout","clear","limitHandler","size","abortOnLimit","responseOnLimit","REQUEST_ENTITY_TOO_LARGE","data","set","buffer","hash","mimetype","tempFilePath","truncated","Boolean","push","message","parseNested","all","then","BAD_REQUEST","busboyError","Error","done","read","write","e"],"mappings":"AAEA,OAAOA,YAAY,SAAQ;AAC3B,SAASC,UAAUC,UAAU,QAAQ,cAAa;AAKlD,SAASC,QAAQ,QAAQ,2BAA0B;AACnD,SAASC,WAAW,QAAQ,mBAAkB;AAC9C,SAASC,UAAU,EAAEC,eAAe,QAAQ,gBAAe;AAC3D,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,iBAAiB,QAAQ,mBAAkB;AACpD,SAASC,WAAW,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,aAAa,QAAQ,iBAAgB;AAE7E,MAAMC,oBAAoBC,OAAO;AAYjC,OAAO,MAAMC,mBAAqC,OAAO,EAAEC,OAAO,EAAEC,OAAO,EAAE;IAC3E,IAAIC,iBAAiB;IAErB,IAAIC,yBAAyB;IAC7B,IAAIC,YAAY;IAChB,IAAIC,iBAAiB;IACrB,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IAEJ,MAAMC,mBAAmB,IAAIC,QAAQ,CAACC,KAAKC;QACzCP,uBAAuBM;QACvBL,uBAAuBM;IACzB;IAEA,MAAMC,iBAAiB,IAAIH,QAAc,CAACI,SAASC;QACjDR,wBAAwBO;QACxBN,uBAAuBO;IACzB;IAEA,MAAMC,SAAqC;QACzCC,QAAQC;QACRC,OAAOD;IACT;IAEA,MAAME,gBAAwC,CAAC;IAC/CpB,QAAQqB,OAAO,CAACC,OAAO,CAAC,CAACC,OAAOC;QAC9BJ,aAAa,CAACI,KAAK,GAAGD;IACxB;IAEA,MAAME,SAASzB,QAAQ0B,IAAI,EAAEC;IAE7B,MAAMC,SAAS7C,OAAO;QAAE,GAAGgB,OAAO;QAAEsB,SAASD;IAAc;IAE3D,SAASS,oBAAoBC,IAAc,EAAEC,GAAa;QACxDD,KAAKE,OAAO;QACZ9B,yBAAyB;QACzBI,qBAAqByB;IACvB;IAEA,kCAAkC;IAClCH,OAAOK,EAAE,CAAC,SAAS,CAACC,OAAOC;QACzBnB,OAAOC,MAAM,GAAGzB,YAAYwB,OAAOC,MAAM,EAAEiB,OAAOC;IACpD;IAEA,yBAAyB;IACzBP,OAAOK,EAAE,CAAC,QAAQ,CAACC,OAAOJ,MAAMM;QAC9BjC,aAAa;QACb,wDAAwD;QACxD,MAAM,EAAEkC,QAAQ,EAAEC,UAAUd,IAAI,EAAEe,UAAUC,IAAI,EAAE,GAAGJ;QACrD,MAAME,WAAW3C,cAAcI,SAASyB;QAExC,MAAMiB,mBACJ,AAACH,YAAYA,SAASI,QAAQ,CAAC,WAAW,uBACzCJ,YAAYA,SAASI,QAAQ,CAAC,YAAY,qBAC3CF;QAEF,kDAAkD;QAClD,MAAM,EAAEG,OAAO,EAAEC,QAAQ,EAAEC,WAAW,EAAEC,WAAW,EAAEC,WAAW,EAAEC,OAAO,EAAEC,eAAe,EAAE,GAC1FlD,QAAQmD,YAAY,GAChB7D,gBAAgBU,SAASmC,OAAOI,UAAU,8BAA8B;WACxElD,WAAWW,SAASmC,OAAOI,UAAU,mBAAmB;;QAE9D,MAAMa,eAAepD,QAAQmD,YAAY,GACrCD,kBAAkBG,KAAK,CAAC;YACtBxB,OAAOyB,GAAG;YACVV;QACF,KACAM;QAEJ,MAAMK,cAAc/D,kBAAkBQ,QAAQwD,aAAa,EAAE;YAC3D,OAAO1B,oBACLC,MACA,IAAI5C,SAAS,CAAC,mBAAmB,EAAEgD,MAAM,EAAE,EAAEI,SAAS,QAAQ,EAAES,eAAe;QAEnF;QAEAjB,KAAKG,EAAE,CAAC,SAAS;YACfxC,SAASM,SAAS,CAAC,uBAAuB,EAAEmC,MAAM,EAAE,EAAEI,SAAS,QAAQ,EAAES,eAAe;YACxFO,YAAYE,KAAK;YAEjB,IAAI9D,OAAOK,QAAQ0D,YAAY,GAAG;gBAChC1D,QAAQ0D,YAAY,CAAC;oBAAEzD;oBAAS0D,MAAMX;gBAAc;YACtD;YAEA,sDAAsD;YACtD,IAAIhD,QAAQ4D,YAAY,EAAE;gBACxBlE,SAASM,SAAS,CAAC,+BAA+B,EAAEmC,MAAM,EAAE,EAAEI,SAAS,CAAC,CAAC;gBACzEK;gBACAd,oBACEC,MACA,IAAI5C,SAASa,QAAQ6D,eAAe,EAAG3E,WAAW4E,wBAAwB,EAAE;oBAC1EH,MAAMX;gBACR;YAEJ;QACF;QAEAjB,KAAKG,EAAE,CAAC,QAAQ,CAAC6B;YACfR,YAAYS,GAAG;YACflB,YAAYiB;QACd;QAEAhC,KAAKG,EAAE,CAAC,OAAO;YACb,MAAMyB,OAAOX;YACbtD,SAASM,SAAS,CAAC,gBAAgB,EAAEmC,MAAM,EAAE,EAAEI,SAAS,QAAQ,EAAEoB,MAAM;YACxEJ,YAAYE,KAAK;YAEjB,IAAI,CAAChC,QAAQkC,SAAS,GAAG;gBACvBvD,aAAa;gBACb,IAAIJ,QAAQmD,YAAY,EAAE;oBACxBP;oBACAlD,SAASM,SAAS,CAAC,wBAAwB,EAAEmC,MAAM,EAAE,EAAEI,UAAU;gBACnE;gBACA,OAAO7C,SAASM,SAAS,CAAC,2DAA2D,CAAC;YACxF;YAEAK,kBAAkB;YAElBY,OAAOG,KAAK,GAAG3B,YACbwB,OAAOG,KAAK,EACZe,OACA/C,YACE;gBACEqC,MAAMc;gBACN0B,QAAQpB;gBACRP;gBACA4B,MAAMjB;gBACNkB,UAAUzB;gBACViB;gBACAS,cAAcrB;gBACdsB,WAAWC,QAAQ,eAAevC,QAAQA,KAAKsC,SAAS,KAAK;YAC/D,GACArE;YAIJ,IAAI,CAACC,OAAO,CAACJ,kBAAkB,EAAE;gBAC/BI,OAAO,CAACJ,kBAAkB,GAAG,EAAE;YACjC;YACAI,OAAO,CAACJ,kBAAkB,CAAC0E,IAAI,CAACnB;YAEhC,IAAI/C,mBAAmBD,WAAW;gBAChCE;YACF;QACF;QAEAyB,KAAKG,EAAE,CAAC,SAAS,CAACF;YAChBuB,YAAYE,KAAK;YACjB/D,SAASM,SAAS,CAAC,YAAY,EAAEgC,IAAIwC,OAAO,EAAE;YAC9C5B;YACArC,qBAAqByB;QACvB;QAEA,wBAAwB;QACxBtC,SAASM,SAAS,CAAC,mBAAmB,EAAEmC,MAAM,EAAE,EAAEI,SAAS,QAAQ,EAAES,eAAe;QACpFO,YAAYS,GAAG;IACjB;IAEAnC,OAAOK,EAAE,CAAC,UAAU;QAClBxC,SAASM,SAAS,CAAC,gCAAgC,CAAC;QACpD,IAAIA,QAAQyE,WAAW,EAAE;YACvBxD,OAAOC,MAAM,GAAG3B,cAAc0B,OAAOC,MAAM;YAC3CD,OAAOG,KAAK,GAAG7B,cAAc0B,OAAOG,KAAK;QAC3C;QAEA,IAAInB,OAAO,CAACJ,kBAAkB,EAAE;YAC9B,IAAI;gBACF,MAAMc,QAAQ+D,GAAG,CAACzE,OAAO,CAACJ,kBAAkB,EAAE8E,IAAI,CAAC;oBACjD,OAAO1E,OAAO,CAACJ,kBAAkB;gBACnC;YACF,EAAE,OAAOmC,KAAK;gBACZtC,SAASM,SAAS,CAAC,uCAAuC,EAAEgC,KAAK;YACnE;QACF;QAEAxB;IACF;IAEAqB,OAAOK,EAAE,CACP,SACA,CAACF,MAAM,IAAI7C,SAAS,0CAA0CD,WAAW0F,WAAW,CAAC;QACnFlF,SAASM,SAAS,CAAC,YAAY,CAAC;QAChC,MAAM6E,cACJ7C,eAAe8C,QACX9C,MACA,IAAI7C,SAAS,0CAA0CD,WAAW0F,WAAW;QAEnFnE,qBAAqBoE;IACvB;IAGF,MAAO3E,eAAgB;QACrB,MAAM,EAAE6E,IAAI,EAAEvD,KAAK,EAAE,GAAG,MAAME,OAAQsD,IAAI;QAE1C,IAAID,MAAM;YACR7E,iBAAiB;YACjB2B,OAAOyB,GAAG;QACZ;QAEA,IAAI9B,SAAS,CAACrB,wBAAwB;YACpC0B,OAAOoD,KAAK,CAACzD;QACf;IACF;IAEA,IAAIpB,cAAc,GAAG;QACnB,MAAMM,iBAAiB2C,KAAK,CAAC,CAAC6B;YAC5B,MAAMA;QACR;IACF;IAEA,MAAMpE;IAEN,OAAOG;AACT,EAAC"}