diff --git a/docs/uploader/aws-s3-multipart.mdx b/docs/uploader/aws-s3-multipart.mdx index 51261d5d7..d2d629d47 100644 --- a/docs/uploader/aws-s3-multipart.mdx +++ b/docs/uploader/aws-s3-multipart.mdx @@ -169,6 +169,36 @@ const uppy = new Uppy() }); ``` +### Use with TypeScript + +Uppy always puts the response to an upload in `file.response.body`. If you want +this to be type safe with `@uppy/aws-s3`, you can import the `AwsBody` type and +pass it as the second genric to `Uppy`. + +```ts {3,12} showLineNumbers +import Uppy from '@uppy/core'; +import Dashboard from '@uppy/dashboard'; +import AwsS3, { type AwsBody } from '@uppy/aws-s3'; + +import '@uppy/core/dist/style.min.css'; +import '@uppy/dashboard/dist/style.min.css'; + +// Set this to `any` or `Record` +// if you do not set any metadata yourself +type Meta = { license: string }; + +const uppy = new Uppy() + .use(Dashboard, { inline: true, target: 'body' }) + .use(AwsS3, { endpoint: '...' }); + +const id = uppy.addFile(/* ... */); + +await uppy.upload(); + +const body = uppy.getFile(id).response.body!; +const { location } = body; // This is now type safe +``` + ## API ### Options diff --git a/packages/@uppy/aws-s3/src/HTTPCommunicationQueue.ts b/packages/@uppy/aws-s3/src/HTTPCommunicationQueue.ts index a4707ab48..3347d47f5 100644 --- a/packages/@uppy/aws-s3/src/HTTPCommunicationQueue.ts +++ b/packages/@uppy/aws-s3/src/HTTPCommunicationQueue.ts @@ -1,4 +1,4 @@ -import type { Meta, UppyFile } from '@uppy/utils/lib/UppyFile' +import type { Meta, Body, UppyFile } from '@uppy/utils/lib/UppyFile' import type { RateLimitedQueue, WrapPromiseFunctionType, @@ -6,7 +6,7 @@ import type { import { pausingUploadReason, type Chunk } from './MultipartUploader.ts' import type AwsS3Multipart from './index.js' import { throwIfAborted } from './utils.ts' -import type { Body, UploadPartBytesResult, UploadResult } from './utils.js' +import type { UploadPartBytesResult, UploadResult } from './utils.js' import type { AwsS3MultipartOptions, uploadPartBytes } from './index.js' function removeMetadataFromURL(urlString: string) { diff --git a/packages/@uppy/aws-s3/src/MultipartUploader.ts b/packages/@uppy/aws-s3/src/MultipartUploader.ts index 1e9b9bc33..2ca8fdf5b 100644 --- a/packages/@uppy/aws-s3/src/MultipartUploader.ts +++ b/packages/@uppy/aws-s3/src/MultipartUploader.ts @@ -1,8 +1,7 @@ import type { Uppy } from '@uppy/core' import { AbortController } from '@uppy/utils/lib/AbortController' -import type { Meta, UppyFile } from '@uppy/utils/lib/UppyFile' +import type { Meta, Body, UppyFile } from '@uppy/utils/lib/UppyFile' import type { HTTPCommunicationQueue } from './HTTPCommunicationQueue.js' -import type { Body } from './utils.js' const MB = 1024 * 1024 diff --git a/packages/@uppy/aws-s3/src/index.test.ts b/packages/@uppy/aws-s3/src/index.test.ts index cce88b432..afe1c1eda 100644 --- a/packages/@uppy/aws-s3/src/index.test.ts +++ b/packages/@uppy/aws-s3/src/index.test.ts @@ -3,8 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import 'whatwg-fetch' import nock from 'nock' import Core from '@uppy/core' -import AwsS3Multipart from './index.ts' -import type { Body } from './utils.js' +import AwsS3Multipart, { type AwsBody } from './index.ts' const KB = 1024 const MB = KB * KB @@ -13,20 +12,18 @@ describe('AwsS3Multipart', () => { beforeEach(() => nock.disableNetConnect()) it('Registers AwsS3Multipart upload plugin', () => { - const core = new Core() - core.use(AwsS3Multipart) + const core = new Core().use(AwsS3Multipart) // @ts-expect-error private property const pluginNames = core[Symbol.for('uppy test: getPlugins')]( 'uploader', - ).map((plugin: AwsS3Multipart) => plugin.constructor.name) + ).map((plugin: AwsS3Multipart) => plugin.constructor.name) expect(pluginNames).toContain('AwsS3Multipart') }) describe('companionUrl assertion', () => { it('Throws an error for main functions if configured without companionUrl', () => { - const core = new Core() - core.use(AwsS3Multipart) + const core = new Core().use(AwsS3Multipart) const awsS3Multipart = core.getPlugin('AwsS3Multipart')! const err = 'Expected a `endpoint` option' @@ -47,8 +44,7 @@ describe('AwsS3Multipart', () => { describe('non-multipart upload', () => { it('should handle POST uploads', async () => { - const core = new Core() - core.use(AwsS3Multipart, { + const core = new Core().use(AwsS3Multipart, { shouldUseMultipart: false, limit: 0, getUploadParameters: () => ({ @@ -103,11 +99,11 @@ describe('AwsS3Multipart', () => { }) describe('without companionUrl (custom main functions)', () => { - let core: Core - let awsS3Multipart: AwsS3Multipart + let core: Core + let awsS3Multipart: AwsS3Multipart beforeEach(() => { - core = new Core() + core = new Core() core.use(AwsS3Multipart, { shouldUseMultipart: true, limit: 0, @@ -254,7 +250,7 @@ describe('AwsS3Multipart', () => { }) it('retries uploadPartBytes when it fails once', async () => { - const core = new Core().use(AwsS3Multipart, { + const core = new Core().use(AwsS3Multipart, { shouldUseMultipart: true, createMultipartUpload, completeMultipartUpload: vi.fn(async () => ({ location: 'test' })), @@ -287,7 +283,7 @@ describe('AwsS3Multipart', () => { }) it('calls `upload-error` when uploadPartBytes fails after all retries', async () => { - const core = new Core().use(AwsS3Multipart, { + const core = new Core().use(AwsS3Multipart, { shouldUseMultipart: true, retryDelays: [10], createMultipartUpload, @@ -328,7 +324,7 @@ describe('AwsS3Multipart', () => { const newToken = 'new token' beforeEach(() => { - core = new Core() + core = new Core() core.use(AwsS3Multipart, { endpoint: '', headers: { @@ -358,19 +354,17 @@ describe('AwsS3Multipart', () => { }) describe('dynamic companionHeader using setOption', () => { - let core: Core - let awsS3Multipart: AwsS3Multipart + let core: Core + let awsS3Multipart: AwsS3Multipart const newToken = 'new token' it('companionHeader is updated before uploading file', async () => { - core = new Core() + core = new Core() core.use(AwsS3Multipart) /* Set up preprocessor */ core.addPreProcessor(() => { - awsS3Multipart = core.getPlugin('AwsS3Multipart') as AwsS3Multipart< - any, - Body - > + awsS3Multipart = + core.getPlugin>('AwsS3Multipart')! awsS3Multipart.setOptions({ endpoint: 'http://localhost', headers: { @@ -391,7 +385,7 @@ describe('AwsS3Multipart', () => { }) describe('file metadata across custom main functions', () => { - let core: Core + let core: Core const createMultipartUpload = vi.fn((file) => { core.setFileMeta(file.id, { ...file.meta, @@ -459,7 +453,7 @@ describe('AwsS3Multipart', () => { }) it('preserves file metadata if upload is completed', async () => { - core = new Core().use(AwsS3Multipart, { + core = new Core().use(AwsS3Multipart, { shouldUseMultipart: true, createMultipartUpload, signPart, @@ -520,7 +514,7 @@ describe('AwsS3Multipart', () => { } }) - core = new Core().use(AwsS3Multipart, { + core = new Core().use(AwsS3Multipart, { shouldUseMultipart: true, createMultipartUpload, signPart: signPartWithAbort, @@ -590,7 +584,7 @@ describe('AwsS3Multipart', () => { } }) - core = new Core().use(AwsS3Multipart, { + core = new Core().use(AwsS3Multipart, { shouldUseMultipart: true, createMultipartUpload, signPart: signPartWithPause, diff --git a/packages/@uppy/aws-s3/src/index.ts b/packages/@uppy/aws-s3/src/index.ts index 4d4432977..448ce5c48 100644 --- a/packages/@uppy/aws-s3/src/index.ts +++ b/packages/@uppy/aws-s3/src/index.ts @@ -4,7 +4,7 @@ import BasePlugin, { } from '@uppy/core/lib/BasePlugin.js' import { RequestClient } from '@uppy/companion-client' import type { RequestOptions } from '@uppy/utils/lib/CompanionClientProvider' -import type { Body as _Body, Meta, UppyFile } from '@uppy/utils/lib/UppyFile' +import type { Body, Meta, UppyFile } from '@uppy/utils/lib/UppyFile' import type { Uppy } from '@uppy/core' import EventManager from '@uppy/core/lib/EventManager.js' import { RateLimitedQueue } from '@uppy/utils/lib/RateLimitedQueue' @@ -21,7 +21,6 @@ import type { UploadResultWithSignal, MultipartUploadResultWithSignal, UploadPartBytesResult, - Body, } from './utils.js' import createSignedURL from './createSignedURL.ts' import { HTTPCommunicationQueue } from './HTTPCommunicationQueue.ts' @@ -33,13 +32,13 @@ interface MultipartFile extends UppyFile { s3Multipart: UploadResult } -type PartUploadedCallback = ( +type PartUploadedCallback = ( file: UppyFile, part: { PartNumber: number; ETag: string }, ) => void declare module '@uppy/core' { - export interface UppyEventMap { + export interface UppyEventMap { 's3-multipart:part-uploaded': PartUploadedCallback } } @@ -289,6 +288,8 @@ const defaultOptions = { retryDelays: [0, 1000, 3000, 5000], } satisfies Partial> +export type { AwsBody } from './utils.ts' + export default class AwsS3Multipart< M extends Meta, B extends Body, @@ -839,7 +840,7 @@ export default class AwsS3Multipart< ...result, }, status: 200, - uploadURL: result.location, + uploadURL: result.location as string, } this.resetUploaderReferences(file.id) diff --git a/packages/@uppy/aws-s3/src/utils.ts b/packages/@uppy/aws-s3/src/utils.ts index 49d1f032a..835d457dc 100644 --- a/packages/@uppy/aws-s3/src/utils.ts +++ b/packages/@uppy/aws-s3/src/utils.ts @@ -1,5 +1,5 @@ import { createAbortError } from '@uppy/utils/lib/AbortController' -import type { Body as _Body } from '@uppy/utils/lib/UppyFile' +import type { Body } from '@uppy/utils/lib/UppyFile' import type { AwsS3Part } from './index.js' @@ -23,6 +23,6 @@ export type UploadPartBytesResult = { location?: string } -export interface Body extends _Body { +export interface AwsBody extends Body { location: string }