mirror of
https://github.com/transloadit/uppy.git
synced 2026-07-17 16:50:22 +00:00
put signal into missing methods
and make all methods have single arg params
This commit is contained in:
parent
de7d9c2302
commit
260716b9d8
6 changed files with 295 additions and 212 deletions
|
|
@ -214,7 +214,7 @@ export default class S3Uploader<M extends Meta, B extends Body> {
|
|||
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<M extends Meta, B extends Body> {
|
|||
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<M extends Meta, B extends Body> {
|
|||
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<M extends Meta, B extends Body> {
|
|||
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<M extends Meta, B extends Body> {
|
|||
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<M extends Meta, B extends Body> {
|
|||
}
|
||||
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<string, unknown>,
|
||||
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<string, unknown>,
|
||||
) {
|
||||
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<IT.UploadPart[]> {
|
||||
public override async listParts({
|
||||
uploadId,
|
||||
key,
|
||||
}: IT.ListPartsParams): Promise<IT.UploadPart[]> {
|
||||
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<IT.UploadPart>,
|
||||
) {
|
||||
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 })}`,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<string, unknown>,
|
||||
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<string, unknown>,
|
||||
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<IT.UploadPart[]> {
|
||||
public async listParts(params: IT.ListPartsParams): Promise<IT.UploadPart[]> {
|
||||
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<void> {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
public async deleteObject(key: string): Promise<void> {
|
||||
public async deleteObject(params: IT.DeleteObjectParams): Promise<void> {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string, unknown>,
|
||||
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<string, unknown>, // 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<string, unknown>
|
||||
|
|
@ -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<IT.UploadPart[]> {
|
||||
public override async listParts({
|
||||
uploadId,
|
||||
key,
|
||||
signal,
|
||||
}: IT.ListPartsParams): Promise<IT.UploadPart[]> {
|
||||
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<string, unknown>
|
||||
|
|
@ -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<IT.UploadPart>,
|
||||
) {
|
||||
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<string, unknown>
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<string, unknown>
|
||||
onProgress?: OnProgressFn
|
||||
signal?: AbortSignal
|
||||
}
|
||||
|
||||
/** Parameters for {@link S3Client.createMultipartUpload}. */
|
||||
export interface CreateMultipartUploadParams {
|
||||
key: string
|
||||
fileType?: string
|
||||
metadata?: Record<string, unknown>
|
||||
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 }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue