{"version":3,"file":"partsManager.mjs","names":["client: S3","bucket: string","minPartSize: number","partUploadSemaphore: Semaphore","metadataManager: S3MetadataManager","fileOperations: S3FileOperations","generateCompleteTag: (value: 'false' | 'true') => string | undefined","params: AWS.ListPartsCommandInput","params: AWS.UploadPartCommandInput","promises: Promise<void>[]","pendingChunkFilepath: null | string"],"sources":["../../../../src/tus/stores/s3/partsManager.ts"],"sourcesContent":["import fs from 'node:fs'\nimport os from 'node:os'\nimport stream from 'node:stream'\n\nimport { NoSuchKey, NotFound, type S3 } from '@aws-sdk/client-s3'\nimport { StreamSplitter } from '@tus/utils'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudErrors, MediaCloudLogs } from '../../../types/errors'\n\nimport type AWS from '@aws-sdk/client-s3'\nimport type { Readable } from 'node:stream'\nimport type { IncompletePartInfo, TusUploadMetadata } from '../../../types'\nimport type { S3FileOperations } from './fileOperations'\nimport type { S3MetadataManager } from './metadataManager'\nimport type { Semaphore } from './semaphore'\n\ntype RetrievePartsArgs = {\n  id: string\n  partNumberMarker?: string\n}\n\ntype FinishMultipartUploadArgs = {\n  metadata: TusUploadMetadata\n  parts: Array<AWS.Part>\n}\n\ntype GetIncompletePartArgs = {\n  id: string\n}\n\ntype GetIncompletePartSizeArgs = {\n  id: string\n}\n\ntype DeleteIncompletePartArgs = {\n  id: string\n}\n\ntype DownloadIncompletePartArgs = {\n  id: string\n}\n\ntype UploadIncompletePartArgs = {\n  id: string\n  readStream: fs.ReadStream | Readable\n}\n\ntype UploadPartArgs = {\n  metadata: TusUploadMetadata\n  readStream: fs.ReadStream | Readable\n  partNumber: number\n}\n\ntype UploadPartsArgs = {\n  metadata: TusUploadMetadata\n  readStream: stream.Readable\n  currentPartNumber: number\n  offset: number\n}\n\nconst { log, throwError } = useErrorHandler()\n\nexport class S3PartsManager {\n  constructor(\n    private client: S3,\n    private bucket: string,\n    private minPartSize: number,\n    private partUploadSemaphore: Semaphore,\n    private metadataManager: S3MetadataManager,\n    private fileOperations: S3FileOperations,\n    private generateCompleteTag: (value: 'false' | 'true') => string | undefined\n  ) {}\n\n  /**\n   * Gets the number of complete parts/chunks already uploaded to S3.\n   * Retrieves only consecutive parts.\n   * @param args - The function arguments\n   * @param args.id - The upload ID\n   * @param args.partNumberMarker - Marker for pagination (optional)\n   * @returns Promise that resolves to array of uploaded parts\n   */\n  async retrieveParts(args: RetrievePartsArgs): Promise<Array<AWS.Part>> {\n    const { id, partNumberMarker } = args\n    const metadata = await this.metadataManager.getMetadata({ id })\n\n    if (!metadata['upload-id']) {\n      throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n      throw new Error() // This will never execute but satisfies TypeScript\n    }\n\n    const params: AWS.ListPartsCommandInput = {\n      Bucket: this.bucket,\n      Key: this.metadataManager.generatePartKey({\n        id,\n        prefix: metadata.file.metadata?.prefix ?? undefined,\n      }),\n      PartNumberMarker: partNumberMarker,\n      UploadId: metadata['upload-id'],\n    }\n\n    const data = await this.client.listParts(params)\n\n    let parts = data.Parts ?? []\n\n    if (data.IsTruncated) {\n      const rest = await this.retrieveParts({\n        id,\n        partNumberMarker: data.NextPartNumberMarker,\n      })\n      parts = [...parts, ...rest]\n    }\n\n    if (!partNumberMarker) {\n      parts.sort((a, b) => (a.PartNumber || 0) - (b.PartNumber || 0))\n    }\n\n    return parts\n  }\n\n  /**\n   * Completes a multipart upload on S3.\n   * This is where S3 concatenates all the uploaded parts.\n   * @param args - The function arguments\n   * @param args.metadata - The upload metadata\n   * @param args.parts - Array of uploaded parts to complete\n   * @returns Promise that resolves to the location URL (optional)\n   */\n  async finishMultipartUpload(\n    args: FinishMultipartUploadArgs\n  ): Promise<string | undefined> {\n    const { metadata, parts } = args\n\n    const params = {\n      Key: this.metadataManager.generatePartKey({\n        id: metadata.file.id,\n        prefix: metadata.file.metadata?.prefix ?? undefined,\n      }),\n      Bucket: this.bucket,\n      MultipartUpload: {\n        Parts: parts.map((part) => {\n          return {\n            ETag: part.ETag,\n            PartNumber: part.PartNumber,\n          }\n        }),\n      },\n      UploadId: metadata['upload-id'],\n    }\n\n    try {\n      const result = await this.client.completeMultipartUpload(params)\n      return result.Location\n    } catch (error) {\n      throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n      throw new Error() // This will never execute but satisfies TypeScript\n    }\n  }\n\n  /**\n   * Gets incomplete part from S3\n   * @param args - The function arguments\n   * @param args.id - The upload ID\n   * @returns Promise that resolves to readable stream or undefined if not found\n   */\n  async getIncompletePart(\n    args: GetIncompletePartArgs\n  ): Promise<Readable | undefined> {\n    const { id } = args\n\n    try {\n      const { file } = await this.metadataManager.getMetadata({ id })\n\n      const data = await this.client.getObject({\n        Bucket: this.bucket,\n        Key: this.metadataManager.generatePartKey({\n          id,\n          prefix: file?.metadata?.prefix ?? undefined,\n          isIncomplete: true,\n        }),\n      })\n      return data.Body as Readable\n    } catch (error) {\n      if (error instanceof NoSuchKey) {\n        return undefined\n      }\n      throw error\n    }\n  }\n\n  /**\n   * Gets the size of an incomplete part\n   * @param args - The function arguments\n   * @param args.id - The upload ID\n   * @returns Promise that resolves to part size or undefined if not found\n   */\n  async getIncompletePartSize(\n    args: GetIncompletePartSizeArgs\n  ): Promise<number | undefined> {\n    const { id } = args\n\n    try {\n      const { file } = await this.metadataManager.getMetadata({ id })\n\n      const data = await this.client.headObject({\n        Bucket: this.bucket,\n        Key: this.metadataManager.generatePartKey({\n          id,\n          isIncomplete: true,\n          prefix: file?.metadata?.prefix ?? undefined,\n        }),\n      })\n      return data.ContentLength\n    } catch (error) {\n      if (error instanceof NotFound) {\n        return undefined\n      }\n      throw error\n    }\n  }\n\n  /**\n   * Deletes an incomplete part\n   * @param args - The function arguments\n   * @param args.id - The upload ID\n   * @returns Promise that resolves when deletion is complete\n   */\n  async deleteIncompletePart(args: DeleteIncompletePartArgs): Promise<void> {\n    const { id } = args\n\n    try {\n      const { file } = await this.metadataManager.getMetadata({ id })\n\n      await this.client.deleteObject({\n        Bucket: this.bucket,\n        Key: this.metadataManager.generatePartKey({\n          id,\n          isIncomplete: true,\n          prefix: file?.metadata?.prefix ?? undefined,\n        }),\n      })\n    } catch (error) {\n      throwError({\n        ...MediaCloudErrors.S3_DELETE_ERROR,\n        cause: error,\n      })\n    }\n  }\n\n  /**\n   * Downloads incomplete part to temporary file\n   * @param args - The function arguments\n   * @param args.id - The upload ID\n   * @returns Promise that resolves to incomplete part info or undefined if not found\n   */\n  async downloadIncompletePart(\n    args: DownloadIncompletePartArgs\n  ): Promise<IncompletePartInfo | undefined> {\n    const { id } = args\n    const incompletePart = await this.getIncompletePart({ id })\n\n    if (!incompletePart) {\n      return\n    }\n    const filePath = await this.fileOperations.generateUniqueTmpFileName({\n      template: 'tus-s3-incomplete-part-',\n    })\n\n    try {\n      let incompletePartSize = 0\n\n      const byteCounterTransform = new stream.Transform({\n        transform(chunk, _, callback) {\n          incompletePartSize += chunk.length\n          callback(null, chunk)\n        },\n      })\n\n      // Write to temporary file\n      await stream.promises.pipeline(\n        incompletePart,\n        byteCounterTransform,\n        fs.createWriteStream(filePath)\n      )\n\n      const createReadStream = (options: { cleanUpOnEnd: boolean }) => {\n        const fileReader = fs.createReadStream(filePath)\n\n        if (options.cleanUpOnEnd) {\n          fileReader.on('end', () => {\n            fs.unlink(filePath, () => {})\n          })\n\n          fileReader.on('error', (error) => {\n            fileReader.destroy(error)\n            fs.unlink(filePath, () => {})\n          })\n        }\n\n        return fileReader\n      }\n\n      return {\n        createReader: createReadStream,\n        path: filePath,\n        size: incompletePartSize,\n      }\n    } catch (err) {\n      fs.promises.rm(filePath).catch(() => {})\n      throw err\n    }\n  }\n\n  /**\n   * Uploads an incomplete part\n   * @param args - The function arguments\n   * @param args.id - The upload ID\n   * @param args.readStream - The stream to read data from\n   * @returns Promise that resolves to the ETag of the uploaded part\n   */\n  async uploadIncompletePart(args: UploadIncompletePartArgs): Promise<string> {\n    const { id, readStream } = args\n    try {\n      const { file } = await this.metadataManager.getMetadata({ id })\n\n      const data = await this.client.putObject({\n        Body: readStream,\n        Bucket: this.bucket,\n        Key: this.metadataManager.generatePartKey({\n          id,\n          isIncomplete: true,\n          prefix: file?.metadata?.prefix ?? undefined,\n        }),\n        Tagging: this.generateCompleteTag('false'),\n      })\n      log(MediaCloudLogs.S3_STORE_INCOMPLETE_PART_UPLOADED)\n      return data.ETag as string\n    } catch (error) {\n      throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n      throw error\n    }\n  }\n\n  /**\n   * Uploads a single part\n   * @param args - The function arguments\n   * @param args.metadata - The upload metadata\n   * @param args.readStream - The stream to read data from\n   * @param args.partNumber - The part number to upload\n   * @returns Promise that resolves to the ETag of the uploaded part\n   */\n  async uploadPart(args: UploadPartArgs): Promise<AWS.Part> {\n    const { metadata, readStream, partNumber } = args\n    const permit = await this.partUploadSemaphore.acquire()\n\n    if (!metadata['upload-id']) {\n      throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n      throw new Error() // This will never execute but satisfies TypeScript\n    }\n\n    const params: AWS.UploadPartCommandInput = {\n      Body: readStream,\n      Bucket: this.bucket,\n      Key: this.metadataManager.generatePartKey({\n        id: metadata.file.id,\n        prefix: metadata.file.metadata?.prefix ?? undefined,\n      }),\n      PartNumber: partNumber,\n      UploadId: metadata['upload-id'],\n    }\n\n    try {\n      const data = await this.client.uploadPart(params)\n      return { ETag: data.ETag, PartNumber: partNumber }\n    } catch (error) {\n      throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n      throw error\n    } finally {\n      permit()\n    }\n  }\n\n  /**\n   * Uploads a stream to s3 using multiple parts\n   * @param args - The function arguments\n   * @param args.metadata - The upload metadata\n   * @param args.readStream - The stream to read data from\n   * @param args.currentPartNumber - The current part number to start from\n   * @param args.offset - The byte offset to start from\n   * @returns Promise that resolves to the number of bytes uploaded\n   */\n  async uploadParts(args: UploadPartsArgs): Promise<number> {\n    const { metadata, readStream, offset: initialOffset } = args\n    let { currentPartNumber } = args\n    let offset = initialOffset\n    const size = metadata.file.size\n    const promises: Promise<void>[] = []\n    let pendingChunkFilepath: null | string = null\n    let bytesUploaded = 0\n\n    const splitterStream = new StreamSplitter({\n      chunkSize: this.fileOperations.calculateOptimalPartSize({ size }),\n      directory: os.tmpdir(),\n    })\n      .on('chunkStarted', (filepath) => {\n        pendingChunkFilepath = filepath\n      })\n      .on('chunkFinished', ({ path, size: partSize }) => {\n        pendingChunkFilepath = null\n\n        const partNumber = currentPartNumber++\n\n        offset += partSize\n\n        const isFinalPart = size === offset\n\n        const uploadChunk = async () => {\n          try {\n            // Only the first chunk of each PATCH request can prepend\n            // an incomplete part (last chunk) from the previous request.\n            const readable = fs.createReadStream(path)\n            readable.on('error', function (error) {\n              readable.destroy(error)\n              fs.unlink(path, () => {})\n            })\n\n            switch (true) {\n              case partSize >= this.minPartSize || isFinalPart:\n                await this.uploadPart({\n                  metadata,\n                  readStream: readable,\n                  partNumber,\n                })\n                break\n              default:\n                await this.uploadIncompletePart({\n                  id: metadata.file.id,\n                  readStream: readable,\n                })\n                break\n            }\n\n            bytesUploaded += partSize\n          } catch (error) {\n            // Destroy the splitter to stop processing more chunks\n            const mappedError =\n              error instanceof Error ? error : new Error(String(error))\n            splitterStream.destroy(mappedError)\n            throw mappedError\n          } finally {\n            fs.promises.rm(path).catch(function () {})\n          }\n        }\n\n        const deferred = uploadChunk()\n\n        promises.push(deferred)\n      })\n\n    try {\n      await stream.promises.pipeline(readStream, splitterStream)\n    } catch (error) {\n      if (pendingChunkFilepath !== null) {\n        try {\n          await fs.promises.rm(pendingChunkFilepath)\n        } catch {\n          log(MediaCloudLogs.S3_STORE_CHUNK_REMOVAL_FAILED)\n        }\n      }\n      const mappedError =\n        error instanceof Error ? error : new Error(String(error))\n      promises.push(Promise.reject(mappedError))\n    } finally {\n      // Wait for all promises\n      await Promise.allSettled(promises)\n      // Reject the promise if any of the promises reject\n      await Promise.all(promises)\n    }\n\n    return bytesUploaded\n  }\n}\n"],"mappings":"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"}