diff --git a/packages/@uppy/aws-s3/src/S3Uploader.ts b/packages/@uppy/aws-s3/src/S3Uploader.ts index bc0818a83..9dcf8f5a7 100644 --- a/packages/@uppy/aws-s3/src/S3Uploader.ts +++ b/packages/@uppy/aws-s3/src/S3Uploader.ts @@ -214,7 +214,7 @@ export default class S3Uploader { throw new Error('Missing S3 object key for aborting upload') } this.#options.s3Client - .abortMultipartUpload(this.#key, this.#uploadId) + .abortMultipartUpload({ key: this.#key, uploadId: this.#uploadId }) .catch((abortErr) => { this.#options.log?.(abortErr, 'warning') }) @@ -238,10 +238,11 @@ export default class S3Uploader { if (!this.#key) { throw new Error('Missing S3 object key for resuming upload') } - const existingParts = await this.#options.s3Client.listParts( - this.#uploadId, - this.#key, - ) + const existingParts = await this.#options.s3Client.listParts({ + uploadId: this.#uploadId, + key: this.#key, + signal: this.#abortController?.signal, + }) // Sync local state with S3 - mark already-uploaded parts for (const part of existingParts) { const chunkIndex = part.partNumber - 1 @@ -259,17 +260,17 @@ export default class S3Uploader { const signal = this.#abortController?.signal signal?.throwIfAborted() - const { location, key } = await this.#options.s3Client.putObject( - this.#options.key, - this.#data, - this.#options.file.type || 'application/octet-stream', - this.#options.metadata, - (bytesUploaded: number) => { + const { location, key } = await this.#options.s3Client.putObject({ + key: this.#options.key, + data: this.#data, + fileType: this.#options.file.type || 'application/octet-stream', + metadata: this.#options.metadata, + onProgress: (bytesUploaded: number) => { this.#chunkState[0].uploaded = bytesUploaded this.#onProgress() }, signal, - ) + }) this.#onSuccess({ location, @@ -281,12 +282,15 @@ export default class S3Uploader { const signal = this.#abortController?.signal signal?.throwIfAborted() + // We deliberately don't pass the abort signal into the create request: if + // it were cancelled mid-flight, S3 might still create the upload while we + // never receive the uploadId — an orphan we couldn't clean up (see below). const { uploadId, key } = - await this.#options.s3Client.createMultipartUpload( - this.#options.key, - this.#options.file.type || 'application/octet-stream', - this.#options.metadata, - ) + await this.#options.s3Client.createMultipartUpload({ + key: this.#options.key, + fileType: this.#options.file.type || 'application/octet-stream', + metadata: this.#options.metadata, + }) if (key == null) { throw new Error( 'S3 client did not return object key for multipart upload', @@ -330,17 +334,17 @@ export default class S3Uploader { if (this.#key == null) { throw new Error('Missing S3 object key for uploading part') } - const { etag } = await this.#options.s3Client.uploadPart( - this.#key, - this.#uploadId!, - chunkData, + const { etag } = await this.#options.s3Client.uploadPart({ + key: this.#key, + uploadId: this.#uploadId!, + data: chunkData, partNumber, - (bytesUploaded: number) => { + onProgress: (bytesUploaded: number) => { this.#chunkState[chunkIndex].uploaded = bytesUploaded this.#onProgress() }, signal, - ) + }) // after part finished uploading, update chunk state this.#chunkState[i].uploaded = chunk.size @@ -366,11 +370,12 @@ export default class S3Uploader { } const { location, key } = - await this.#options.s3Client.completeMultipartUpload( - this.#key, - this.#uploadId!, + await this.#options.s3Client.completeMultipartUpload({ + key: this.#key, + uploadId: this.#uploadId!, parts, - ) + signal, + }) this.#onSuccess({ location, diff --git a/packages/@uppy/aws-s3/src/s3-client/CompanionS3.ts b/packages/@uppy/aws-s3/src/s3-client/CompanionS3.ts index 9b1ae19fe..ea5da7ac5 100644 --- a/packages/@uppy/aws-s3/src/s3-client/CompanionS3.ts +++ b/packages/@uppy/aws-s3/src/s3-client/CompanionS3.ts @@ -46,14 +46,14 @@ class S3Companion extends S3Client { return this.xhr({ url, method, data, onProgress, signal, contentType }) } - public override async putObject( - keyIn: string, - data: Blob | File, - fileType: string = C.DEFAULT_STREAM_CONTENT_TYPE, - metadata: Record, - onProgress?: IT.OnProgressFn, - signal?: AbortSignal, - ) { + public override async putObject({ + key: keyIn, + data, + fileType = C.DEFAULT_STREAM_CONTENT_TYPE, + metadata = {}, + onProgress, + signal, + }: IT.PutObjectParams) { const response = await this._fetch( `/params?${new URLSearchParams({ filename: keyIn, type: fileType, ...Object.fromEntries(Object.entries(metadata).map(([k, v]) => [`metadata[${k}]`, String(v)])) })}`, ) @@ -81,11 +81,11 @@ class S3Companion extends S3Client { } } - public override async createMultipartUpload( - keyIn: string, - fileType: string = C.DEFAULT_STREAM_CONTENT_TYPE, - metadata: Record, - ) { + public override async createMultipartUpload({ + key: keyIn, + fileType = C.DEFAULT_STREAM_CONTENT_TYPE, + metadata, + }: IT.CreateMultipartUploadParams) { if (typeof fileType !== 'string') { throw new TypeError(`${C.ERROR_PREFIX}fileType must be a string`) } @@ -107,14 +107,14 @@ class S3Companion extends S3Client { return { uploadId, key } } - public override async uploadPart( - key: string, - uploadId: string, - data: XMLHttpRequestBodyInit, - partNumber: number, - onProgress?: IT.OnProgressFn, - signal?: AbortSignal, - ) { + public override async uploadPart({ + key, + uploadId, + data, + partNumber, + onProgress, + signal, + }: IT.UploadPartParams) { const response = await this._fetch( `/multipart/${encodeURIComponent(uploadId)}/${encodeURIComponent(partNumber)}?${new URLSearchParams({ key })}`, { @@ -142,10 +142,10 @@ class S3Companion extends S3Client { return { etag } } - public override async listParts( - uploadId: string, - key: string, - ): Promise { + public override async listParts({ + uploadId, + key, + }: IT.ListPartsParams): Promise { if (!uploadId) { throw new TypeError(C.ERROR_UPLOAD_ID_REQUIRED) } @@ -165,11 +165,11 @@ class S3Companion extends S3Client { })) } - public override async completeMultipartUpload( - key: string, - uploadId: string, - parts: Array, - ) { + public override async completeMultipartUpload({ + key, + uploadId, + parts, + }: IT.CompleteMultipartUploadParams) { const response = await this._fetch( `/multipart/${encodeURIComponent(uploadId)}/complete?${new URLSearchParams({ key })}`, { @@ -201,7 +201,10 @@ class S3Companion extends S3Client { } } - public override async abortMultipartUpload(key: string, uploadId: string) { + public override async abortMultipartUpload({ + key, + uploadId, + }: IT.AbortMultipartUploadParams) { await this._fetch( `/multipart/${encodeURIComponent(uploadId)}?${new URLSearchParams({ key })}`, { diff --git a/packages/@uppy/aws-s3/src/s3-client/S3Client.ts b/packages/@uppy/aws-s3/src/s3-client/S3Client.ts index 52465861a..b446cfca3 100644 --- a/packages/@uppy/aws-s3/src/s3-client/S3Client.ts +++ b/packages/@uppy/aws-s3/src/s3-client/S3Client.ts @@ -1,5 +1,4 @@ import { fetcher } from '@uppy/core/utils' -import * as C from './consts.js' import type * as IT from './types.js' class S3Client { @@ -115,14 +114,7 @@ class S3Client { }) } - public async putObject( - key: string, - data: XMLHttpRequestBodyInit, - fileType: string = C.DEFAULT_STREAM_CONTENT_TYPE, - metadata: Record, - onProgress?: IT.OnProgressFn, - signal?: AbortSignal, - ): Promise<{ + public async putObject(params: IT.PutObjectParams): Promise<{ location: string key: string etag: string | undefined @@ -131,10 +123,7 @@ class S3Client { } public async createMultipartUpload( - key: string, - fileType?: string, - // @ts-expect-error unused - metadata: Record, + params: IT.CreateMultipartUploadParams, ): Promise<{ uploadId: string key: string @@ -142,30 +131,18 @@ class S3Client { throw new Error('Not implemented') } - public async uploadPart( - key: string, - uploadId: string, - data: XMLHttpRequestBodyInit, - partNumber: number, - onProgress?: IT.OnProgressFn, - signal?: AbortSignal, - ): Promise<{ + public async uploadPart(params: IT.UploadPartParams): Promise<{ etag: string }> { throw new Error('Not implemented') } - public async listParts( - uploadId: string, - key: string, - ): Promise { + public async listParts(params: IT.ListPartsParams): Promise { throw new Error('Not implemented') } public async completeMultipartUpload( - key: string, - uploadId: string, - parts: IT.UploadPart[], + params: IT.CompleteMultipartUploadParams, ): Promise<{ location: string bucket: string | undefined @@ -176,13 +153,12 @@ class S3Client { } public async abortMultipartUpload( - key: string, - uploadId: string, + params: IT.AbortMultipartUploadParams, ): Promise { throw new Error('Not implemented') } - public async deleteObject(key: string): Promise { + public async deleteObject(params: IT.DeleteObjectParams): Promise { throw new Error('Not implemented') } } diff --git a/packages/@uppy/aws-s3/src/s3-client/S3mini.ts b/packages/@uppy/aws-s3/src/s3-client/S3mini.ts index 6fe8b4a23..54bf0cb3e 100644 --- a/packages/@uppy/aws-s3/src/s3-client/S3mini.ts +++ b/packages/@uppy/aws-s3/src/s3-client/S3mini.ts @@ -34,7 +34,7 @@ import * as U from './utils.js' * }, * }); * - * await s3.putObject('file.txt', 'Hello, World!'); + * await s3.putObject({ key: 'file.txt', data: 'Hello, World!' }); */ class S3mini extends S3Client { readonly endpoint?: URL @@ -175,20 +175,14 @@ class S3mini extends S3Client { /** * Uploads an object to S3 using XHR for progress tracking. - * @param key - Object key - * @param data - Data to upload (Blob, ArrayBuffer, Uint8Array, or string) - * @param fileType - Content type - * @param onProgress - Optional progress callback - * @param signal - Optional abort signal */ - public override async putObject( - key: string, - data: XMLHttpRequestBodyInit, - fileType: string = C.DEFAULT_STREAM_CONTENT_TYPE, - metadata?: Record, - onProgress?: IT.OnProgressFn, - signal?: AbortSignal, - ) { + public override async putObject({ + key, + data, + fileType = C.DEFAULT_STREAM_CONTENT_TYPE, + onProgress, + signal, + }: IT.PutObjectParams) { this._checkKey(key) const { xhr, url } = await this.request({ @@ -207,11 +201,12 @@ class S3mini extends S3Client { } /** Initiates a multipart upload and returns the upload ID. */ - public override async createMultipartUpload( - key: string, - fileType: string = C.DEFAULT_STREAM_CONTENT_TYPE, - metadata?: Record, // todo support metadata here too? - ) { + public override async createMultipartUpload({ + key, + fileType = C.DEFAULT_STREAM_CONTENT_TYPE, + signal, + }: IT.CreateMultipartUploadParams) { + // todo support metadata here too? this._checkKey(key) if (typeof fileType !== 'string') { throw new TypeError(`${C.ERROR_PREFIX}fileType must be a string`) @@ -220,6 +215,7 @@ class S3mini extends S3Client { const { xhr } = await this.request({ request: { method: 'POST', key }, contentType: fileType, + signal, }) const parsed = U.parseXml(xhr.responseText) as Record @@ -247,14 +243,14 @@ class S3mini extends S3Client { ) } - public override async uploadPart( - key: string, - uploadId: string, - data: XMLHttpRequestBodyInit, - partNumber: number, - onProgress?: IT.OnProgressFn, - signal?: AbortSignal, - ) { + public override async uploadPart({ + key, + uploadId, + data, + partNumber, + onProgress, + signal, + }: IT.UploadPartParams) { this._validateUploadPartParams(key, uploadId, partNumber) const { xhr } = await this.request({ @@ -379,16 +375,18 @@ class S3mini extends S3Client { } /** Lists uploaded parts for a multipart upload. */ - public override async listParts( - uploadId: string, - key: string, - ): Promise { + public override async listParts({ + uploadId, + key, + signal, + }: IT.ListPartsParams): Promise { this._checkKey(key) if (!uploadId) { throw new TypeError(C.ERROR_UPLOAD_ID_REQUIRED) } const { xhr } = await this.request({ request: { method: 'GET', key, uploadId }, + signal, }) const parsed = U.parseXml(xhr.responseText) as Record @@ -417,17 +415,19 @@ class S3mini extends S3Client { } /** Completes a multipart upload by combining all uploaded parts. */ - public override async completeMultipartUpload( - key: string, - uploadId: string, - parts: Array, - ) { + public override async completeMultipartUpload({ + key, + uploadId, + parts, + signal, + }: IT.CompleteMultipartUploadParams) { const xmlBody = this._buildCompleteMultipartUploadXml(parts) const { xhr } = await this.request({ request: { method: 'POST', key, uploadId }, contentType: C.XML_CONTENT_TYPE, data: xmlBody, + signal, }) const parsed = U.parseXml(xhr.responseText) @@ -473,7 +473,11 @@ class S3mini extends S3Client { } /** Aborts a multipart upload and removes all uploaded parts. */ - public override async abortMultipartUpload(key: string, uploadId: string) { + public override async abortMultipartUpload({ + key, + uploadId, + signal, + }: IT.AbortMultipartUploadParams) { this._checkKey(key) if (!uploadId) { throw new TypeError(C.ERROR_UPLOAD_ID_REQUIRED) @@ -481,6 +485,7 @@ class S3mini extends S3Client { const { xhr } = await this.request({ request: { method: 'DELETE', key, uploadId }, + signal, }) const parsed = U.parseXml(xhr.responseText) as Record @@ -511,9 +516,10 @@ class S3mini extends S3Client { } /** Deletes an object from the bucket. Returns true on success. */ - public override async deleteObject(key: string) { + public override async deleteObject({ key, signal }: IT.DeleteObjectParams) { const { xhr } = await this.request({ request: { method: 'DELETE', key }, + signal, }) if (xhr.status !== 200 && xhr.status !== 204) { diff --git a/packages/@uppy/aws-s3/src/s3-client/types.ts b/packages/@uppy/aws-s3/src/s3-client/types.ts index 349386ed7..6045004e0 100644 --- a/packages/@uppy/aws-s3/src/s3-client/types.ts +++ b/packages/@uppy/aws-s3/src/s3-client/types.ts @@ -110,6 +110,62 @@ export interface UploadPart { etag: string } +/** Parameters for {@link S3Client.putObject}. */ +export interface PutObjectParams { + key: string + data: Blob | File + fileType?: string + metadata?: Record + onProgress?: OnProgressFn + signal?: AbortSignal +} + +/** Parameters for {@link S3Client.createMultipartUpload}. */ +export interface CreateMultipartUploadParams { + key: string + fileType?: string + metadata?: Record + signal?: AbortSignal +} + +/** Parameters for {@link S3Client.uploadPart}. */ +export interface UploadPartParams { + key: string + uploadId: string + data: XMLHttpRequestBodyInit + partNumber: number + onProgress?: OnProgressFn + signal?: AbortSignal +} + +/** Parameters for {@link S3Client.listParts}. */ +export interface ListPartsParams { + uploadId: string + key: string + signal?: AbortSignal +} + +/** Parameters for {@link S3Client.completeMultipartUpload}. */ +export interface CompleteMultipartUploadParams { + key: string + uploadId: string + parts: UploadPart[] + signal?: AbortSignal +} + +/** Parameters for {@link S3Client.abortMultipartUpload}. */ +export interface AbortMultipartUploadParams { + key: string + uploadId: string + signal?: AbortSignal +} + +/** Parameters for {@link S3Client.deleteObject}. */ +export interface DeleteObjectParams { + key: string + signal?: AbortSignal +} + export interface ErrorWithCode { code?: string cause?: { code?: string } diff --git a/packages/@uppy/aws-s3/tests/s3-client/minio.test.ts b/packages/@uppy/aws-s3/tests/s3-client/minio.test.ts index 190c13c15..944a339e8 100644 --- a/packages/@uppy/aws-s3/tests/s3-client/minio.test.ts +++ b/packages/@uppy/aws-s3/tests/s3-client/minio.test.ts @@ -39,7 +39,7 @@ if (config) { beforeAll(async () => { for (const key of FILE_KEYS) { try { - await s3client.deleteObject(key) + await s3client.deleteObject({ key }) } catch { // intentionally ignore the errors as the objects don't exists. still feels hacky tbh } @@ -85,7 +85,11 @@ if (config) { 'Hello from pre-signed URL test.', ) - const result = await s3client.putObject(key, fileContents, 'text/plain') + const result = await s3client.putObject({ + key, + data: fileContents, + fileType: 'text/plain', + }) expect(result.location).toBeDefined() expect(result.location).toContain(key) @@ -99,30 +103,32 @@ if (config) { const partSize = 5 * 1024 * 1024 // 5MB const part = randomBytes(partSize) - const { uploadId } = await s3client.createMultipartUpload( + const { uploadId } = await s3client.createMultipartUpload({ key, - 'application/octet-stream', - ) + fileType: 'application/octet-stream', + }) expect(uploadId).toBeDefined() const partNumber = 1 - const { etag } = await s3client.uploadPart( + const { etag } = await s3client.uploadPart({ key, uploadId, - part, + data: part, partNumber, - ) + }) expect(etag).toBeDefined() - const result = await s3client.completeMultipartUpload(key, uploadId, [ - { etag, partNumber }, - ]) + const result = await s3client.completeMultipartUpload({ + key, + uploadId, + parts: [{ etag, partNumber }], + }) expect(result.etag).toBeDefined() expect(result.location).toBeDefined() expect(result.location).toContain(key) expect(result.key).toBe(key) - await s3client.deleteObject(key) + await s3client.deleteObject({ key }) }) // ===== getCredentials tests (STS) ===== @@ -158,13 +164,17 @@ if (config) { }) try { - const result = await s3.putObject(testKey, 'Hello STS!', 'text/plain') + const result = await s3.putObject({ + key: testKey, + data: 'Hello STS!', + fileType: 'text/plain', + }) expect(result.location).toBeDefined() expect(result.location).toContain(testKey) expect(result.location).not.toContain('X-Amz-Signature') expect(result.location).not.toContain('?') } finally { - await s3.deleteObject(testKey) + await s3.deleteObject({ key: testKey }) } }) @@ -196,16 +206,16 @@ if (config) { }, }) - await s3.putObject( - `sts-cache-1-${Date.now()}.txt`, - 'File 1', - 'text/plain', - ) - await s3.putObject( - `sts-cache-2-${Date.now()}.txt`, - 'File 2', - 'text/plain', - ) + await s3.putObject({ + key: `sts-cache-1-${Date.now()}.txt`, + data: 'File 1', + fileType: 'text/plain', + }) + await s3.putObject({ + key: `sts-cache-2-${Date.now()}.txt`, + data: 'File 2', + fileType: 'text/plain', + }) expect(fetchCount).toBe(1) }) @@ -238,25 +248,32 @@ if (config) { const partSize = 5 * 1024 * 1024 // 5MB const part = randomBytes(partSize) - const { uploadId } = await s3.createMultipartUpload( + const { uploadId } = await s3.createMultipartUpload({ key, - 'application/octet-stream', - ) + fileType: 'application/octet-stream', + }) expect(uploadId).toBeDefined() const partNumber = 1 - const { etag } = await s3.uploadPart(key, uploadId, part, partNumber) + const { etag } = await s3.uploadPart({ + key, + uploadId, + data: part, + partNumber, + }) expect(etag).toBeDefined() - const result = await s3.completeMultipartUpload(key, uploadId, [ - { etag, partNumber }, - ]) + const result = await s3.completeMultipartUpload({ + key, + uploadId, + parts: [{ etag, partNumber }], + }) expect(result.etag).toBeDefined() expect(result.location).toBeDefined() expect(result.location).toContain(key) expect(result.key).toBe(key) - await s3.deleteObject(key) + await s3.deleteObject({ key }) }) }) @@ -266,41 +283,45 @@ if (config) { // we don't need an explicit eTag method as we already get eTag in the putOject response it('putObject uploads successfully and returns ETag', async () => { - const response = await s3client.putObject(key, content, 'text/plain') + const response = await s3client.putObject({ + key, + data: content, + fileType: 'text/plain', + }) expect(response.etag).toBeDefined() - await s3client.deleteObject(key) + await s3client.deleteObject({ key }) }) it('putObject handles binary data', async () => { const binaryData = new Uint8Array(6).fill(0xff) - const response = await s3client.putObject( + const response = await s3client.putObject({ key, - binaryData, - 'application/octet-stream', - ) + data: binaryData, + fileType: 'application/octet-stream', + }) expect(response).toBeDefined() expect(response.etag).toBeDefined() // cleanup - await s3client.deleteObject(key) + await s3client.deleteObject({ key }) }) // test createMultipartUpload it('createMultipartUpload returns a valid uploadId', async () => { - const { uploadId } = await s3client.createMultipartUpload( - key_bin, - 'application/octet-stream', - ) + const { uploadId } = await s3client.createMultipartUpload({ + key: key_bin, + fileType: 'application/octet-stream', + }) expect(uploadId).toBeDefined() expect(typeof uploadId).toBe('string') expect(uploadId.length).toBeGreaterThan(0) // cleanup - await s3client.abortMultipartUpload(key, uploadId) + await s3client.abortMultipartUpload({ key, uploadId }) }) // test uploadPart @@ -308,18 +329,18 @@ if (config) { it('uploadPart returns partNumber and Etag', async () => { const partData = randomBytes(EIGHT_MB) - const { uploadId } = await s3client.createMultipartUpload( - key_bin, - 'application/octet-stream', - ) + const { uploadId } = await s3client.createMultipartUpload({ + key: key_bin, + fileType: 'application/octet-stream', + }) // upload part - const partResult = await s3client.uploadPart( - key_bin, + const partResult = await s3client.uploadPart({ + key: key_bin, uploadId, - partData, - 1, - ) + data: partData, + partNumber: 1, + }) expect(partResult).toBeDefined() expect(partResult.etag).toBeDefined() @@ -327,7 +348,7 @@ if (config) { expect(partResult.etag.length).toBe(32) // cleanup - await s3client.abortMultipartUpload(key, uploadId) + await s3client.abortMultipartUpload({ key, uploadId }) }) // end to end multipart flow @@ -337,10 +358,10 @@ if (config) { const partSize = EIGHT_MB const totalParts = Math.ceil(large_buffer.byteLength / partSize) - const { uploadId } = await s3client.createMultipartUpload( - key_bin, - 'application/octet-stream', - ) + const { uploadId } = await s3client.createMultipartUpload({ + key: key_bin, + fileType: 'application/octet-stream', + }) expect(uploadId).toBeDefined() // upload all parts @@ -351,7 +372,12 @@ if (config) { (i + 1) * partSize, ) uploadPromises.push( - s3client.uploadPart(key_bin, uploadId, partBuffer, i + 1), + s3client.uploadPart({ + key: key_bin, + uploadId, + data: partBuffer, + partNumber: i + 1, + }), ) } const uploadResponses = await Promise.all(uploadPromises) @@ -371,11 +397,11 @@ if (config) { etag: response.etag, })) - const completeResponse = await s3client.completeMultipartUpload( - key_bin, + const completeResponse = await s3client.completeMultipartUpload({ + key: key_bin, uploadId, parts, - ) + }) expect(completeResponse).toBeDefined() expect(typeof completeResponse).toBe('object') @@ -385,51 +411,62 @@ if (config) { // cleanup - await s3client.deleteObject(key_bin) + await s3client.deleteObject({ key: key_bin }) }) it('abortMultipartUpload cancels upload successfully', async () => { // start upload - const { uploadId } = await s3client.createMultipartUpload( - key_abort_multipart, - 'application/octet-stream', - ) + const { uploadId } = await s3client.createMultipartUpload({ + key: key_abort_multipart, + fileType: 'application/octet-stream', + }) expect(uploadId).toBeDefined() const partData = randomBytes(EIGHT_MB) - await s3client.uploadPart(key_abort_multipart, uploadId, partData, 1) + await s3client.uploadPart({ + key: key_abort_multipart, + uploadId, + data: partData, + partNumber: 1, + }) // abort - await s3client.abortMultipartUpload(key_abort_multipart, uploadId) + await s3client.abortMultipartUpload({ + key: key_abort_multipart, + uploadId, + }) }) it('listParts returns uploaded parts correctly', async () => { const partSize = EIGHT_MB - const { uploadId } = await s3client.createMultipartUpload( - key_list_parts, - 'application/octet-stream', - ) + const { uploadId } = await s3client.createMultipartUpload({ + key: key_list_parts, + fileType: 'application/octet-stream', + }) expect(uploadId).toBeDefined() const part1Data = randomBytes(partSize) const part2Data = randomBytes(partSize) - const part1Result = await s3client.uploadPart( - key_list_parts, + const part1Result = await s3client.uploadPart({ + key: key_list_parts, uploadId, - part1Data, - 1, - ) - const part2Result = await s3client.uploadPart( - key_list_parts, + data: part1Data, + partNumber: 1, + }) + const part2Result = await s3client.uploadPart({ + key: key_list_parts, uploadId, - part2Data, - 2, - ) + data: part2Data, + partNumber: 2, + }) - const parts = await s3client.listParts(uploadId, key_list_parts) + const parts = await s3client.listParts({ + uploadId, + key: key_list_parts, + }) expect(parts).toBeInstanceOf(Array) expect(parts.length).toBe(2) @@ -443,7 +480,7 @@ if (config) { expect(parts[1].etag).toBe(part2Result.etag) // cleanup abort upload - await s3client.abortMultipartUpload(key, uploadId) + await s3client.abortMultipartUpload({ key, uploadId }) }) }) } else {